OAuth2生成token代码备忘
一、登录接口(用户名+密码)
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 pwdLogin(@RequestBody MemberLoginPwdVO vo, HttpServletRequest request) {
if (StringUtil.isEmpty(vo.getClientId())) {
vo.setClientId("app");
}
vo.setIp(IpUtils.ip(request));
Map params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_PWD.getCode());
params.put("mobile", vo.getMobile());
params.put("password", vo.getPassword());
List grantedAuthorities = new ArrayList<>();
Oauth2TokenDto oauth2TokenDto = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
return Result.success(oauth2TokenDto);
}
private Map getMemberBaseParam(MemberLoginBaseVO vo, String grantType) {
Map 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 grantedAuthorities, Map params) throws HttpRequestMethodNotSupportedException {
User principle = new User(clientId,clientSecurity,true,true,true,true,grantedAuthorities);
return getAccessToken(principle,params);
}
public Oauth2TokenDto getAccessToken(User principle, Map 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 parameters = (Map)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 {
@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/**").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 map);
}
@Slf4j
public class MyUserDetailServiceImpl implements MyUserDetailsService{
@Override
public SecurityUser loadAdminUser(String mobileOrUserName) {
SecurityUser securityUser = null;
Result 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 authorities = new HashSet<>();
//基于权限控制
Result> 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、MobileAuthenticationSecurityConfig
@Component
public class MobileAuthenticationSecurityConfig extends SecurityConfigurerAdapter {
@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);
}
}
2、管理系统登录实现类
@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);
}
}
3、手机号+密码登录实现类
@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 parameters = (Map)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);
}
}
4、MobilePasswordAuthenticationToken
package com.alanchen.ac;
public class MobilePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
public MobilePasswordAuthenticationToken(String mobile, String password) {
super(null);
this.principal = mobile;
this.credentials = password;
setAuthenticated(false);
}
public MobilePasswordAuthenticationToken(Object principal, Object credentials,
Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
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;
}
}
}
//省略代码
}
3.4 手机号+密码Granter
重点关注该类中的:
1、MobilePasswordAuthenticationToken
2、SecurityGrantType.APP_PWD.getCode(),和Controller.pwdLogin里的grant_type是同一个类型。
public class MobilePwdGranter extends AbstractTokenGranter {
private final AuthenticationManager authenticationManager;
public MobilePwdGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
super(tokenServices, clientDetailsService, requestFactory, SecurityGrantType.APP_PWD.getCode());
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
Authentication userAuth = new MobilePasswordAuthenticationToken(mobile, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
userAuth = authenticationManager.authenticate(userAuth);
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
3.5 TokenGranter配置
@Configuration
public class TokenGranterConfig {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore tokenStore;
@Autowired(required = false)
private List tokenEnhancer;
@Autowired
private RandomValueAuthorizationCodeServices authorizationCodeServices;
private boolean reuseRefreshToken = true;
private AuthorizationServerTokenServices tokenServices;
private TokenGranter tokenGranter;
/**
* 授权模式
*/
@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;
}
/**
* 所有授权模式:默认的5种模式 + 自定义的模式
*/
private List getAllTokenGranters() {
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
//获取默认的授权模式
List 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 getDefaultTokenGranters(AuthorizationServerTokenServices tokenServices
, AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) {
List 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
4.2 TokenStore实现类
/**
* 优化自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
五、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 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;
}
}
共有 0 条评论