Oauth2認證模式之授權碼模式實現

  • 2019 年 10 月 3 日
  • 筆記

Oauth2認證模式之授權碼模式(authorization code)

本示例實現了Oauth2之授權碼模式,授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的後台伺服器,與"服務提供商"的認證伺服器進行互動。

閱讀本示例之前,你需要先有以下兩點基礎:

  • 需要對spring security有一定的配置使用經驗,用戶認證這一塊,spring security oauth2建立在spring security的基礎之上
  • oauth2開放授權標準基礎,可以穩步到OAuth2 詳解,瀏覽下授權碼模式,理解下基本概念

概述

實現 oauth2,可以簡易的分為三個步驟

  • 配置資源伺服器
  • 配置認證伺服器
  • 配置spring security

程式碼實現

1.pom.xml添加maven依賴

    <dependencies>          <dependency>              <groupId>org.springframework.security.oauth</groupId>              <artifactId>spring-security-oauth2</artifactId>              <version>2.3.6.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-thymeleaf</artifactId>          </dependency>      </dependencies>

2.配置資源伺服器

public class ResourceServerConfig {        private static final String RESOURCE_ID = "account";        @Configuration      @EnableResourceServer()      protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {            @Override          public void configure(ResourceServerSecurityConfigurer resources) {              resources.resourceId(RESOURCE_ID).stateless(true);          }            @Override          public void configure(HttpSecurity httpSecurity) throws Exception {              httpSecurity                      .requestMatchers()                      // 保險起見,防止被主過濾器鏈路攔截                      .antMatchers("/account/**").and()                      .authorizeRequests().anyRequest().authenticated()                      .and()                      .authorizeRequests()                      .antMatchers("/account/info/**").access("#oauth2.hasScope('get_user_info')")                      .antMatchers("/account/child/**").access("#oauth2.hasScope('get_childlist')");          }      }  }

3.配置認證伺服器

    @Override      public void configure(ClientDetailsServiceConfigurer clients) throws Exception {          clients.inMemory()                  .withClient("client1")                  .resourceIds(RESOURCE_ID)                  .authorizedGrantTypes("authorization_code", "refresh_token", "implicit")                  .authorities("ROLE_CLIENT")                  .scopes("get_user_info", "get_childlist")                  .secret("secret")                  .redirectUris("http://localhost:8081/client/account/redirect")                  .autoApprove(true)                  .autoApprove("get_user_info");      }

4.配置spring security

@Configuration  @EnableWebSecurity  public class SecurityConfig extends WebSecurityConfigurerAdapter {        @Bean      @Override      protected UserDetailsService userDetailsService() {          InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();          // 創建兩個記憶體用戶          manager.createUser(User.withUsername("admin").password("123456").authorities("USER").build());          manager.createUser(User.withUsername("lin").password("123456").authorities("USER").build());          return manager;      }        @Override      @Bean      public AuthenticationManager authenticationManagerBean() throws Exception {          return super.authenticationManagerBean();      }        @Bean      PasswordEncoder passwordEncoder(){          return NoOpPasswordEncoder.getInstance();      }        /**       * 密碼生成器(默認為bcrypt模式)       *       * @return       */  //    @Bean  //    PasswordEncoder passwordEncoder() {  //        return PasswordEncoderFactories.createDelegatingPasswordEncoder();  //    }        @Override      protected void configure(HttpSecurity httpSecurity) throws Exception {            httpSecurity.                  requestMatchers()                  // 必須登錄過的用戶才可以進行 oauth2 的授權碼申請                  .antMatchers("/", "/home", "/login", "/oauth/authorize")                  .and()                  .authorizeRequests()                  .anyRequest().permitAll()                  .and()                  .formLogin()                  .loginPage("/login")                  .and()                  .httpBasic()                  .disable()                  .exceptionHandling()                  .accessDeniedPage("/login?authorization_error=true")                  .and()                  .csrf()                  .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))                  .disable();      }  }

使用介紹

  • 找到AuthResServerApplication.java運行server服務,默認埠:8080
  • 找到ClientApplication.java運行client客戶端,埠:8081

1.嘗試直接訪問用戶資訊

http://localhost:8080/account/info/testAccount1/

返回未授權錯誤

<oauth>  <error_description>  Full authentication is required to access this resource  </error_description>  <error>unauthorized</error>  </oauth>

2.嘗試獲取授權碼

http://localhost:8080/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://localhost:8081/client/account/redirect

結果被主過濾器攔截,302 跳轉到登錄頁,因為 /oauth/authorize 端點是受保護的端點,必須登錄的用戶才能申請 code。

3.輸入用戶名和密碼

輸入用戶名和密碼 admin 123456
如上用戶名密碼是交給 SpringSecurity 的主過濾器用來認證的

4.登錄成功後,真正進行授權碼的申請

oauth/authorize 認證成功,會根據 redirect_uri 執行 302 重定向,並且帶上生成的 code,注意重定向到的是 8001 埠,這個時候已經是另外一個應用了。

localhost:8081/client/account/redirect?code=xxxx
程式碼中封裝了一個 http 請求,使得 client1 使用 restTemplate 向 server 發送 token 的申請,當然是使用 code 來申請的,並最終成功獲取到 access_token

{  access_token: "59a25558-f714-4ca8-aa87-c36f93c120bf",  token_type: "bearer",  refresh_token: "92436849-7ef7-4923-8270-5a2c9b464556",  expires_in: 43199,  scope: "get_user_info get_childlist"  }

5.攜帶 access_token 訪問account資訊

http://localhost:8080/account/info/testAccount1?access_token=59a25558-f714-4ca8-aa87-c36f93c120bf

6.正常返回資訊

{  name: "testAccount1",  nickName: "測試用戶1",  remark: "備註1",  childAccount: [  {  name: "testChild1_0",  nickName: "測試子用戶1_0",  remark: "0",  childAccount: null  },  {  name: "testChild1_1",  nickName: "測試子用戶1_1",  remark: "1",  childAccount: null  },  {  name: "testChild1_2",  nickName: "測試子用戶1_2",  remark: "2",  childAccount: null  },  {  name: "testChild1_3",  nickName: "測試子用戶1_3",  remark: "3",  childAccount: null  },  {  name: "testChild1_4",  nickName: "測試子用戶1_4",  remark: "4",  childAccount: null  },  {  name: "testChild1_5",  nickName: "測試子用戶1_5",  remark: "5",  childAccount: null  },  {  name: "testChild1_6",  nickName: "測試子用戶1_6",  remark: "6",  childAccount: null  },  {  name: "testChild1_7",  nickName: "測試子用戶1_7",  remark: "7",  childAccount: null  },  {  name: "testChild1_8",  nickName: "測試子用戶1_8",  remark: "8",  childAccount: null  },  {  name: "testChild1_9",  nickName: "測試子用戶1_9",  remark: "9",  childAccount: null  }  ]  }

資料