<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>

          OAuth2.0授權(quán)碼模式原理與實(shí)戰(zhàn)

          共 16995字,需瀏覽 34分鐘

           ·

          2021-05-29 07:17

          OAuth2.0是目前比較流行的一種開源授權(quán)協(xié)議,可以用來授權(quán)第三方應(yīng)用,允許在不將用戶名和密碼提供給第三方應(yīng)用的情況下獲取一定的用戶資源,目前很多網(wǎng)站或APP基于微信或QQ的第三方登錄方式都是基于OAuth2實(shí)現(xiàn)的。本文將基于OAuth2中的授權(quán)碼模式,采用數(shù)據(jù)庫配置方式,搭建認(rèn)證服務(wù)器與資源服務(wù)器,完成授權(quán)與資源的訪問。

          流程分析

          在OAuth2中,定義了4種不同的授權(quán)模式,其中授權(quán)碼模式(authorization code)功能流程相對更加完善,也被更多的系統(tǒng)采用。首先使用圖解的方式簡單了解一下它的授權(quán)流程:

          • 對上面的流程進(jìn)行一下說明:

            1、用戶訪問客戶端

            2、客戶端將用戶導(dǎo)向認(rèn)證服務(wù)器

            3、用戶登錄,并對第三方客戶端進(jìn)行授權(quán)

            4、認(rèn)證服務(wù)器將用戶導(dǎo)向客戶端事先指定的重定向地址,并附上一個授權(quán)碼

            5、客戶端使用授權(quán)碼,向認(rèn)證服務(wù)器換取令牌

            6、認(rèn)證服務(wù)器對客戶端進(jìn)行認(rèn)證以后,發(fā)放令牌

            7、客戶端使用令牌,向資源服務(wù)器申請獲取資源

            8、資源服務(wù)器確認(rèn)令牌,向客戶端開放資源

          在對授權(quán)碼模式的流程有了一定基礎(chǔ)的情況下,我們開始動手搭建項(xiàng)目。

          準(zhǔn)備工作

          1、在Project中創(chuàng)建兩個module,采用認(rèn)證服務(wù)器和資源服務(wù)器分離的架構(gòu):

          2、spring-security-oauth2是對Oauth2協(xié)議規(guī)范的一種實(shí)現(xiàn),這里可以直接使用spring-cloud-starter-oauth2,就不需要分別引入spring-securityoauth2了。在父pom中引入:

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-oauth2</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>

          3、數(shù)據(jù)庫建表,OAuth2需要的表結(jié)構(gòu)如下:

          DROP TABLE IF EXISTS `oauth_access_token`;
          CREATE TABLE `oauth_access_token`  (
            `token_id` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `token` blob NULL,
            `authentication_id` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `user_name` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `client_id` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `authentication` blob NULL,
            `refresh_token` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
          ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

          DROP TABLE IF EXISTS `oauth_client_details`;
          CREATE TABLE `oauth_client_details`  (
            `client_id` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
            `resource_ids` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `client_secret` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `scope` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `authorized_grant_types` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `web_server_redirect_uri` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `authorities` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `access_token_validity` int(11NULL DEFAULT NULL,
            `refresh_token_validity` int(11NULL DEFAULT NULL,
            `additional_information` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
            `autoapprove` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'false',
            PRIMARY KEY (`client_id`USING BTREE
          ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

          DROP TABLE IF EXISTS `oauth_code`;
          CREATE TABLE `oauth_code`  (
            `code` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `authentication` blob NULL
          ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

          DROP TABLE IF EXISTS `oauth_refresh_token`;
          CREATE TABLE `oauth_refresh_token`  (
            `token_id` varchar(255CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
            `token` blob NULL,
            `authentication` blob NULL
          ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

          • oauth_access_token:存儲生成的access_token,由類JdbcTokenStore操作

          • oauth_client_details:存儲客戶端的配置信息,由類JdbcClientDetailsService操作

          • oauth_code:存儲服務(wù)端系統(tǒng)生成的code的值,由類JdbcAuthorizationCodeServices操作

          • oauth_refresh_token:存儲刷新令牌的refresh_token,如果客戶端的grant_type不支持refresh_token,那么不會用到這張表,同樣由類JdbcTokenStore操作

          其余spring security相關(guān)的用戶表、角色表以及權(quán)限表的表結(jié)構(gòu)在這里省略,可以在文末的Git地址中下載。

          認(rèn)證服務(wù)器

          認(rèn)證服務(wù)器是服務(wù)提供者專門用來處理認(rèn)證授權(quán)的服務(wù)器,主要負(fù)責(zé)獲取用戶授權(quán)并頒發(fā)token,以及完成后續(xù)的token認(rèn)證工作。認(rèn)證部分功能主要由spring security 負(fù)責(zé),授權(quán)則由oauth2負(fù)責(zé)。

          1、開啟Spring Security配置

          @Configuration
          @EnableWebSecurity
          public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
              @Bean
              public BCryptPasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder();
              }

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

          通過@Configuration@EnableWebSecurity 開啟Spring Security配置,繼承WebSecurityConfigurerAdapter的方法,實(shí)現(xiàn)個性化配置。如果使用內(nèi)存配置用戶,可以重寫其中的configure方法進(jìn)行配置,由于我們使用數(shù)據(jù)庫中的用戶信息,所以不需要在這里進(jìn)行配置。并且采用認(rèn)證服務(wù)器和資源服務(wù)器分離,也不需要在這里對服務(wù)資源進(jìn)行權(quán)限的配置。

          在類中創(chuàng)建了兩個Bean,分別是用于處理認(rèn)證請求的認(rèn)證管理器AuthenticationManager,以及配置全局統(tǒng)一使用的密碼加密方式BCryptPasswordEncoder,它們會在認(rèn)證服務(wù)中被使用。

          2、開啟并配置認(rèn)證服務(wù)器

          @Configuration
          @EnableAuthorizationServer 
          public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
              @Autowired
              private AuthenticationManager authenticationManager;    //認(rèn)證管理器
              @Autowired
              private BCryptPasswordEncoder passwordEncoder;//密碼加密方式
              @Autowired
              private DataSource dataSource;  // 注入數(shù)據(jù)源
              @Autowired
              private UserDetailsService userDetailsService; //自定義用戶身份認(rèn)證

              @Bean
              public ClientDetailsService jdbcClientDetailsService(){
                  //將client信息存儲在數(shù)據(jù)庫中
                  return new JdbcClientDetailsService(dataSource);
              }

              @Bean
              public TokenStore tokenStore(){
                  //對token進(jìn)行持久化存儲在數(shù)據(jù)庫中,數(shù)據(jù)存儲在oauth_access_token和oauth_refresh_token
                  return new JdbcTokenStore(dataSource);
              }

              @Bean
              public AuthorizationCodeServices authorizationCodeServices() {
                  //加入對授權(quán)碼模式的支持
                  return new JdbcAuthorizationCodeServices(dataSource);
              }

              @Override
              public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                  //設(shè)置客戶端的配置從數(shù)據(jù)庫中讀取,存儲在oauth_client_details表
                  clients.withClientDetails(jdbcClientDetailsService());
              }

              @Override
              public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                  endpoints
                          .tokenStore(tokenStore())//token存儲方式
                          .authenticationManager(authenticationManager)// 開啟密碼驗(yàn)證,來源于 WebSecurityConfigurerAdapter
                          .userDetailsService(userDetailsService)// 讀取驗(yàn)證用戶的信息
                          .authorizationCodeServices(authorizationCodeServices())
                          .setClientDetailsService(jdbcClientDetailsService());
              }

              @Override
              public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
                  //  配置Endpoint,允許請求,不被Spring-security攔截
                  security.tokenKeyAccess("permitAll()"// 開啟/oauth/token_key 驗(yàn)證端口無權(quán)限訪問
                          .checkTokenAccess("isAuthenticated()"// 開啟/oauth/check_token 驗(yàn)證端口認(rèn)證權(quán)限訪問
                          .allowFormAuthenticationForClients()// 允許表單認(rèn)證
                          .passwordEncoder(passwordEncoder);   // 配置BCrypt加密
              }
          }

          在類中,通過@EnableAuthorizationServer 注解開啟認(rèn)證服務(wù),通過繼承父類AuthorizationServerConfigurerAdapter,對以下信息進(jìn)行了配置:

          • ClientDetailsServiceConfigurer:配置客戶端服務(wù),這里我們通過JdbcClientDetailsService從數(shù)據(jù)庫讀取相應(yīng)的客戶端配置信息,進(jìn)入源碼可以看到客戶端信息是從表oauth_client_details中拉取。

          • AuthorizationServerEndpointsConfigurer:用來配置授權(quán)(authorization)以及令牌(token)的訪問端點(diǎn),以及令牌服務(wù)的配置信息。該類作為一個裝載類,裝載了Endpoints所有的相關(guān)配置。

          • AuthorizationServerSecurityConfigurer:配置令牌端點(diǎn)(endpoint)的安全約束,OAuth2開放了端點(diǎn)用于檢查令牌,/oauth/check_token/oauth/token_key這些端點(diǎn)默認(rèn)受到保護(hù),在這里配置可被外部調(diào)用。

          3、采用從數(shù)據(jù)庫中獲取用戶信息的方式進(jìn)行身份驗(yàn)證

          @Service
          public class UserDetailServiceImpl implements UserDetailsService {
              @Autowired
              private TbUserService userService;
              @Autowired
              private TbPermissionService permissionService;

              @Override
              public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
                  TbUser tbUser = userService.getUserByUserName(userName);
                  if (tbUser==null){
                      throw new UsernameNotFoundException("username : "+userName+" is not exist");
                  }

                  List<GrantedAuthority> authorities=new ArrayList<>();
                  //獲取用戶權(quán)限
                  List<TbPermission> permissions = permissionService.getByUserId(tbUser.getId());
                  permissions.forEach(permission->{
                      authorities.add(new SimpleGrantedAuthority(permission.getEname()));
                  });
                  return new User(tbUser.getUsername(),tbUser.getPassword(),authorities);
              }
          }

          創(chuàng)建UserDetailServiceImpl 實(shí)現(xiàn)UserDetailsService接口,并實(shí)現(xiàn)loadUserByUsername方法,根據(jù)用戶名從數(shù)據(jù)庫查詢用戶信息及權(quán)限。

          4、啟動服務(wù)

          首先發(fā)起請求獲取授權(quán)碼(code),直接訪問下面的url

          http://localhost:9004/oauth/authorize?client_id=client1&redirect_uri=http://localhost:8848/nacos&response_type=code&scope=select

          看一下各個參數(shù)的意義:

          client_id:因?yàn)檎J(rèn)證服務(wù)器要知道是哪一個應(yīng)用在請求授權(quán),所以client_id就是認(rèn)證服務(wù)器給每個應(yīng)用分配的id

          redirect_uri:重定向地址,會在這個重定向地址后面附加授權(quán)碼,讓第三方應(yīng)用獲取code

          response_typecode表明采用授權(quán)碼認(rèn)證模式

          scope:需要獲得哪些授權(quán),這個參數(shù)的值是由服務(wù)提供商定義的,不能隨意填寫

          首先會重定向到登錄驗(yàn)證頁面,因?yàn)橹暗?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">url中只明確了第三方應(yīng)用的身份,這里要確定第三方應(yīng)用要請求哪一個用戶的授權(quán)。輸入數(shù)據(jù)庫表tb_user中配置的用戶信息 admin/123456

          注意url中請求的參數(shù)必須和在數(shù)據(jù)庫中的表oauth_client中配置的相同,如果不存在或信息不一致都會報(bào)錯,在參數(shù)填寫錯誤時會產(chǎn)生如下報(bào)錯信息:

          如果參數(shù)完全匹配,會請求用戶向請求資源的客戶端client授權(quán):

          點(diǎn)擊Authorize同意授權(quán),會跳轉(zhuǎn)到redirect_uri定義的重定向地址,并在url后面附上授權(quán)碼code

          這樣,用戶的登錄和授權(quán)的操作都在瀏覽器中完成了,接下來我們需要獲取令牌,發(fā)送post請求到/oauth/token接口,使用授權(quán)碼獲取access_token。在發(fā)送請求時,需要在請求頭中包含clientIdclientSecret,并且攜帶參數(shù) grant_typecoderedirect_uri,這里會對redirect_uri做二次驗(yàn)證:

          這樣,就通過/oauth/token端點(diǎn)獲取到了access_token,并一同拿到了它的令牌類型、過期時間、授權(quán)范圍信息,這個令牌將在請求資源服務(wù)器的資源時被使用。由于這個令牌在一定時間內(nèi)有效,客戶端可以在有效期內(nèi),將令牌保存在本地,避免重復(fù)申請。

          資源服務(wù)器

          資源服務(wù)器簡單來說就是資源的訪問入口,主要負(fù)責(zé)處理用戶數(shù)據(jù)的api調(diào)用,資源服務(wù)器中存儲了用戶數(shù)據(jù),并對外提供http服務(wù),可以將用戶數(shù)據(jù)返回給經(jīng)過身份驗(yàn)證的客戶端。資源服務(wù)器和認(rèn)證服務(wù)器可以部署在一起,也可以分離部署,我們這里采用分開部署的形式。

          1、配置資源服務(wù)器

          @Configuration
          @EnableResourceServer
          public class ResourceConfig extends ResourceServerConfigurerAdapter {
              @Bean
              public BCryptPasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder();
              }

              @Bean
              @Primary
              public RemoteTokenServices remoteTokenServices(){
                  final RemoteTokenServices tokenServices=new RemoteTokenServices();
                  //設(shè)置授權(quán)服務(wù)器check_token Endpoint 完整地址
                  tokenServices.setCheckTokenEndpointUrl("http://localhost:9004/oauth/check_token");
                  //設(shè)置客戶端id與secret,注意:client_secret 值不能使用passwordEncoder加密
                  tokenServices.setClientId("client1");
                  tokenServices.setClientSecret("client-secret");
                  return tokenServices;
              }

              @Override
              public void configure(HttpSecurity http) throws Exception {
                  http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
                  http.authorizeRequests()
                          .anyRequest().authenticated();
              }

              @Override
              public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                  resources.resourceId("oauth2").stateless(true);
              }
          }

          在類中主要實(shí)現(xiàn)了以下功能:

          • @EnableResourceServer注解表明開啟OAuth2資源服務(wù)器,在請求資源服務(wù)器的請求前,需要通過認(rèn)證服務(wù)器獲取access_token令牌,然后在訪問資源服務(wù)器中的資源時需要攜帶令牌才能正常進(jìn)行請求

          • 通過RemoteTokenServices實(shí)現(xiàn)自定義認(rèn)證服務(wù)器,這里配置了我們之前創(chuàng)建的認(rèn)證服務(wù)器

          • 重寫configure(HttpSecurity http)方法,開啟所有請求需要授權(quán)才可以訪問

          • 配置資源相關(guān)設(shè)置configure(ResourceServerSecurityConfigurer resources),這里只設(shè)置resourceId,作為該服務(wù)資源的唯一標(biāo)識

          2、測試接口,負(fù)責(zé)提供用戶信息

          @RestController
          public class TestController {
              @GetMapping("/user/{name}")
              public User user(@PathVariable String name){
                  return new User(name, 20);
              }
          }

          3、啟動服務(wù)

          不攜帶access_token,直接訪問接口http://127.0.0.1:9005/user/hydra:

          使用Postman,在Authorization中配置使用Bearer Token,并填入從認(rèn)證服務(wù)器獲取的access_token(或在Headers中的Authorization字段直接填寫Bearer 'access_token')。

          再次訪問接口,可以正常訪問接口資源,這樣就實(shí)現(xiàn)了對資源服務(wù)器內(nèi)資源的訪問,完成了認(rèn)證服務(wù)器與資源服務(wù)器的整合。

          項(xiàng)目git地址:https://github.com/trunks2008/oauth2


          往期推薦


          想要搭建個人博客?這4個Java 開源博客系統(tǒng),真香
          畢業(yè)設(shè)計(jì):Java簡易學(xué)生宿舍管理系統(tǒng)
          SpringBoot+Vue 完整的外賣系統(tǒng),手機(jī)端和后臺管理,附源碼!
          帶工作流的SpringBoot后臺管理項(xiàng)目,一個企業(yè)級快速開發(fā)解決方案
          哈哈哈,徒手給小區(qū)開發(fā)一套系統(tǒng)!看能換一個停車位不....

          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
          獲取方式:關(guān)注公眾號并回復(fù) java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
          明天見(??ω??)??


          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  无码一区二区三区免费 | 欧美v亚洲v综合v国产v妖精 | 成人做爰黄 片视频免费看 | 色老板视频在线观看 | 青娱乐 超碰 |