一、登录接口(用户名+密码)
1、前端请求auth服务
http://127.0.0.1:72/oauth/pwdLogin
2、请求数据
{
"mobile": "134178101xx",
"password": "123456"
}
3、Controller方法
@SneakyThrows
@PostMapping("pwdLogin")
@SignMemberLoginLog(value = "APP_PWD", desc = "密码登录")
@ApiOperation(value = "会员登录(密码登录)")
public Result<Oauth2TokenDto> pwdLogin(@RequestBody MemberLoginPwdVO vo, HttpServletRequest request) {
if (StringUtil.isEmpty(vo.getClientId())) {
vo.setClientId("app");
}
vo.setIp(IpUtils.ip(request));
Map<String, String> params = getMemberbaseParam(vo, SecurityLoginTypeEnum.APP_PWD.getCode());
params.put("mobile", vo.getMobile());
params.put("password", vo.getPassword());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
Oauth2TokenDto oauth2TokenDto = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
return Result.success(oauth2TokenDto);
}
private Map<String, String> getMemberbaseParam(MemberLoginbaseVO vo, String grantType) {
Map<String, String> params = new HashMap<>();
params.put("client_id", vo.getClientId());
params.put("client_secret", "app");
params.put("grant_type", grantType);
params.put("scope", "all");
params.put("platform", vo.getPlatform());
//附加信息
params.put("version", vo.getVersion());
params.put("device", vo.getDevice());
params.put("iemi", vo.getIemi());
params.put("location", vo.getLocation());
params.put("ip", vo.getIp());
params.put("recommendCode", vo.getRecommendCode());
return params;
}
二、授权接口调用逻辑
2.1 AuthTokenComponent类
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
@Component
public class AuthTokenComponent {
@Autowired
private TokenEndpoint tokenEndpoint;
public Oauth2TokenDto getAccessToken(String clientId, String clientSecurity , List<GrantedAuthority> grantedAuthorities, Map<String, String> params) throws HttpRequestMethodNotSupportedException {
User principle = new User(clientId,clientSecurity,true,true,true,true,grantedAuthorities);
return getAccessToken(principle,params);
}
public Oauth2TokenDto getAccessToken(User principle, Map<String, String> params) throws HttpRequestMethodNotSupportedException {
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(principle,null,principle.getAuthorities());
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, params).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
return oauth2TokenDto;
}
}
调用tokenEndpoint.postAccessToken
生成token时,接口调用逻辑:
1、调用AuthenticationProvider接口(AdminAuthenticationProvider实现类)密码校验
2、调用UserDetailsService
接口(MyUserDetailsService实现类)获取用户信息
3、调用DefaultTokenServices
接口(CustomTokenServices实现类)生成token
2.2 AuthenticationProvider接口
1、MobilePasswordAuthenticationProvider实现类
@Setter
public class MobilePasswordAuthenticationProvider implements AuthenticationProvider {
private QmUserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
MobilePasswordAuthenticationToken authenticationToken = (MobilePasswordAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
String password = (String) authenticationToken.getCredentials();
SecurityUser user = userDetailsService.loadUserByMobile(mobile);
if (user == null) {
throw new QiMiaoException(ResultCode.JWT_USER_INVALID);
}
if(userDetailsService.checkBlock(mobile)) {
throw new QiMiaoException(ResultCode.JWT_USER_BLOCK);
}
if (!passwordEncoder.matches(password, user.getPassword())) {
userDetailsService.inc(mobile);
throw new QiMiaoException(ResultCode.JWT_USER_INVALID_PWD);
}
Map<String, String> parameters = (Map<String, String>)authenticationToken.getDetails();
if(null != parameters.get("platform")) {
user.setPlatform(parameters.get("platform"));
}
MobilePasswordAuthenticationToken authenticationResult = new MobilePasswordAuthenticationToken(user, password, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return MobilePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
2、MobileAuthenticationSecurityConfig配置类
@Component
public class MobileAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private QmUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SmsVcodeRdsHelper smsVcodeRdsHelper;
@Override
public void configure(HttpSecurity http) {
MobilePasswordAuthenticationProvider provider = new MobilePasswordAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(provider);
MobileSmsAuthenticationProvider smsProvider = new MobileSmsAuthenticationProvider();
smsProvider.setUserDetailsService(userDetailsService);
smsProvider.setSmsVcodeRdsHelper(smsVcodeRdsHelper);
http.authenticationProvider(smsProvider);
MobileoneKeyAuthenticationProvider oneKeyProvider = new MobileoneKeyAuthenticationProvider();
oneKeyProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(oneKeyProvider);
VisitorAuthenticationProvider visitorAuthenticationProvider = new VisitorAuthenticationProvider();
visitorAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(visitorAuthenticationProvider);
QRCodeAuthenticationProvider qrCodeAuthenticationProvider = new QRCodeAuthenticationProvider();
qrCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(qrCodeAuthenticationProvider);
SocialAuthenticationProvider socialAuthenticationProvider = new SocialAuthenticationProvider();
socialAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(socialAuthenticationProvider);
}
}
3、WebSecurityConfig配置类
@Configuration
@EnableWebSecurity
@import(DefaultPasswordConfig.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//管理系统登录
@Autowired
private AdminAuthenticationSecurityConfig adminAuthenticationSecurityConfig;
//App登录
@Autowired
private MobileAuthenticationSecurityConfig mobileAuthenticationSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/rsa/publicKey", "/actuator
@Bean
public TokenGranter tokenGranter() {
if (tokenGranter == null) {
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getAllTokenGranters());
}
return delegate.grant(grantType, tokenRequest);
}
};
}
return tokenGranter;
}
private List<TokenGranter> getAllTokenGranters() {
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
//获取默认的授权模式
List<TokenGranter> tokenGranters = getDefaultTokenGranters(tokenServices, authorizationCodeServices, requestFactory);
if (authenticationManager != null) {
// 添加social模式
tokenGranters.add(new SocialGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
// 添加手机号加密码授权模式
tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
// 添加手机号加密码授权模式
tokenGranters.add(new MobileSmsGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new AdminPwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new MobileoneKeyGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new VisitorGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new QRCodeGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
}
return tokenGranters;
}
private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerTokenServices tokenServices
, AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) {
List<TokenGranter> tokenGranters = new ArrayList<>();
// 添加授权码模式
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
// 添加刷新令牌的模式
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
// 添加隐士授权模式
tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
// 添加客户端模式
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
if (authenticationManager != null) {
// 添加密码模式
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
}
return tokenGranters;
}
private AuthorizationServerTokenServices tokenServices() {
if (tokenServices != null) {
return tokenServices;
}
this.tokenServices = createDefaultTokenServices();
return tokenServices;
}
private AuthorizationCodeServices authorizationCodeServices() {
if (authorizationCodeServices == null) {
authorizationCodeServices = new InMemoryAuthorizationCodeServices();
}
return authorizationCodeServices;
}
private OAuth2RequestFactory requestFactory() {
return new DefaultOAuth2RequestFactory(clientDetailsService);
}
private DefaultTokenServices createDefaultTokenServices() {
//token互踢
DefaultTokenServices tokenServices = new CustomTokenServices(true);
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(reuseRefreshToken);
tokenServices.setClientDetailsService(clientDetailsService);
tokenServices.setTokenEnhancer(tokenEnhancer());
addUserDetailsService(tokenServices, this.userDetailsService);
return tokenServices;
}
private TokenEnhancer tokenEnhancer() {
if (tokenEnhancer != null) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(tokenEnhancer);
return tokenEnhancerChain;
}
return null;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService));
tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));
}
}
}
四、生成toekn详解
生成token前,先从tokenStore里去toeken,看是否已经存在,如果存在则执行挤下线逻辑。tokenStore
对应的是实现类com.auth.store.CustomRedisTokenStore
。
4.1 TokenStore配置类
@Configuration
public class AuthRedisTokenStore {
@Bean
public TokenStore tokenStore(RedisConnectionFactory connectionFactory, RedisSerializer<Object> redisValueSerializer) {
return new CustomRedisTokenStore(connectionFactory, redisValueSerializer);
}
}
4.2 TokenStore实现类
@Slf4j
public class CustomRedisTokenStore implements TokenStore {
private static final String ACCESS = "{auth}access:";
private static final String AUTH_TO_ACCESS = "{auth}auth_to_access:";
private static final String REFRESH_AUTH = "{auth}refresh_auth:";
private static final String ACCESS_TO_REFRESH = "{auth}access_to_refresh:";
private static final String REFRESH = "{auth}refresh:";
private static final String REFRESH_TO_ACCESS = "{auth}refresh_to_access:";
private static final String RELATION_ID_TOKEN = "{auth}relation_id_token:";
private static final boolean springDataRedis_2_0 = ClassUtils.isPresent(
"org.springframework.data.redis.connection.RedisStandaloneConfiguration",
RedisTokenStore.class.getClassLoader());
private final RedisConnectionFactory connectionFactory;
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
private String prefix = "";
private Method redisConnectionSet_2_0;
private RedisSerializer<Object> redisValueSerializer;
public CustomRedisTokenStore(RedisConnectionFactory connectionFactory, RedisSerializer<Object> redisValueSerializer) {
this.connectionFactory = connectionFactory;
this.redisValueSerializer = redisValueSerializer;
if (springDataRedis_2_0) {
this.loadRedisConnectionMethods_2_0();
}
}
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
this.serializationStrategy = serializationStrategy;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
private void loadRedisConnectionMethods_2_0() {
this.redisConnectionSet_2_0 = ReflectionUtils.findMethod(
RedisConnection.class, "set", byte[].class, byte[].class);
}
private RedisConnection getConnection() {
return connectionFactory.getConnection();
}
private byte[] serialize(Object object) {
return serializationStrategy.serialize(object);
}
private byte[] serializeKey(String object) {
return serialize(prefix + object);
}
private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);
}
private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
}
private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);
}
private ClientDetails deserializeClientDetails(byte[] bytes) {
return (ClientDetails)redisValueSerializer.deserialize(bytes);
}
private byte[] serialize(String string) {
return serializationStrategy.serialize(string);
}
private String deserializeString(byte[] bytes) {
return serializationStrategy.deserializeString(bytes);
}
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] bytes;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializedKey);
} finally {
conn.close();
}
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
if (accessToken != null) {
OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
// Keep the stores consistent (maybe the same user is
// represented by this authentication but the details have
// changed)
storeAccessToken(accessToken, authentication);
}
}
return accessToken;
}
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
OAuth2Authentication auth2Authentication = readAuthentication(token.getValue());
//是否开启token续签
boolean isRenew = true;
if (isRenew && auth2Authentication != null) {
OAuth2Request clientAuth = auth2Authentication.getOAuth2Request();
//判断当前应用是否需要自动续签
if (checkRenewClientId(clientAuth.getClientId())) {
//获取过期时长
int validitySeconds = 2592000;
double expiresRatio = token.getExpiresIn() / (double)validitySeconds;
//判断是否需要续签,当前剩余时间小于过期时长的50%则续签
if (expiresRatio <= 0.5) {
//更新AccessToken过期时间
DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token;
oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
storeAccessToken(oAuth2AccessToken, auth2Authentication, true);
}
}
}
return auth2Authentication;
}
private boolean checkRenewClientId(String clientId) {
boolean result = true;
return result;
}
public String getToken(String id){
byte[] relationIdTokenKey = getRelationIdTokenKey(id);
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(relationIdTokenKey);
return deserializeString(bytes);
} finally {
conn.close();
}
}
private int getAccessTokenValiditySeconds(String clientId) {
RedisConnection conn = getConnection();
byte[] bytes;
try {
bytes = conn.get(serializeKey(SecurityConstants.CACHE_CLIENT_KEY + ":" + clientId));
} finally {
conn.close();
}
if (bytes != null) {
ClientDetails clientDetails = deserializeClientDetails(bytes);
if (clientDetails.getAccessTokenValiditySeconds() != null) {
return clientDetails.getAccessTokenValiditySeconds();
}
}
//返回默认值
return SecurityConstants.ACCESS_TOKEN_VALIDITY_SECONDS;
}
@Override
public OAuth2Authentication readAuthentication(String token) {
byte[] bytes;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token));
} finally {
conn.close();
}
return deserializeAuthentication(bytes);
}
@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
return deserializeAuthentication(bytes);
} finally {
conn.close();
}
}
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
storeAccessToken(token, authentication, false);
}
private byte[] getRelationIdTokenKey(String id){
return serializeKey(RELATION_ID_TOKEN + id);
}
private byte[] getRelationIdTokenKey(OAuth2Authentication authentication){
byte[] relationIdTokenKey = null;
Object obj = authentication.getPrincipal();
Object details = authentication.getUserAuthentication().getDetails();
if(obj!=null && details!=null){
if(obj instanceof SecurityUser && details instanceof HashMap){
SecurityUser user = (SecurityUser)obj;
Map map = (Map)details;
String clientId = String.valueOf(map.get("client_id"));
Long userId = user.getId();
relationIdTokenKey = serializeKey(RELATION_ID_TOKEN +clientId+":"+userId);
}
}else{
log.error("storeAccessToken 没有取到principal");
}
return relationIdTokenKey;
}
public SecurityUser getSecurityUser(String token){
SecurityUser user = null;
OAuth2Authentication auth2Authentication = readAuthentication(token);
if (auth2Authentication != null) {
Object obj = auth2Authentication.getPrincipal();
if (obj!=null && obj instanceof SecurityUser) {
user = (SecurityUser) obj;
}else{
log.error("getSecurityUser:解析User失败,"+obj);
}
}else{
log.error("getSecurityUser:auth2Authentication 为nuLl");
}
return user;
}
private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isRenew) {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] serializedToken = serialize(token.getValue());
byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
byte[] relationIdTokenKey = getRelationIdTokenKey(authentication);
RedisConnection conn = getConnection();
try {
byte[] oldAccessToken = conn.get(accessKey);
//如果token已存在,并且不是续签的话直接返回
if (!isRenew && oldAccessToken != null) {
return;
}
conn.openPipeline();
if (springDataRedis_2_0) {
try {
this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken);
this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth);
this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken);
if(relationIdTokenKey!=null){
this.redisConnectionSet_2_0.invoke(conn, relationIdTokenKey, serializedToken);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(accessKey, serializedAccessToken);
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
if(relationIdTokenKey!=null){
conn.set(relationIdTokenKey, serializedToken);
}
}
//如果是续签token,需要先删除集合里旧的值
if (oldAccessToken != null) {
if (!authentication.isClientonly()) {
conn.lRem(approvalKey, 1, oldAccessToken);
}
conn.lRem(clientId, 1, oldAccessToken);
}
if (!authentication.isClientonly()) {
conn.rPush(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
conn.expire(authKey, seconds);
conn.expire(authToAccessKey, seconds);
conn.expire(clientId, seconds);
conn.expire(approvalKey, seconds);
}
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = serialize(token.getRefreshToken().getValue());
byte[] auth = serialize(token.getValue());
byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());
if (springDataRedis_2_0) {
try {
this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth);
this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(refreshToAccessKey, auth);
conn.set(accessToRefreshKey, refresh);
}
expireRefreshToken(refreshToken, conn, refreshToAccessKey, accessToRefreshKey);
}
conn.closePipeline();
} finally {
conn.close();
}
}
private static String getApprovalKey(OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication() == null ? ""
: authentication.getUserAuthentication().getName();
return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
}
private static String getApprovalKey(String clientId, String userName) {
return clientId + (userName == null ? "" : ":" + userName);
}
@Override
public void removeAccessToken(OAuth2AccessToken accessToken) {
removeAccessToken(accessToken.getValue());
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
byte[] key = serializeKey(ACCESS + tokenValue);
byte[] bytes;
RedisConnection conn = getConnection();
try {
bytes = conn.get(key);
} finally {
conn.close();
}
return deserializeAccessToken(bytes);
}
public void removeAccessToken(String tokenValue) {
byte[] accessKey = serializeKey(ACCESS + tokenValue);
byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
RedisConnection conn = getConnection();
try {
byte[] access = conn.get(accessKey);
byte[] auth = conn.get(authKey);
conn.openPipeline();
conn.del(accessKey);
conn.del(accessToRefreshKey);
// Don't remove the refresh token - it's up to the caller to do that
conn.del(authKey);
conn.closePipeline();
OAuth2Authentication authentication = deserializeAuthentication(auth);
if (authentication != null) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] relationIdTokenKey = getRelationIdTokenKey(authentication);
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
conn.openPipeline();
conn.del(authToAccessKey);
conn.lRem(unameKey, 1, access);
conn.lRem(clientId, 1, access);
conn.del(serialize(ACCESS + key));
if(relationIdTokenKey!=null){
conn.del(relationIdTokenKey);
}
conn.closePipeline();
}
} catch (Exception e){
e.printStackTrace();
log.error("removeAccessToken 失败:{}",e.getMessage());
}finally {
conn.close();
}
}
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
RedisConnection conn = getConnection();
try {
byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue());
byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue());
byte[] serializedRefreshToken = serialize(refreshToken);
conn.openPipeline();
if (springDataRedis_2_0) {
try {
this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken);
this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(refreshKey, serializedRefreshToken);
conn.set(refreshAuthKey, serialize(authentication));
}
expireRefreshToken(refreshToken, conn, refreshKey, refreshAuthKey);
conn.closePipeline();
} catch (Exception e){
e.printStackTrace();
log.error("storeRefreshToken 失败:{}",e.getMessage());
}finally {
conn.close();
}
}
private void expireRefreshToken(OAuth2RefreshToken refreshToken, RedisConnection conn, byte[] refreshKey, byte[] refreshAuthKey) {
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
Date expiration = expiringRefreshToken.getExpiration();
if (expiration != null) {
int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue();
conn.expire(refreshKey, seconds);
conn.expire(refreshAuthKey, seconds);
}
}
}
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
byte[] key = serializeKey(REFRESH + tokenValue);
byte[] bytes;
RedisConnection conn = getConnection();
try {
bytes = conn.get(key);
} finally {
conn.close();
}
return deserializeRefreshToken(bytes);
}
@Override
public void removeRefreshToken(OAuth2RefreshToken refreshToken) {
removeRefreshToken(refreshToken.getValue());
}
public void removeRefreshToken(String tokenValue) {
RedisConnection conn = getConnection();
try {
byte[] refreshKey = serializeKey(REFRESH + tokenValue);
byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue);
byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue);
byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
conn.openPipeline();
conn.del(refreshKey);
conn.del(refreshAuthKey);
conn.del(refresh2AccessKey);
conn.del(access2RefreshKey);
conn.closePipeline();
} catch (Exception e){
e.printStackTrace();
log.error("removeRefreshToken 失败:{}",e.getMessage());
}finally {
conn.close();
}
}
@Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
removeAccessTokenUsingRefreshToken(refreshToken.getValue());
}
private void removeAccessTokenUsingRefreshToken(String refreshToken) {
byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken);
RedisConnection conn = getConnection();
byte[] bytes = null;
try {
bytes = conn.get(key);
conn.del(key);
} finally {
conn.close();
}
if (bytes == null) {
return;
}
String accessToken = deserializeString(bytes);
if (accessToken != null) {
removeAccessToken(accessToken);
}
}
@Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(clientId, userName));
List<byte[]> byteList;
RedisConnection conn = getConnection();
try {
byteList = conn.lRange(approvalKey, 0, -1);
} finally {
conn.close();
}
return getTokenCollections(byteList);
}
@Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
byte[] key = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + clientId);
List<byte[]> byteList;
RedisConnection conn = getConnection();
try {
byteList = conn.lRange(key, 0, -1);
} finally {
conn.close();
}
return getTokenCollections(byteList);
}
private Collection<OAuth2AccessToken> getTokenCollections(List<byte[]> byteList) {
if (byteList == null || byteList.size() == 0) {
return Collections.emptySet();
}
List<OAuth2AccessToken> accessTokens = new ArrayList<>(byteList.size());
for (byte[] bytes : byteList) {
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
accessTokens.add(accessToken);
}
return Collections.unmodifiableCollection(accessTokens);
}
}
五、token续期
方式:后端自动续期,APP前端不用处理。
在gateway服务中,每次校验token时,如果token离过期时间小于24小时,则自动续期。
5.1 gateway服务代码
public class CustomAuthenticationManager implements ReactiveAuthenticationManager {
private TokenStore tokenStore;
public CustomAuthenticationManager(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication)
.filter(a -> a instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap((accessTokenValue -> {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new AlanChenException(ResultCode.UNAUTHORIZED,"登录状态失效");
} else if (accessToken.isExpired()) {
throw new AlanChenException(ResultCode.UNAUTHORIZED,"登录状态失效");
} else {
OAuth2RefreshToken refreshToken= tokenStore.readRefreshToken(accessToken.getRefreshToken().getValue());
if(null == refreshToken) {
throw new AlanChenException(ResultCode.JWT_OFFLINE,"账号在其他设备登录了");
}
}
// token续期代码在readAuthentication方法里
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
throw new AlanChenException(ResultCode.FORBIDDEN,"没有权限");
}
return Mono.just(result);
}))
.cast(Authentication.class);
}
}
注意:gateway这里取token不是直接用的App前端传过来的token,而是通过Authentication来取的,这有这样后端token续期了才会有效。
5.2 CustomRedisTokenStore代码
@Slf4j
public class CustomRedisTokenStore implements TokenStore {
//其他代码省略
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
OAuth2Authentication auth2Authentication = readAuthentication(token.getValue());
if (auth2Authentication != null) {
//获取过期时长
int validitySeconds = 2592000; //2592000
int expiresRatio = validitySeconds - token.getExpiresIn();
//判断是否需要续签,当前剩余时间小于过期时长的24小时则续签
if (expiresRatio >= 24 * 3600) {
//更新AccessToken过期时间
DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token;
Date expiration = new Date(System.currentTimeMillis() + (validitySeconds * 1000L));
oAuth2AccessToken.setExpiration(expiration);
storeAccessToken(oAuth2AccessToken, auth2Authentication, true);
OAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(token.getRefreshToken().getValue(), expiration);
storeRefreshToken(refreshToken, auth2Authentication);
}
}
return auth2Authentication;
}
}