Spring Cloud实战 | 第九篇:token失效后,refresh_token刷新
一、解决措施
请求时返回access_token过期的异常时,浏览器发出一次使用refresh_token换取access_token的请求,获取到新的access_token之后,重试因access_token过期而失败的请求。
二、根据 gitee的2021-12-17- 2021-12-21修改的内容完善代码
以下摘出重要部分
1、修改ljf-auth的AuthorizationServerConfig代码
**
* @Auther: lijinfeng
* @Date: 2021/12/8
* @Description 描述:认证服务配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
// 认证管理器 WebSecurityConfig 中创建bean
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 客户端信息配置:client存储方式
*/
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
System.out.println("ljf-auth:AuthorizationServerConfig::configure::OAuth2客户端【数据库加载】");
clients.withClientDetails(clientDetailsService);
}
/**
* 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
*
* @param security 定义令牌端点上的安全约束。配置token获取合验证时的策略
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
// 允许所有人请求令牌
.tokenKeyAccess("permitAll()")
// 已验证的客户端才能请求check_token端点
.checkTokenAccess("isAuthenticated()")
// 允许表单登录
.allowFormAuthenticationForClients();
}
/**
* 配置授权(authorization)
* 以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
System.out.println("ljf-auth:配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)");
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenService()) //将令牌增强器注入endpoints中
.userDetailsService(userDetailsService) //用户认证
// refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
// 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
// 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
.reuseRefreshTokens(true)
;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
// 令牌(token)管理服务
@Bean
public AuthorizationServerTokenServices tokenService(){
DefaultTokenServices services = new DefaultTokenServices();
// 客户端详情服务
// 因为是向客户端颁发令牌,所以需要知道是哪一个客户端
services.setClientDetailsService(clientDetailsService);
// 支持刷新令牌
services.setSupportRefreshToken(true);
// 令牌存储策略
services.setTokenStore(tokenStore());
//令牌增强
services.setTokenEnhancer(jwtAccessTokenConverter());
// 时效在数据库中设置
// 令牌默认有效期24小时
// services.setAccessTokenValiditySeconds(60);
// 刷新令牌默认有效期3天
// services.setRefreshTokenValiditySeconds(20);
return services;
}
/**
* 使用非对称加密算法对token签名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
System.out.println("从classpath下的密钥库中获取密钥对(公钥+私钥)");
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("ljf.jks"), "123456".toCharArray());
KeyPair keyPair = factory.getKeyPair("ljf", "123456".toCharArray());
// CustomJwtTokenConverter自定义,可以添加属性
JwtAccessTokenConverter converter = new CustomJwtTokenConverter();
// CustomerAccessTokenConverter自定义,防止refresh_token刷新时无法获取用户信息。
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
// 设置公钥
converter.setKeyPair(keyPair);
return converter;
}
/**
* 从classpath下的密钥库中获取密钥对(公钥+私钥)
*/
@Bean
public KeyPair keyPair() {
System.out.println("从classpath下的密钥库中获取密钥对(公钥+私钥)");
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("ljf.jks"), "123456".toCharArray());
KeyPair keyPair = factory.getKeyPair("ljf", "123456".toCharArray());
return keyPair;
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false); // 用户不存在异常抛出
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
/**
* 密码编码器
* 注意:想要密码编码器生效,需要authenticationProvider()改方法注入
*
* 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式
* 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
2、ljf-auth添加OAuthExceptionHandler异常处理类
/**
* 异常处理
*/
@RestControllerAdvice
@Slf4j
public class OAuthExceptionHandler {
/**
* 用户不存在
*
* @param e
* @return
*/
@ExceptionHandler(UsernameNotFoundException.class)
public Result handleUsernameNotFoundException(UsernameNotFoundException e) {
return Result.error(ResultEnum.USER_NOT_EXIST);
}
/**
* 用户名和密码异常
*
* @param e
* @return
*/
@ExceptionHandler(InvalidGrantException.class)
public Result handleInvalidGrantException(InvalidGrantException e) {
return Result.error(ResultEnum.USERNAME_OR_PASSWORD_ERROR);
}
/**
* 账户异常(禁用、锁定、过期)
*
* @param e
* @return
*/
@ExceptionHandler({InternalAuthenticationServiceException.class})
public Result handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
return Result.error(ResultEnum.USER_ABNORMAL,e.getMessage());
}
/**
* token无效
*
* @param e
* @return
*/
@ExceptionHandler(InvalidTokenException.class)
public Result handleInvalidTokenException(InvalidTokenException e) {
if (e.getMessage().contains("Invalid refresh token (expired)"))
return Result.error(ResultEnum.REFRESH_TOKEN_EXPIRED,e.getMessage());
return Result.error(ResultEnum.TOKEN_INVALID,e.getMessage());
}
}
前端通过自定义返回信息,进行token刷新