接着前一篇博客的代码: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()");
}
修改后,重新访问