接着前一篇博客的代碼: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
測試刷新token請求:http://localhost:8090/auth/oauth/token
grant_type填refresh_token,refresh_token填上一步獲取到的refresh_token
發送請求后,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中了:
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中:
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碼:
jdbc存儲客戶端信息
oauth_client_details:客戶端詳情記錄表
注意:要使用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);
}
}
測試密碼認證模式,成功。
令牌端點的安全策略
- /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()
拒絕訪問的權限,要將這兩個端點認證或授權后可以訪問,因為后面資源服務器,要通過此端點檢驗令牌是否有效
配置AuthorizationServerConfig,重寫configure方法
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//所有人可以訪問/oauth/token_key后面獲取公鑰,默認拒絕訪問
security.tokenKeyAccess("permitAll()");
//認證后可訪問/oauth/check_token,默認拒絕訪問
security.checkTokenAccess("isAuthenticated()");
}
修改后,重新訪問