OAuth + Security - 1 - 認證服務器配置

配置

基礎包依賴

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
================================== 在spring-boot中 ==================================
<dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
   <version>2.3.5.RELEASE</version>
</dependency>
================================== 或者在spring-cloud中 ==================================
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

配置三大核心

認證服務器的配置需要繼承 AuthorizationServerConfigurerAdapter 類,然後重寫內部的方法來獲得自己邏輯的token,其源碼如下:

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {

    // 配置安全約束,主要是對默認的6個端點的開啓與關閉配置
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
	}

    // 客戶端信息配置
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
	}

    // 認證服務器令牌訪問端點配置和令牌服務配置,可以替換默認的端點url,配置支持的授權模式
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
	}

}
  1. 配置客戶端詳細信息

ClientDetailsServiceConfigurer能夠使用內存或者JDBC來實現客戶端詳情服務
(ClientDetailsService ) , ClientDetailsService負責查找ClientDetails ,而ClientDetails有幾個重要的屬性如下列表:

  • clientld: (必須的)用來標識客戶的Id。
  • secret : ( 需要值得信任的客戶端)客戶端安全碼,如果有的話。
  • scope :用來限制客戶端的訪問範圍,如果爲空(默認)的話,那麼客戶端擁有全部的訪問範圍。
  • authorizedGrantTypes :此客戶端可以使用的授權類型,默認爲空。
  • authorities :此客戶端可以使用的權限(基於Spring Security authorities )。

客戶端詳情( ClientDetails )能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務(例如
將客戶端詳情存儲在一個關係數據庫的表中,就可以使用JdbcClientDetailsService或者通過自己實現
ClientRegistrationService接口(同時你也可以實現ClientDetailsService接口)來進行管理。

  1. 令牌訪問端點和令牌服務配置

AuthorizationServerEndpointsConfigurer這個對象的實例可以完成令牌服務以及令牌endpoint配置。

配置授權類型( Grant Types )

AuthorizationServerEndpointsConfigurer通過設定以下屬性決定支持的授權類型( Grant Types ) :

  • authenticationManager:認證管理器,當你選擇了資源所有者密碼(password)授權類型的時候,請設置這個屬性注入一個AuthenticationManager對象。
  • userDetailsService :如果你設置了這個屬性的話,那說明你有一個自己的UserDetailsService接口的實現,或者你可以把這個東西設置到全局域上面去(例如GlobalAuthenticationManagerConfigurer這個配置對象),當你設置了這個之後,那麼"refresh_token"即刷新令牌授權類型模式的流程中就會包含一個檢查,用來確保這個賬號是否仍然有效,假如說你禁用了這個賬戶的話。
  • authorizationCodeServices:這個屬性是用來設置授權碼服務的(即AuthorizationCodeServices的實例對象) ,主要用於"authorization_ code" 授權碼類型模式。
  • implicitGrantService :這個屬性用於設置隱式授權模式,用來管理隱式授權模式的狀態。
  • tokenGranter :當你設置了這個東西(即TokenGranter接口實現),那麼授權將會交由你來完全掌控,並且會忽略掉上面的這幾個屬性,這個屬性一般是用作拓展用途的,即標準的四種授權模式已經滿足不了你的需求的時候,纔會考慮使用這個。
配置授權端點的URL

默認的端點URL有以下6個:

  • /oauth/authorize : 授權端點。
  • /oauth/token : 令牌端點。
  • /oauth/confirm _access : 用戶確認授權提交端點。
  • /oauth/error : 授權服務錯誤信息端點。
  • /oauth/check_token : 用於資源服務訪問的令牌解析端點。
  • /oauth/token_key : 提供公有密匙的端點,如果你使用JWT令牌的話。

這些端點都可以通過配置的方式更改其路徑,在AuthorizationServerEndpointsConfigurer中有一個叫pathMapping()的方法用來配置端點URL鏈接,他有兩個參數:

  • 第一個參數:string類型,默認的URL
  • 第二個參數:string類型,進行替代的URL

以上的參數都是以"/"開始的字符串

  1. 配置令牌端點的安全約束

AuthorizationServerSecurityConfigurer : 用來配置令牌端點(Token Endpoint)的安全約束,如配置資源服務器需要驗證token的/oauth/check_token

總結

授權服務配置分成三大塊,可以關聯記憶。

既然要完成認證,它首先得知道客戶端信息從哪兒讀取,因此要進行客戶端詳情配置。

既然要頒發token,那必須得定義token的相關endpoint,以及token如何存取,以及客戶端支持哪些類型的token。

既然暴露除了一些endpoint,那對這些endpoint可以定義一些安全上的約束等。

詳細代碼如下:

  • 認證服務器配置類
/**
 * @author zhongyj <[email protected]><br/>
 * @date 2020/6/1
 */
@Configuration
@EnableAuthorizationServer
public class DimplesAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private PasswordEncoder passwordEncoder;

    private TokenStore tokenStore;

    private ClientDetailsService clientDetailsService;

    private AuthenticationManager authenticationManager;

    @Autowired
    public DimplesAuthorizationServerConfiguration(PasswordEncoder passwordEncoder, TokenStore tokenStore, ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager) {
        this.passwordEncoder = passwordEncoder;
        this.tokenStore = tokenStore;
        this.clientDetailsService = clientDetailsService;
        this.authenticationManager = authenticationManager;
    }

    /**
     * 令牌端點的安全約束
     *
     * @param security AuthorizationServerSecurityConfigurer
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 客戶端詳情服務,暫時配置在內存中,後期改爲存在數據庫
     *
     * @param clients ClientDetailsServiceConfigurer
     * @throws Exception Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("dimples")
                .secret(this.passwordEncoder.encode("123456"))
                // 爲了測試,所以開啓所有的方式,實際業務根據需要選擇
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(864000)
                .scopes("select")
                // false跳轉到授權頁面,在授權碼模式中會使用到
                .autoApprove(false)
                // 驗證回調地址
                .redirectUris("http://www.baidu.com");
    }

    /**
     * 令牌訪問端點和令牌訪問服務
     *
     * @param endpoints AuthorizationServerEndpointsConfigurer
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                // /oauth/token_key公開
                .authenticationManager(authenticationManager)
                // /oauth/check_token公開
                .authorizationCodeServices(authorizationCodeServices())
                // 允許表單獲取token
                .tokenServices(tokenServices());

    }

    /**
     * 令牌管理服務
     *
     * @return TokenServices
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        // 客戶端詳情服務
        services.setClientDetailsService(clientDetailsService);
        // 支持令牌刷新
        services.setSupportRefreshToken(true);
        // 令牌存儲策略
        services.setTokenStore(tokenStore);
        // 令牌默認有效期2小時
        services.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默認有效期2天
        services.setRefreshTokenValiditySeconds(259200);
        return services;
    }

    /**
     * 設置授權碼模式的授權碼如何存儲,暫時採用內存
     *
     * @return AuthorizationCodeServices
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }

}

  • Token存儲配置類
/**
 * @author zhongyj <[email protected]><br/>
 * @date 2020/6/1
 */
@Configuration
public class TokenConfigure {

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

}
  • Security 安全配置
/**
 * @author zhongyj <[email protected]><br/>
 * @date 2020/6/1
 */
@Configuration
public class DimplesWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private DimplesUserDetailServiceImpl userDetailsService;

    private PasswordEncoder passwordEncoder;

    @Autowired
    public DimplesWebSecurityConfiguration(DimplesUserDetailServiceImpl userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * 安全攔截機制(最重要)
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated()
                .and().formLogin();
    }

    /**
     * 非必須配置,可以不配
     * 認證管理配置
     * 連接數據查詢用戶信息,與數據庫中密碼比對
     *
     * @param auth AuthenticationManagerBuilder
     * @throws Exception Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }


    /**
     * 認證管理區配置,密碼模式需要用到
     *
     * @return AuthenticationManager
     * @throws Exception Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
  • 配置密碼
@Configuration
public class DimplesConfigure {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • 用戶信息獲取類
@Configuration
public class DimplesUserDetailServiceImpl implements UserDetailsService {

    private PasswordEncoder passwordEncoder;

    @Autowired
    public DimplesUserDetailServiceImpl(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模擬一個用戶,替代數據庫獲取邏輯
        DimplesUser user = new DimplesUser();
        user.setUserName(username);
        user.setPassword(this.passwordEncoder.encode("123456"));
        // 輸出加密後的密碼
        System.out.println(user.getPassword());

        return new User(username, user.getPassword(), user.isEnabled(),
                user.isAccountNonExpired(), user.isCredentialsNonExpired(),
                user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

@Data
public class DimplesUser implements Serializable {
    private static final long serialVersionUID = 3497935890426858541L;

    private String userName;

    private String password;

    private boolean accountNonExpired = true;

    private boolean accountNonLocked= true;

    private boolean credentialsNonExpired= true;

    private boolean enabled= true;
}

可能出現的問題

  • 最可能出現的問題,項目啓動時報循環依賴的錯誤,要注意代碼中在當前配置的類中寫的Bean不能再當前類中去使用@Autowired注入
  • 在登錄時控制檯報錯堆棧溢出

錯誤追蹤:

是由於配置密碼模式下的AuthenticationManager時,方法名稱爲authenticationManager,更改爲authenticationManagerBean即可

測試

授權碼模式下獲取token

  1. 首先獲取授權碼
    在瀏覽器輸入 http://127.0.0.1:8080/oauth/authorize?client_id=dimples&response_type=code&redirect_uri=http://www.baidu.com

跳轉到登錄界面,然後進行用戶登錄,登錄成功以後選擇用戶授權,獲取相應的授權碼,將會顯示在重定向URL的鏈接後面。如下圖:

  1. 獲取code

image

  1. 驗證用戶

image

  1. 授權

image

  1. 得到code

image

  1. 拿着這個獲取的code的值去/oauth/token端點獲取token,如下圖:

image

這個code有錯將不能獲取到token,因爲這個將會保存在程序內存中,同時這個code只能獲取一次token

密碼模式

/oauth/token?client_id=dimples&client_secret=123456&grant_type=password&username=dimples&password=123456

這種模式十分簡單,但是也意味着直接將用戶的敏感信息泄露給了client端,因此這種模式一般只用於client是我們自己開發的情況下。

image

客戶端模式

/oauth/token?client_id=dimples&client_secret=123456&grant_type=client_credentials

image

簡化模式使用較少,也比較簡單,此處不做測試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章