SpringCloud-OAuth2(二):實戰篇

竹根七發表於2021-06-18

如果不瞭解Oauth2 是什麼、工作流程的可以看我上一篇文章:
SpringCloud-OAuth2(一):基礎篇

這篇講的內容是:Oauth2在SpringBoot/SpringCloud中的實戰。

SpringBoot版本:2.2.5.Release
SpringCloud版本:Hoxton.SR9
JDK版本:1.8

1:POM配置

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <groupId>org.springframework.cloud</groupId>
        </dependency>
        <!--使用redis存放token-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--密碼加密解密依賴包-->
        <dependency>
            <groupId>org.jasypt</groupId>
            <artifactId>jasypt</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>

2:關鍵配置

2.1:認證服務配置-WebAuthorizationConfig

@Configuration
@EnableAuthorizationServer
public class WebAuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;
    private final TokenStore tokenStore;
    private final AuthorizationCodeServices authorizationCodeServices;
    private final AuthTokenExceptionHandler authTokenExceptionHandler;

    public WebAuthorizationConfig(AuthenticationManager authenticationManager,
                                  UserDetailsService userDetailsService,
                                  PasswordEncoder passwordEncoder,
                                  TokenStore tokenStore,
                                  AuthorizationCodeServices authorizationCodeServices,
                                  AuthTokenExceptionHandler authTokenExceptionHandler) {
        this.authenticationManager = authenticationManager;
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.tokenStore = tokenStore;
        this.authorizationCodeServices = authorizationCodeServices;
        this.authTokenExceptionHandler = authTokenExceptionHandler;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        String secret = PasswordHelper.encryptPassword(Oauth2ClientUserEnums.ADMIN.getClientSecret());

        clients.inMemory()
                .withClient(Oauth2ClientUserEnums.ADMIN.getClientId())
                .secret(secret)
                .scopes("all", "test")
                .resourceIds("admin")
                // autoApprove 可跳過授權頁直接返回code
                .autoApprove("all")
                .redirectUris("http://www.baidu.com")
                //客戶端認證所支援的授權型別 1:客戶端憑證 2:賬號密碼 3:授權碼 4:token重新整理 5:簡易模式
                .authorizedGrantTypes(CLIENT_CREDENTIALS, PASSWORD, REFRESH_TOKEN, AUTHORIZATION_CODE, IMPLICIT)
                //使用者角色
                .authorities("admin")
                //允許自動授權
                .autoApprove(false)
                //token 過期時間
                .accessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(12))
                //refresh_token 過期時間
                .refreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30))
        ;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .passwordEncoder(passwordEncoder)                //設定密碼編輯器
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")                   //開啟 /oauth/token_key 的訪問許可權控制
                .checkTokenAccess("permitAll()")                 //開啟 /oauth/check_token 驗證埠認證許可權訪問
        ;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置授權伺服器端點的屬性
        endpoints.authenticationManager(authenticationManager)    //認證管理器
                .tokenStore(tokenStore)
                .authorizationCodeServices(authorizationCodeServices)
                .userDetailsService(userDetailsService)
                .exceptionTranslator(authTokenExceptionHandler);
    }
}

註解:@EnableAuthorizationServer表明當前服務是認證服務。


介紹一下幾個基礎元件


①:authenticationManager
認證管理器,對客戶端憑證、使用者進行認證的地方。


②:tokenStore
存放token的地方,預設是存放在Inmemory(記憶體)中的。


③:authorizationCodeServices
code生成服務,使用預設的即可。


④:userDetailsService
使用者詳情服務,可重寫實現,使用者資訊從資料庫中載入。


⑤:authTokenExceptionHandler
自定義的 token 鑑別失敗異常處理器。


⑥:authClientExceptionHandler
自定義的 客戶端憑證 鑑別失敗異常處理器。

2.2:資源服務配置-WebResourceConfig

@Configuration
@EnableResourceServer
public class WebResourceConfig extends ResourceServerConfigurerAdapter {

    private final AuthClientExceptionHandler authClientExceptionHandler;

    public WebResourceConfig(AuthClientExceptionHandler authClientExceptionHandler) {
        this.authClientExceptionHandler = authClientExceptionHandler;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("admin").stateless(true).authenticationEntryPoint(authClientExceptionHandler);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 資源鏈路
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and().formLogin().permitAll()
                // 登入放通
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/favicon.ico")
                //.authenticated()
                .permitAll()
                // 其他請求都需認證
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                // 跨域
                .and()
                .cors()
                // 關閉跨站請求防護
                .and()
                .csrf()
                .disable();
    }

}

註解:@EnableResourceServer表明當前服務是認證服務。

筆記:
為什麼要放開 /favicon.ico 的訪問許可權?因為在進行授權碼模式的時候,一直無法跳轉到我定義的redirect_uri 地址中去。
使用 logging.level.org.springframework.security=debug 發現會訪問 /favicon.ico ,然後報錯,再然後就是調到登入頁去了。
因此 /favicon.ico 放開之後,認證正常,成功調到redirect_uri 地址並返回了code。(這是OAuth2的小坑嗎?)

2.3:安全配置-WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final PasswordEncoder passwordEncoder;

    public WebSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder);                  //為認證管理器配置passwordEncoder,無論客戶端憑證密碼還是使用者密碼都通過passwordEncoder進行密碼匹配
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return new VipUserDetailService();
    }

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

}

詳細配置可以翻閱我的GitHub:GitHub地址

3:認證授權演示

3.1:授權碼模式

注意!!!一定要配置redirect_uri,並且一定要允許表單登入!!!
以下是步驟:


①:瀏覽器輸入以下連結地址,會重定向到登入頁
http://localhost:8123/oauth/authorize?client_id=admin&client_secret=admin&response_type=code&redirect_uri=http://www.baidu.com&scope=test

drawing

②:輸入配置的使用者名稱密碼,回撥到授權頁

drawing

如果我們配置的scope是autoApprove的,即可跳過這步,直接到第③步,拿到code。


③:選擇 Approve,點選Authorize 即可調到redirect_uri地址
drawing


④:拿code 換 token

我的請求地址:127.0.0.1:8123/oauth/token?grant_type=authorization_code&code=h35Fh1&redirect_uri=http://www.baidu.com
需要配置Authorization,請求時會將客戶端憑證以base64格式放在請求頭中。

drawing

3.2:使用者密碼模式

我的請求地址:127.0.0.1:8123/oauth/token?grant_type=password&username=found&password=123456
需要配置Authorization,請求時會將客戶端憑證以base64格式放在請求頭中。

drawing

3.3:客戶端憑證模式

我的請求地址:127.0.0.1:8123/oauth/token?grant_type=client_credentials
需要配置Authorization,請求時會將客戶端憑證以base64格式放在請求頭中。

drawing

3.4:隱式授權模式

瀏覽器輸入以下地址即可拿到token:
http://localhost:8123/oauth/authorize?client_id=admin&client_secret=admin&response_type=token&redirect_uri=http://www.baidu.com

drawing

3.5:token 重新整理

我的請求地址:127.0.0.1:8123/oauth/token?grant_type=refresh_token&refresh_token=dde5f388-4ad7-4781-a1b7-aaafb3c34b10
refresh_token是伺服器頒發給客戶端的,作用就是在一定時間內,讓使用者不用重新登入。
需要配置Authorization,請求時會將客戶端憑證以base64格式放在請求頭中。

drawing

4:聊聊第三方登入

個人認為做第三方登入是當前比較流行的做法。

4.1:流程

①:比如我們現在登入ProcessOn,就可以選擇第三方登入。

drawing

②:點選QQ登入後會調到這個頁面↓↓↓↓↓↓↓↓↓↓↓↓

drawing

③:上面這個client_id 應該就是ProcessOn 官方申請的第三方客戶端憑證了。
選擇賬號密碼登入,輸入賬號密碼之後就可以拿到token,(我手速快截了個圖)

drawing

④:可以看到返回了access_token、過期時間。
隨後processOn既發起請求進行登入操作,登入成功進入使用者首頁,失敗則重定向到登入頁。
drawing
drawing

4.2:uml圖

drawing

認真辨別了一下,QQ第三方登入採用不是授權碼模式,而是Implicat Grant Type。(差點看走眼)

總結 :
學習總要從身邊找例子,這樣才能更好的理解。

相關文章