一、登录接口(用户名+密码)
1、前端请求auth服务
http://127.0.0.1:72/oauth/userLogin
2、请求数据
{
"mobile": "134178101xx",
"password": "123456"
}
3、Controller方法
@PostMapping("userLogin")
@ApiOperation(value = "用户登录")
public Result<Oauth2TokenDto> userLogin(@RequestBody @Valid UserLoginVO userLoginVO) {
Map<String, String> params = new HashMap<>();
params.put("mobile", userLoginVO.getMobile());
params.put("password", userLoginVO.getPassword());
params.put("client_id", userLoginVO.getClientId());
params.put("client_secret", "app");
params.put("grant_type", SecurityLoginTypeEnum.ADMIN_PWD.name());
params.put("scope", "all");
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
Oauth2TokenDto oauth2TokenDto = authTokenComponent.getAccessToken(userLoginVO.getClientId(), "app", grantedAuthorities, params);
return Result.success(oauth2TokenDto);
}
二、授权接口调用逻辑
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、AdminAuthenticationProvider实现类
@Setter
public class AdminAuthenticationProvider implements AuthenticationProvider {
private QmUserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
String userName = (String) authenticationToken.getPrincipal();
String password = (String) authenticationToken.getCredentials();
UserDetails user = userDetailsService.loadAdminUser(userName);
if (user == null) {
throw new Exception(ResultCode.JWT_USER_INVALID);
}
if(userDetailsService.checkBlock(userName)) {
throw new Exception(ResultCode.JWT_USER_BLOCK);
}
//密码校验
if (!passwordEncoder.matches(password, user.getPassword())) {
userDetailsService.inc(userName);
throw new Exception(ResultCode.JWT_USER_INVALID);
}
UsernamePasswordAuthenticationToken authenticationResult = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
2、AdminAuthenticationProvider配置类
@Component
public class AdminAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private QmUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(HttpSecurity http) {
//admin provider
AdminAuthenticationProvider provider = new AdminAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(provider);
}
}
3、AdminAuthenticationSecurityConfig配置类
@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/**").permitAll()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/oauth/**").permitAll()
// swagger
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest()
//授权服务器关闭basic认证
.permitAll()
.and()
.logout()
.logoutUrl(SecurityConstants.LOGOUT_URL)
.logoutSuccessHandler(oauthLogoutSuccessHandler)
.addLogoutHandler(oauthLogoutHandler)
.clearAuthentication(true)
.and()
.apply(mobileAuthenticationSecurityConfig)
.and()
.apply(adminAuthenticationSecurityConfig)
.and()
.csrf().disable()
// 解决不允许显示在iframe的问题
.headers().frameOptions().disable().cacheControl();
// 基于密码 等模式可以无session,不支持授权码模式
if (authenticationEntryPoint != null) {
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
} else {
// 授权码模式单独处理,需要session的支持,此模式可以支持所有oauth2的认证
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
}
}
2.3 MyUserDetailsService接口 & MyUserDetailServiceImpl类
public interface MyUserDetailsService extends UserDetailsService {
SecurityUser loadUserByMobile(String mobile);
SecurityUser loadUserById(Long id);
SecurityUser loadUserByOpenId(String openId, String platform);
SecurityUser loadUserByImei(String im);
SecurityUser loadAdminUser(String mobileOrUserName);
SecurityUser createUserByMobile(String mobile);
SecurityUser createUserByMobile(String globalCode, String mobile);
SecurityUser createVisitor(Map<String, String> map);
}
@Slf4j
public class MyUserDetailServiceImpl implements MyUserDetailsService{
@Override
public SecurityUser loadAdminUser(String mobileOrUserName) {
SecurityUser securityUser = null;
Result<UserDTO> userDTOResult = userFeignService.selectByMobile(mobileOrUserName);
if (ResultCode.SUCCESS.getCode() == userDTOResult.getCode()) {
if (userDTOResult.getData() == null) {
throw new Exception(ResultCode.JWT_USER_INVALID);
}
UserDTO userDTO = userDTOResult.getData();
//是否被禁用
if (!userDTO.getStatus()) {
throw new Exception(ResultCode.JWT_USER_ENABLED);
}
securityUser = new SecurityUser();
securityUser.setUserType(SecurityUserTypeEnum.ADMIN);
securityUser.setGrantType(SecurityLoginTypeEnum.ADMIN_PWD);
securityUser.setId(userDTO.getId());
securityUser.setUsername(userDTO.getLoginName());
securityUser.setPassword(userDTO.getLoginPwd());
securityUser.setEnabled(true);
Collection<SimpleGrantedAuthority> authorities = new HashSet<>();
//基于权限控制
Result<List<PermDTO>> dtoResult = permFeignService.permListByUserId(userDTO.getId());
if (ResultCode.SUCCESS.getCode() == dtoResult.getCode()) {
if (dtoResult.getData() != null) {
for (PermDTO dto : dtoResult.getData()) {
authorities.add(new SimpleGrantedAuthority(dto.getPermApiHttpMethod() + dto.getPermValue()));
}
}
}
securityUser.setAuthorities(authorities);
}
return securityUser;
}
}
2.4 CustomTokenServices类
@Slf4j
public class CustomTokenServices extends DefaultTokenServices {
private TokenStore tokenStore;
private TokenEnhancer accessTokenEnhancer;
//是否登录同应用同账号互踢
private boolean isSingleLogin;
public CustomTokenServices(boolean isSingleLogin) {
this.isSingleLogin = isSingleLogin;
}
@Override
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
log.info("createAccessToken,start existingAccessToken={}",existingAccessToken);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (isSingleLogin) {
if (existingAccessToken.getRefreshToken() != null) {
tokenStore.removeRefreshToken(existingAccessToken.getRefreshToken());
log.info("createAccessToken,removeRefreshToken A={}",existingAccessToken);
log.info("createAccessToken,getRefreshToken A1={}",existingAccessToken.getRefreshToken());
}
} else if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
log.info("createAccessToken,isExpired B={}",existingAccessToken);
} else {
// oidc每次授权都刷新id_token
existingAccessToken = refreshIdToken(existingAccessToken, authentication);
tokenStore.storeAccessToken(existingAccessToken, authentication);
log.info("createAccessToken,isExpired C={}",existingAccessToken);
return existingAccessToken;
}
}
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
log.info("createAccessToken,end accessToken={}",accessToken);
return accessToken;
}
}
三、AuthenticationProvider详解
3.1 AuthenticationProvider多个实现类
如果项目中定义了多个AuthenticationProvider实现类,那登录时,怎么判断用哪个AuthenticationProvider实现类?我们可以通过源码来找到答案
1、管理系统登录
@Component
public class AdminAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(HttpSecurity http) {
//admin provider
AdminAuthenticationProvider provider = new AdminAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(provider);
}
}
2、App登录
@Component
public class MobileAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private MyUserDetailsService 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、管理系统登录实现类
@Setter
public class AdminAuthenticationProvider implements AuthenticationProvider {
private QmUserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
String userName = (String) authenticationToken.getPrincipal();
String password = (String) authenticationToken.getCredentials();
UserDetails user = userDetailsService.loadAdminUser(userName);
if (user == null) {
throw new Exception(ResultCode.JWT_USER_INVALID);
}
if(userDetailsService.checkBlock(userName)) {
throw new Exception(ResultCode.JWT_USER_BLOCK);
}
if (!passwordEncoder.matches(password, user.getPassword())) {
userDetailsService.inc(userName);
throw new Exception(ResultCode.JWT_USER_INVALID);
}
UsernamePasswordAuthenticationToken authenticationResult = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
// 重点看这里
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
4、App短信验证码登录实现类
@Setter
public class MobileSmsAuthenticationProvider implements AuthenticationProvider {
private QmUserDetailsService userDetailsService;
private SmsVcodeRdsHelper smsVcodeRdsHelper;
@Override
public Authentication authenticate(Authentication authentication) {
MobileSmsAuthenticationToken authenticationToken = (MobileSmsAuthenticationToken) authentication;
Map<String, String> parameters = (Map<String, String>) authenticationToken.getDetails();
String mobile = (String) authenticationToken.getPrincipal();
String code = (String) authenticationToken.getCredentials();
String globalCode = parameters.get("globalCode");
if (!smsVcodeRdsHelper.vcodeVerify(SmsBusiTypeEnum.LOGIN_CODE, mobile, code)) {
throw new Exception(ResultCode.ERROR_VCODE, "手机号或验证码错误!");
}
SecurityUser user = userDetailsService.loadUserByMobile(mobile);
if (user == null) {
user = userDetailsService.createUserByMobile(globalCode, mobile);
}
if (null != parameters.get("platform")) {
user.setPlatform(parameters.get("platform"));
}
MobileSmsAuthenticationToken authenticationResult = new MobileSmsAuthenticationToken(user, code, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
// 重点看这里
@Override
public boolean supports(Class<?> authentication) {
return MobileSmsAuthenticationToken.class.isAssignableFrom(authentication);
}
}
3.2 AuthenticationProvider源码
首先进入到AuthenticationProvider源码中可以看到它只是个简单的接口里面也只有两个方法:
public interface AuthenticationProvider {
// 具体认证流程
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
// supports函数用来指明该Provider是否适用于该类型的认证,如果不合适,则寻找另一个Provider进行验证处理。
boolean supports(Class<?> authentication);
}
3.3 ProviderManager源码
ProviderManager提供了一个list对AuthenticationProvider进行统一管理,即一个认证处理器链来支持同一个应用中的多个不同身份认证机制,ProviderManager将会根据顺序来进行验证。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator();
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
//这里调用的就是AuthenticationProvider的方法supports(),如果项目中定义了多个AuthenticationProvider,则是通过这里判断来取哪一个AuthenticationProvider实现类
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
}
//省略代码
}
四、生成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实现类
import com.common.oauth2.constants.SecurityConstants;
import com.common.oauth2.domain.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.*;
/**
* 优化自Spring Security的RedisTokenStore
* 1. 支持redis所有集群模式包括cluster模式
* 2. 使用pipeline减少连接次数,提升性能
* 3. 自动续签token
*/
@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;
/**
* 业务redis的value序列化
*/
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;
}
/**
* 判断应用自动续签是否满足白名单和黑名单的过滤逻辑 后期看需求是否需要
* @param clientId 应用id
* @return 是否满足
*/
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();
}
}
/**
* 获取token的总有效时长
* @param clientId 应用id
*/
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;
}
/**
* 存储token
* @param isRenew 是否续签
*/
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);
}
}
网友评论