<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          spring-cloud-oauth2實(shí)現(xiàn)用戶認(rèn)證及單點(diǎn)登錄

          共 11369字,需瀏覽 23分鐘

           ·

          2020-11-02 05:06

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          ? 作者?|??工程師小哥

          來源 |? urlify.cn/mI3qEz

          66套java從入門到精通實(shí)戰(zhàn)課程分享

          需求

          在微服務(wù)架構(gòu)中,我們有很多業(yè)務(wù)模塊,每個(gè)模塊都需要有用戶認(rèn)證,權(quán)限校驗(yàn)。有時(shí)候也會(huì)接入來自第三方廠商的應(yīng)用。要求是只登錄一次,即可在各個(gè)服務(wù)的授權(quán)范圍內(nèi)進(jìn)行操作。看到這個(gè)需求,立馬就想到了這不就是單點(diǎn)登錄嗎?于是基于這樣的需求,作者使用spring-cloud-oauth2去簡單的實(shí)現(xiàn)了下用戶認(rèn)證和單點(diǎn)登錄。

          相關(guān)介紹

          OAuth2

          OAuth2是一個(gè)關(guān)于授權(quán)的網(wǎng)絡(luò)標(biāo)準(zhǔn),他定制了設(shè)計(jì)思路和執(zhí)行流程。OAuth2一共有四種授權(quán)模式:授權(quán)碼模式(authorization code)、簡化模式(implicit)、密碼模式(resource owner password)和客戶端模式(client credentials)。數(shù)據(jù)的所有者告訴系統(tǒng)同意授權(quán)第三方應(yīng)用進(jìn)入系統(tǒng),獲取這些數(shù)據(jù)。于是數(shù)據(jù)所有者生產(chǎn)了一個(gè)短時(shí)間內(nèi)有效的授權(quán)碼(token)給第三方應(yīng)用,用來代替密碼,供第三方使用。具體流程請(qǐng)看下圖,具體的OAuth2介紹,可以參考這篇文章,寫的很詳細(xì)。(http://www.ruanyifeng.com/blog/2019/04/oauth_design.html)

          Token

          令牌(token)和密碼(password)的作用是一樣的,都可以進(jìn)入系統(tǒng)獲取資源,但是也有幾點(diǎn)不同:

          1. 令牌是短期的,到期會(huì)自動(dòng)失效,用戶無法修改。密碼是長期的,用戶可以修改,如果不修改,就不會(huì)發(fā)生變化。

          2. 令牌可以被數(shù)據(jù)所有者撤銷,令牌會(huì)立即失效。密碼一般不允許其他人撤銷,只能被操作權(quán)限更高的人或者本人修改/重制。

          3. 令牌是有權(quán)限范圍的,會(huì)被數(shù)據(jù)所有者授予。

          實(shí)現(xiàn)的功能

          本篇介紹的是通過密碼模式來實(shí)現(xiàn)單點(diǎn)登錄的功能。

          在微服務(wù)架構(gòu)中,我們的一個(gè)應(yīng)用可能會(huì)有很多個(gè)服務(wù)運(yùn)行,協(xié)調(diào)來處理實(shí)際的業(yè)務(wù)。這就需要用到單點(diǎn)登錄的技術(shù),來統(tǒng)一認(rèn)證調(diào)取接口的是哪個(gè)用戶。那總不能請(qǐng)求一次,就認(rèn)證一次,這么做肯定是不行的。那么就需要在認(rèn)證完用戶之后,給這個(gè)用戶授權(quán),然后發(fā)一個(gè)令牌(token),有效期內(nèi)用戶請(qǐng)求資源時(shí),就只需要帶上這個(gè)標(biāo)識(shí)自己身份的token即可。

          架構(gòu)說明

          認(rèn)證中心:oauth2-oauth-server,OAuth2的服務(wù)端,主要完成用戶Token的生成、刷新、驗(yàn)證等。

          微服務(wù):mzh-etl,微服務(wù)之一,接收到請(qǐng)求之后回到認(rèn)證中心(oauth2-oauth-server)去驗(yàn)證。

          代碼實(shí)現(xiàn)

          使用到的框架是java基礎(chǔ)的spring boot 和spring-cloud-oauth2

          認(rèn)證中心:
          1、引入需要的maven包

          ????org.springframework.boot
          ????spring-boot-starter-web


          ????org.springframework.cloud
          ????spring-cloud-starter-oauth2


          ????org.springframework.boot
          ????spring-boot-starter-data-redis


          ????org.springframework.boot
          ????spring-boot-starter-actuator

          因?yàn)?/span>spring-cloud-starter-oauth2中包含了spring-cloud-starter-security,所以就不用再單獨(dú)引入了,引入redis包是為了使用redis來存儲(chǔ)token。

          2、配置application.yml

          這里主要用到的是redis的配置,mysql數(shù)據(jù)庫的配置暫時(shí)沒有用到。

          spring:
          ??application:
          ????name:?oauth-server
          ??datasource:
          ????url:?jdbc:mysql://localhost:3306/mzh_oauth?useSSL=false&characterEncoding=UTF-8
          ????username:?root
          ????password:?admin123
          ????driver-class-name:?com.mysql.jdbc.Driver
          ????hikari:
          ??????connection-timeout:?30000
          ??????idle-timeout:?600000
          ??????max-lifetime:?1800000
          ??????maximum-pool-size:?9
          ??redis:
          ????database:?0
          ????host:?localhost
          ????port:?6379
          ????jedis:
          ??????pool:
          ????????max-active:?8
          ????????max-idle:?8
          ????????min-idle:?0
          ????timeout:?10000
          server:
          ??port:?8888
          ??use-forward-headers:?true

          management:
          ??endpoint:
          ????health:
          ??????enabled:?true
          3、spring security 權(quán)限配置

          需要繼承WebSecurityConfigurerAdapter

          /**
          ?*?@Author?mzh
          ?*?@Date?2020/10/24
          ?*/
          @Configuration
          @EnableWebSecurity
          @EnableGlobalMethodSecurity(prePostEnabled?=?true)
          public?class?WebSecurityConfig?extends?WebSecurityConfigurerAdapter?{

          ????@Autowired
          ????private?CustomUserDetailsService?customUserDetailsService;
          ??
          ????/**
          ?????*?修改密碼的加密方式
          ?????*?@return
          ?????*/
          ????@Bean
          ????public?PasswordEncoder?passwordEncoder(){
          ????????return?new?BCryptPasswordEncoder();
          ????}

          ????@Bean
          ????@Override
          ????public?AuthenticationManager?authenticationManagerBean()?throws?Exception{
          ????????return?super.authenticationManagerBean();
          ????}

          ????@Override
          ????protected?void?configure(AuthenticationManagerBuilder?auth)?throws?Exception{
          ???????//?如果使用BCryptPasswordEncoder,這里就必須指定密碼的加密類
          ???????auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
          ????}

          ????@Override
          ????protected?void?configure(HttpSecurity?http)?throws?Exception?{
          ????????http.authorizeRequests()
          ????????????????.antMatchers("/oauth/**").permitAll();
          ????}
          }

          BCryptPasswordEncoder是一個(gè)不可逆的密碼加密類,AuthenticationManager是OAuth2的password必須指定的授權(quán)管理Bean。

          CustomUserDetailsService這個(gè)類是被注入進(jìn)來的,熟悉spring security的同學(xué)應(yīng)該知道,spring security有一個(gè)自己的UserdetailsService用于權(quán)限校驗(yàn)時(shí)獲取用戶信息,但是很多時(shí)候不符合我們的業(yè)務(wù)場(chǎng)景,就需要重現(xiàn)實(shí)現(xiàn)這個(gè)類。

          4、實(shí)現(xiàn)CustomUserDetailsService

          UserDetailsService這個(gè)類的核心方法就是loadUserByUsername()方法,他接收一個(gè)用戶名,返回一個(gè)UserDetails對(duì)象。

          /**
          ?*?@Author?mzh
          ?*?@Date?2020/10/24
          ?*/
          @Component(value?=?"customUserDetailsService")
          public?class?CustomUserDetailsService?implements?UserDetailsService?{

          ????@Autowired
          ????private?PasswordEncoder?passwordEncoder;

          ????@Override
          ????public?UserDetails?loadUserByUsername(String?username)?throws?UsernameNotFoundException?{
          ????????//?1.?根據(jù)username?去數(shù)據(jù)庫查詢?user

          ????????//?2.獲取用戶的角色和權(quán)限

          ????????//?下面是寫死,暫時(shí)不和數(shù)據(jù)庫交互
          ????????if(!(("admin").equals(username))){
          ????????????throw?new?UsernameNotFoundException("the?user?is?not?found");
          ????????}else{
          ????????????String?role?=?"ADMIN_ROLE";
          ????????????List?authorities?=?new?ArrayList<>();
          ????????????authorities.add(new?SimpleGrantedAuthority(role));
          ????????????String?password?=?passwordEncoder.encode("123456");
          ????????????return?new?User(username,password,authorities);
          ????????}
          ????}
          }

          這里是在程序中寫死了用戶和權(quán)限。賬號(hào):admin,密碼:123456,權(quán)限:ADMIN_ROLE(注意是權(quán)限,不是角色),實(shí)際中應(yīng)該從數(shù)據(jù)庫獲取用戶和相關(guān)的權(quán)限,然后進(jìn)行認(rèn)證。

          5、OAuth2 配置

          OAuth2配置需要繼承AuthorizationServerConfigurerAdapter

          /**
          ?*?@Author?mzh
          ?*?@Date?2020/10/24
          ?*/
          @Configuration
          @EnableAuthorizationServer
          public?class?Oauth2Config?extends?AuthorizationServerConfigurerAdapter?{

          ????@Autowired
          ????private?PasswordEncoder?passwordEncoder;

          ????@Autowired
          ????private?UserDetailsService?customUserDetailsService;

          ????@Autowired
          ????private?AuthenticationManager?authenticationManager;

          ????@Autowired
          ????private?TokenStore?redisTokenStore;

          ????/**
          ?????*?對(duì)AuthorizationServerEndpointsConfigurer參數(shù)的重寫
          ?????*?重寫授權(quán)管理Bean參數(shù)
          ?????*?重寫用戶校驗(yàn)
          ?????*?重寫token緩存方式
          ?????*?@param?endpointsConfigurer
          ?????*?@throws?Exception
          ?????*/
          ????@Override
          ????public?void?configure(final?AuthorizationServerEndpointsConfigurer?endpointsConfigurer)?throws?Exception{
          ????????endpointsConfigurer.authenticationManager(authenticationManager)
          ????????????????.userDetailsService(customUserDetailsService)
          ????????????????.tokenStore(redisTokenStore);
          ????}

          ????/**
          ?????*?客戶端的參數(shù)的重寫
          ?????*?這里是將數(shù)據(jù)直接寫入內(nèi)存,實(shí)際應(yīng)該從數(shù)據(jù)庫表獲取
          ?????*?clientId:客戶端Id
          ?????*?secret:客戶端的密鑰
          ?????*?authorizedGrantTypes:授權(quán)方式
          ?????*?????authorization_code:?授權(quán)碼類型,
          ?????*?????implicit:?隱式授權(quán),
          ?????*?????password:?密碼授權(quán),
          ?????*?????client_credentials:?客戶端授權(quán),
          ?????*?????refresh_token:?通過上面4中方式獲取的刷新令牌獲取的新令牌,
          ?????*??????????????????????注意是獲取token和refresh_token之后,通過refresh_toke刷新之后的令牌
          ?????*?accessTokenValiditySeconds:?token有效期
          ?????*?scopes?用來限制客戶端訪問的權(quán)限,只有在scopes定義的范圍內(nèi),才可以正常的換取token
          ?????*?@param?clients
          ?????*?@throws?Exception
          ?????*/
          ????@Override
          ????public?void?configure(ClientDetailsServiceConfigurer?clients)?throws?Exception{
          ????????clients.inMemory()
          ????????????????.and()
          ????????????????.withClient("mzh-etl")
          ????????????????.secret(passwordEncoder.encode("mzh-etl-8888"))
          ????????????????.authorizedGrantTypes("refresh_token","authorization_code","password")
          ????????????????.accessTokenValiditySeconds(3600)
          ????????????????.scopes("all");
          ????}

          ????@Override
          ????public?void?configure(AuthorizationServerSecurityConfigurer?serverSecurityConfigurer)?throws?Exception{
          ????????serverSecurityConfigurer.allowFormAuthenticationForClients();
          ????????serverSecurityConfigurer.checkTokenAccess("permitAll()");
          ????????serverSecurityConfigurer.tokenKeyAccess("permitAll()");
          ????????serverSecurityConfigurer.passwordEncoder(passwordEncoder);
          ????}
          }
          6、啟動(dòng)服務(wù)

          上述步驟完成之后啟動(dòng)服務(wù),然后觀察IDEA下方的Endpoints中的Mappings,就可以找到相關(guān)的認(rèn)證端口。主要的有以下幾個(gè):

          POST?/oauth/authorize??授權(quán)碼模式認(rèn)證授權(quán)接口?
          GET/POST?/oauth/token??獲取?token?的接口?
          POST??/oauth/check_token??檢查?token?合法性接口

          到此,認(rèn)證中心就算是創(chuàng)建完成了。我們通過idea的REST Client 來請(qǐng)求一個(gè)token進(jìn)行測(cè)試。

          請(qǐng)求內(nèi)容如下:

          POST?http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all?
          Accept:?*/*?
          Cache-Control:?no-cache?
          Authorization:?Basic?dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

          第一行POST http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all?表示發(fā)起一個(gè)POST請(qǐng)求,請(qǐng)求路徑是/oauth/token,請(qǐng)求參數(shù)是grant_type=password表示認(rèn)證類型是password,username=admin&password=123456表示用戶名是admin,密碼是123456scope=all是權(quán)限相關(guān)的,之前在Oauth2Config中配置了scope是all。

          第四行表示在請(qǐng)求頭中加入一個(gè)字段Authorization,值為Basic空格base64(clientId:clientSecret),我們之前配置的clientId是“meh-etl”,clientSecret是"meh-etl-8888",所以這個(gè)值的base64是:bXpoLWV0bDptemgtZXRsLTg4ODg=

          運(yùn)行請(qǐng)求之后,如果參數(shù)都正確的話,獲取到返回的內(nèi)容如下:

          {
          ??//?token值,后面請(qǐng)求接口時(shí)都需要帶上的token
          ?"access_token":?"b4cb804c-93d2-4635-913c-265ff4f37309",
          ??//?token的形式
          ??"token_type":?"bearer",
          ??//?快過期時(shí)可以用這個(gè)換取新的token
          ??"refresh_token":?"5cac05f4-158f-4561-ab16-b06c4bfe899f",
          ??//?token的過期時(shí)間
          ?"expires_in":?3599,
          ??//?權(quán)限范圍
          ?"scope":?"all"
          }

          token值過期之后,可以通過refresh_token來換取新的access_token

          POST?http://localhost:8888/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282?
          Accept:?*/*?
          Cache-Control:?no-cache?
          Authorization:?Basic?dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

          這次grant_type的值為“refresh_token”,refresh_token的值是要過期的token的refresh_token值,也就是之前請(qǐng)求獲取Token的refresh_token值,請(qǐng)求之后會(huì)返回一個(gè)和獲取token時(shí)一樣格式的數(shù)據(jù)。

          微服務(wù)
          1、引入需要的maven包

          ????org.springframework.boot
          ????spring-boot-starter-web


          ????org.springframework.cloud
          ????spring-cloud-starter-oauth2


          ????org.springframework.boot
          ????spring-boot-starter-data-redis

          2、配置application.yml
          spring:
          ??application:
          ????name:?mzh-etl
          ??redis:
          ????database:?1
          ????host:?localhost
          ????port:?6379
          ????jedis:
          ??????pool:
          ????????max-active:?8
          ????????max-idle:?8
          ????????min-idle:?0
          ????timeout:?10000
          server:
          ??port:?8889
          security:
          ??oauth2:
          ????client:
          ??????#?需要和之前認(rèn)證中心配置中的一樣
          ??????client-id:?mzh-etl
          ??????client-secret:?mzh-etl-8888
          ??????#?獲取token的地址
          ??????access-token-uri:?http://localhost:8888/oauth/token
          ????resource:
          ??????id:?mzh-etl
          ??????user-info-uri:?user-info
          ????authorization:
          ??????#?檢查token的地址
          ??????check-token-access:?http://localhost:8888/oauth/check_token

          這里的配置一定要仔細(xì),必須和之前認(rèn)證中心中配置的一樣。

          3、資源配置

          在OAuth2中接口也稱為資源,資源的權(quán)限也就是接口的權(quán)限。spring-cloud-oauth2提供了關(guān)于資源的注解@EnableResourceServer

          /**
          ?*?@Author?mzh
          ?*?@Date?2020/10/24
          ?*/
          @Configuration
          @EnableResourceServer
          @EnableGlobalMethodSecurity(prePostEnabled?=?true)
          public?class?ResourceServerConfig?extends?ResourceServerConfigurerAdapter?{

          ????@Value("${security.oauth2.client.client-id}")
          ????private?String?clientId;

          ????@Value("${security.oauth2.client.client-secret}")
          ????private?String?clientSecret;

          ????@Value("${security.oauth2.authorization.check-token-access}")
          ????private?String?checkTokenEndpointUrl;

          ????@Autowired
          ????private?RedisConnectionFactory?redisConnectionFactory;

          ????@Bean("redisTokenStore")
          ????public?TokenStore?redisTokenStore(){
          ????????return?new?RedisTokenStore(redisConnectionFactory);
          ????}

          ????@Bean
          ????public?RemoteTokenServices?tokenService()?{
          ????????RemoteTokenServices?tokenService?=?new?RemoteTokenServices();
          ????????tokenService.setClientId(clientId);
          ????????tokenService.setClientSecret(clientSecret);
          ????????tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
          ????????return?tokenService;
          ????}

          ????@Override
          ????public?void?configure(ResourceServerSecurityConfigurer?resources)?throws?Exception?{
          ????????resources.tokenServices(tokenService());
          ????}

          ????@Override
          ????public?void?configure(HttpSecurity?http)?throws?Exception?{
          ????????http.authorizeRequests().antMatchers("/get/**").authenticated();
          ????}
          }
          4、創(chuàng)建一個(gè)接口
          @RestController
          public?class?UserController?{

          ????@GetMapping("get")
          ????@PreAuthorize("hasAuthority('ADMIN_ROLE')")
          ????public?Object?get(Authentication?authentication){
          ????????authentication.getAuthorities();
          ????????OAuth2AuthenticationDetails?details?=?(OAuth2AuthenticationDetails)?authentication.getDetails();
          ????????String?token?=?details.getTokenValue();
          ????????return?token;
          ????}
          }

          這個(gè)接口就是會(huì)返回一個(gè)請(qǐng)求他時(shí)攜帶的token值,@PreAuthorize會(huì)在請(qǐng)求接口時(shí)檢查是否用權(quán)限“ADMIN_ROLE”(之前認(rèn)證中心配置的權(quán)限)

          5、啟動(dòng)服務(wù)

          啟動(dòng)服務(wù),只有當(dāng)用戶有“ADMIN_ROLE“的時(shí)候,才能正確返回,否則返回401未授權(quán)

          同樣適用REST Client來發(fā)起一個(gè)請(qǐng)求:

          GET?http://localhost:8889/get?
          Accept:?*/*?
          Cache-Control:?no-cache?
          Authorization:?bearer?b4cb804c-93d2-4635-913c-265ff4f37309

          請(qǐng)求路徑是http://localhost:8889/get 然后在請(qǐng)求頭部帶上我們上一步驟獲取到的token,放入到Authorization中,格式是bearer空格token值,如果請(qǐng)求成功,就會(huì)把token原樣返回。





          粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取

          ???

          ?長按上方微信二維碼?2 秒
          即可獲取資料



          感謝點(diǎn)贊支持下哈?

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九精品在线视频 | 99色图| 亚洲乱伦一区二区三区 | 91蜜桃婷婷狠狠久久综合9色 | 91精品国久久久久久无码一区二区三区 |