Spring Security OAuth2:認證服務器策略配置


接着前一篇博客的代碼:https://www.cnblogs.com/wwjj4811/p/14503898.html

刷新令牌

如果用戶訪問的時候,客戶端的"訪問令牌"已經過期,則需要使用"更新令牌"申請一個新的訪問令牌。

客戶端發出更新令牌的HTTP請求,包含以下參數:

  • grant_type:表示使用的授權模式,此處的值固定為refresh_token,必選項。
  • refresh_token:表示早前收到的更新令牌,必選項。
  • scope:表示申請的授權范圍,不可以超出上一次申請的范圍,如果省略該參數,則表示與上一次相同

注意: 刷新令牌只在授權碼模式和密碼模式中才有, 對應的指定這兩種模式時, 類型加上refresh_token即可

UserDetailsService實現

創建 com.wj.oauth2.server.service.CustomUserDetailsService 實現 UserDetailsService 接口

@Component
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    //這里寫死的,也可以從數據庫查詢
    @Override
    public UserDetails loadUserByUsername(String u) throws UsernameNotFoundException {
        return new User("admin", passwordEncoder.encode("1234"),
                AuthorityUtils.commaSeparatedStringToAuthorityList("product"));
    }
}

配置SpringSecurityConfig

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService);
    }

    /**
     * password密碼模式需要使用此認證管理器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

配置AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer//開啟認證服務器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    /**  配置被允許訪問此認證服務器的客戶端詳情信息
     * 方式1:內存方式管理
     * 方式2:數據庫管理
     * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用內存方式
        clients.inMemory()
                // 客戶端id
                .withClient("wj-pc")
        // 客戶端密碼,要加密,不然一直要求登錄, 獲取不到令牌, 而且一定不能被泄露
        .secret(passwordEncoder.encode("wj-secret"))
        // 資源id, 如商品資源
        .resourceIds("product-server")
        // 授權類型, 可同時支持多種授權類型
        .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
        // 授權范圍標識,哪部分資源可訪問(all是標識,不是代表所有)
        .scopes("all")
        // false 跳轉到授權頁面手動點擊授權,true 不用手動授權,直接響應授權碼,
        .autoApprove(false)
        .redirectUris("http://www.baidu.com/");// 客戶端回調地址
    }

    /**
     * 重寫父類的方法
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密碼模式需要設置此認證管理器
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌獲取新令牌時需要
        endpoints.userDetailsService(customUserDetailsService);
    }
}

測試

以密碼認證模式為例:先獲取到refesh_token

image-20210309103030517

測試刷新token請求:http://localhost:8090/auth/oauth/token

image-20210309103513012

grant_type填refresh_token,refresh_token填上一步獲取到的refresh_token

image-20210309103638860

發送請求后,access_token就被刷新了

令牌管理策略

默認情況下,令牌通過 randomUUID 產生32位隨機數的來進行填充的,而產生的令牌默認是存儲在內存中。

內存存儲采用的是TokenStore接口的默認實現類InMemoryTokenStore , 開發時方便調試,適用單機版。

RedisTokenStore將令牌存儲到 Redis 非關系型數據庫中,適用於並發高的服務。

JdbcTokenStore基於 JDBC 將令牌存儲到關系型數據庫中,可以在不同的服務器之間共享令牌。

JwtTokenStore(JSON Web Token)將用戶信息直接編碼到令牌中,這樣后端可以不用存儲它,前端拿到令牌可以直接解析出用戶信息

redis存儲令牌

pom中需要引入redis的starter,上一篇博客已經引入過了。

新增TokenConfig 配置類:向容器中添加RedisTokenStore

@Configuration
public class TokenConfig {

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

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

修改AuthorizationServerConfig,令牌管理策略添加到端點:

    @Autowired
    private TokenStore tokenStore;

    /**
     * 重寫父類的方法
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密碼模式需要設置此認證管理器
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌獲取新令牌時需要
        endpoints.userDetailsService(customUserDetailsService);
        //設置token存儲策略
        endpoints.tokenStore(tokenStore);
    }

yml配置redis連接信息:

spring:
  redis:
    port: 6379
    host: 192.168.1.43

重啟應用再請求令牌,發現令牌已經存儲在redis中了:

image-20210309105220289

jdbc存儲令牌

oauth2相關sql:mysq5.7

create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);

create table oauth_code (
  code VARCHAR(256), authentication BLOB
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);

-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

修改TokenConfig

@Configuration
public class TokenConfig {

/*    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }*/

    @Bean
    public TokenStore tokenStore(DataSource dataSource){
        return new JdbcTokenStore(dataSource);
    }
}

修改完成后,重啟服務並請求令牌,發現access_token相關信息已經存儲到mysql中:

image-20210309110517206

jdbc管理授權碼

授權碼主要操作oauth_code表的,只有當 grant_type 為 "authorization_code" 時,該表中才會有數據產生; 其他的grant_type沒有使用該表。更多的細節請參考 JdbcAuthorizationCodeServices

默認情況下並未將授權碼保存到 oauth_code 表中,原因是 JdbcAuthorizationCodeServices 沒有添加到容器中。

開啟后,會將授權碼放到auth_code表,授權后就會刪除它

修改AuthorizationServerConfig:

@Configuration
@EnableAuthorizationServer//開啟認證服務器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    /**  配置被允許訪問此認證服務器的客戶端詳情信息
     * 方式1:內存方式管理
     * 方式2:數據庫管理
     * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用內存方式
        clients.inMemory()
                // 客戶端id
                .withClient("wj-pc")
        // 客戶端密碼,要加密,不然一直要求登錄, 獲取不到令牌, 而且一定不能被泄露
        .secret(passwordEncoder.encode("wj-secret"))
        // 資源id, 如商品資源
        .resourceIds("product-server")
        // 授權類型, 可同時支持多種授權類型
        .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
        // 授權范圍標識,哪部分資源可訪問(all是標識,不是代表所有)
        .scopes("all")
        // false 跳轉到授權頁面手動點擊授權,true 不用手動授權,直接響應授權碼,
        .autoApprove(false)
        .redirectUris("http://www.baidu.com/");// 客戶端回調地址
    }

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DataSource dataSource;

    /**
     * 重寫父類的方法
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密碼模式需要設置此認證管理器
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌獲取新令牌時需要
        endpoints.userDetailsService(customUserDetailsService);
        //設置token存儲策略
        endpoints.tokenStore(tokenStore);
        endpoints.authorizationCodeServices(authorizationCodeServices());
    }

    // 向容器中導入AuthorizationCodeServices
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }
}

重啟應用並獲取code碼:

image-20210309111330751

jdbc存儲客戶端信息

oauth_client_details:客戶端詳情記錄表

image-20210309112201087

注意:要使用BCryptPasswordEncoder為client_secret客戶端密碼加密

向該表插入測試數據:client_secret是wj-secret加密后的結果

INSERT INTO `study-security`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('wj-pc', 'product-server', '$2a$10$fTo73KCRzU3HXcPGtaTmxu9zDIrnoud6GvhlKF0sIxWzm7awSkGOK', 'all', 'authorization_code,password,implicit,client_credentials,refresh_token', 'http://www.baidu.com', NULL, 50000, NULL, NULL, 'false');

修改AuthorizationServerConfig:修改客戶端管理為jdbc方式

@Configuration
@EnableAuthorizationServer//開啟認證服務器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    // 授權碼管理策略
    @Bean
    public JdbcClientDetailsService JdbcClientDetailsService(){
        return new JdbcClientDetailsService(dataSource);
    }

    /**  配置被允許訪問此認證服務器的客戶端詳情信息
     * 方式1:內存方式管理
     * 方式2:數據庫管理
     * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(JdbcClientDetailsService());
    }

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DataSource dataSource;

    /**
     * 重寫父類的方法
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密碼模式需要設置此認證管理器
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌獲取新令牌時需要
        endpoints.userDetailsService(customUserDetailsService);
        //設置token存儲策略
        endpoints.tokenStore(tokenStore);
        endpoints.authorizationCodeServices(authorizationCodeServices());
    }

    // 向容器中導入AuthorizationCodeServices
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }
}

測試密碼認證模式,成功。

image-20210309115653211

令牌端點的安全策略

  • /oauth/authorize:申請授權碼 code, 涉及的類AuthorizationEndpoint
  • /oauth/token:獲取令牌 token, 涉及的類TokenEndpoint
  • /oauth/check_token:用於資源服務器請求端點來檢查令牌是否有效, 涉及的類CheckTokenEndpoint
  • /oauth/confirm_access:用戶確認授權提交, 涉及的類WhitelabelApprovalEndpoint
  • /oauth/error:授權服務錯誤信息, 涉及的類WhitelabelErrorEndpoint
  • /oauth/token_key:提供公有密匙的端點,使用 JWT 令牌時會使用 , 涉及的類TokenKeyEndpoint

默認情況下/oauth/check_token和/oauth/token_key端點默認是denyAll()

拒絕訪問的權限,要將這兩個端點認證或授權后可以訪問,因為后面資源服務器,要通過此端點檢驗令牌是否有效

image-20210309121509698

配置AuthorizationServerConfig,重寫configure方法

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    //所有人可以訪問/oauth/token_key后面獲取公鑰,默認拒絕訪問
    security.tokenKeyAccess("permitAll()");
    //認證后可訪問/oauth/check_token,默認拒絕訪問
    security.checkTokenAccess("isAuthenticated()");
}

修改后,重新訪問

image-20210309121601587


免責聲明!

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



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