OAuth2 合並服務器,刷新令牌,刪除令牌


6. 合並服務器

在項目比較小時,考慮到節省服務器資源,會考慮將授權服務器和資源服務器合並到一個項目中,避免啟動多個 Java 進程。良心的艿艿,編寫了四種授權模式的示例,如下圖所示:

 

 

具體的代碼實現,實際和上述每個授權模式對應的小節是基本一致的,只是說將代碼“”在了一個項目中。嘿嘿~

 

7. 刷新令牌

在 OAuth2.0 中,一共有兩類令牌:

  

  • 訪問令牌(Access Token)
  • 刷新令牌(Refresh Token)

訪問令牌過期時,我們可以使用刷新令牌向授權服務器獲取一個的訪問令牌。

可能會胖友有疑惑,為什么會有刷新令牌呢?每次請求資源服務器時,都會在請求上帶上訪問令牌,這樣它的泄露風險是相對高的。

因此,出於安全性的考慮,訪問令牌的過期時間比較短,刷新令牌的過期時間比較長。這樣,如果訪問令牌即使被盜用走,那么在一定的時間后,訪問令牌也能在較短的時間吼過期。當然,安全也是相對的,如果使用刷新令牌后,獲取到新的訪問令牌,訪問令牌后續可能被盜用。

艿艿整理了下,大家常用開放平台的令牌過期時間,讓大家更好的理解:

 

開放平台 Access Token 有效期 Refresh Token 有效期
微信開放平台 2 小時 未知
騰訊開放平台 90 天 未知
小米開放平台 90 天 10 年

 

7.1 示例項目

下面,復制出 lab-68-demo03-authorization-server-with-client-credentials 項目,搭建提供訪問令牌授權服務器。改動點如下圖所示:

 

 

 

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 用戶認證 Manager
     */
    @Autowired
    private AuthenticationManager authenticationManager;


    @Autowired
    private UserDetailsService userDetailsService;


    //配置使用的 AuthenticationManager 實現用戶認證的功能
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

//    @Override
//    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//        endpoints.authenticationManager(authenticationManager);
//    }

    //設置 /oauth/check_token 端點,通過認證后可訪問。
    //這里的認證,指的是使用 client-id + client-secret 進行的客戶端認證,不要和用戶認證混淆。
    //其中,/oauth/check_token 端點對應 CheckTokenEndpoint 類,用於校驗訪問令牌的有效性。
    //在客戶端訪問資源服務器時,會在請求中帶上訪問令牌。
    //在資源服務器收到客戶端的請求時,會使用請求中的訪問令牌,找授權服務器確認該訪問令牌的有效性。
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.checkTokenAccess("isAuthenticated()");
    }

    //進行 Client 客戶端的配置。
    //設置使用基於內存的 Client 存儲器。實際情況下,最好放入數據庫中,方便管理。
/*
*
* 創建一個 Client 配置。如果要繼續添加另外的 Client 配置,可以在 <4.3> 處使用 #and() 方法繼續拼接。
* 注意,這里的 .withClient("clientapp").secret("112233") 代碼段,就是 client-id 和 client-secret。
*補充知識:可能會有胖友會問,為什么要創建 Client 的 client-id 和 client-secret 呢?
*通過 client-id 編號和 client-secret,授權服務器可以知道調用的來源以及正確性。這樣,
*即使“壞人”拿到 Access Token ,但是沒有 client-id 編號和 client-secret,也不能和授權服務器發生有效的交互。
*/
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // <4.1>
                .withClient("clientapp").secret("112233") // <4.2> Client 賬號、密碼。
                .authorizedGrantTypes("password","refresh_token") //
                .scopes("read_userinfo", "read_contacts") // <4.2> 可授權的 Scope
                .accessTokenValiditySeconds(3000)
                .refreshTokenValiditySeconds(864000)
//                .and().withClient() // <4.3> 可以繼續配置新的 Client
        ;
    }

}

  

@Configuration
@EnableWebSecurity
public class    SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    @Bean(name = BeanIds.USER_DETAILS_SERVICE)
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }


    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.
                // 使用內存中的 InMemoryUserDetailsManager
                        inMemoryAuthentication()
                // 不使用 PasswordEncoder 密碼編碼器
                .passwordEncoder(passwordEncoder())
                // 配置 yunai 用戶
                .withUser("yunai").password("1024").roles("USER");
    }

}

  

① 在 OAuth2AuthorizationServerConfig 的 #configure(ClientDetailsServiceConfigurer clients) 方法中,在配置的 Client 的授權模式中,額外新增 "refresh_token" 刷新令牌。

  通過 #accessTokenValiditySeconds(int accessTokenValiditySeconds) 方法,設置訪問令牌的有效期。

  通過 #refreshTokenValiditySeconds(int refreshTokenValiditySeconds) 方法,設置刷新令牌的有效期。

② 在 OAuth2AuthorizationServerConfig 的 #configure(AuthorizationServerEndpointsConfigurer endpoints) 方法中,設置使用的 userDetailsService 用戶詳情 Service。

  而該 userDetailsService 是在 SecurityConfig 的 #userDetailsServiceBean() 方法創建的 UserDetailsService Bean。

友情提示:如果不進行 UserDetailsService 的設置,在使用刷新令牌獲取新的訪問令牌時,會拋出異常。

 

7.2 簡單測試

執行 AuthorizationServerApplication 啟動授權服務器。下面,我們使用 Postman 模擬一個 Client

① POST 請求 http://localhost:8080/oauth/token 地址,使用密碼模式進行授權。如下圖所示:

 

 額外多返回了 refresh_token 刷新令牌。

 

② POST 請求 http://localhost:8080/oauth/token 地址,使用刷新令牌模式進行授權。如下圖所示:

 

 

請求說明:

  

  • 通過 Basic Auth 的方式,填寫 client-id + client-secret 作為用戶名與密碼,實現 Client 客戶端有效性的認證。
  • 請求參數 grant_type 為 "refresh_token",表示使用刷新令牌模式
  • 請求參數 refresh_token,表示刷新令牌

在響應中,返回了新的 access_token 訪問令牌。注意,老的 access_token 訪問令牌會失效,無法繼續使用。

 

8. 刪除令牌

在用戶登出系統時,我們會有刪除令牌的需求。雖然說,可以通過客戶端本地刪除令牌的方式實現。但是,考慮到真正的徹底的實現刪除令牌,必然服務端自身需要刪除令牌。

友情提示:客戶端本地刪除令牌的方式實現,指的是清楚本地 Cookie、localStorage 的令牌緩存。

 

在 Spring Security OAuth2 中,並沒有提供內置的接口,所以需要自己去實現。筆者參看 《Spring Security OAuth2 – Simple Token Revocation》 文檔,實現刪除令牌的 API 接口。

具體的實現,通過調用 ConsumerTokenServices 的 #revokeToken(String tokenValue) 方法,刪除訪問令牌和刷新令牌。如下圖所示:

 

 

8.1 示例項目

下面,我們直接在授權服務器 lab-68-demo03-authorization-server-with-resource-owner-password-credentials 項目,修改接入刪除令牌的功能。改動點如下圖所示:

 

 

① 創建 TokenDemoController 類,提供 /token/demo/revoke 接口,調用 ConsumerTokenServices 的 #revokeToken(String tokenValue) 方法,刪除訪問令牌和刷新令牌。代碼如下:

@RestController
@RequestMapping("/token/demo")
public class TokenDemoController {

    @Autowired
    private ConsumerTokenServices tokenServices;

    @PostMapping(value = "/revoke")
    public boolean revokeToken(@RequestParam("token") String token) {
        return tokenServices.revokeToken(token);
    }

}

  ② 在 SecurityConfig 配置類,設置 /token/demo/revoke 接口無需授權,方便測試。代碼如下:

// SecurityConfig.java

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            // 設置 /token/demo/revoke 無需授權
            .mvcMatchers("/token/demo/revoke").permitAll()
            // 設置其它接口需要授權
            .anyRequest().authenticated();
}

  

8.2 簡單測試

執行 AuthorizationServerApplication 啟動授權服務器。下面,我們使用 Postman 模擬一個 Client

 

① POST 請求 http://localhost:8080/oauth/token 地址,使用密碼模式進行授權。如下圖所示:

 

 

② POST 請求 http://localhost:8080/token/demo/revoke 地址,刪除令牌。如下圖所示:

 

 

刪除成功。后續,胖友可以自己調用授權服務器的 oauth/check_token 接口,測試訪問令牌是否已經被刪除。

 

666. 彩蛋

至此,我們完整學習 Spring Security OAuth 框架。不過 Spring 團隊宣布該框架處於 Deprecation 廢棄狀態。如下圖所示:

 

 

 

 

同時,Spring 團隊正在實現新的 Spring Authorization Server 授權服務器,目前還處於 Experimental 實驗狀態。

 

實際項目中,根據艿艿了解到的情況,很少項目會直接采用 Spring Security OAuth 框架,而是自己參考它進行 OAuth2.0 的實現。並且,一般只會實現密碼授權模式。

 

在本文中,我們采用基於內存的 InMemoryTokenStore,實現訪問令牌和刷新令牌的存儲。它會存在兩個明顯的缺點

  

  • 重啟授權服務器時,令牌信息會丟失,導致用戶需要重新授權。
  • 多個授權服務器時,令牌信息無法共享,導致用戶一會授權成功,一會授權失敗。

因此,下一篇《芋道 Spring Security OAuth2 存儲器》文章,我們來學習 Spring Security OAuth 提供的基於數據庫和 Redis的存儲器。走起~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM