第一部分:关于授权类型 grant_type
的解析
- 每种
grant_type
都会有一个对应的TokenGranter
实现类。 - 所有
TokenGranter
实现类都通过CompositeTokenGranter
中的tokenGranters
集合存起来。 - 然后通过判断
grantType
参数来定位具体使用那个TokenGranter
实现类来处理授权。
第二部分:关于授权登录逻辑
- 每种
授权方式
都会有一个对应的AuthenticationProvider
实现类来实现。 - 所有
AuthenticationProvider
实现类都通过ProviderManager
中的providers
集合存起来。 TokenGranter
类会 new 一个AuthenticationToken
实现类,如UsernamePasswordAuthenticationToken
传给ProviderManager
类。- 而
ProviderManager
则通过AuthenticationToken
来判断具体使用那个AuthenticationProvider
实现类来处理授权。 - 具体的登录逻辑由
AuthenticationProvider
实现类来实现,如DaoAuthenticationProvider
。
所有的授权类型都会继承
AbstractTokenGranter
自定义grant_type步骤
- 自定义token,继承自
**AbstractAuthenticationToken* *
,用于到登录的时候传入认证信息 - 自定义模式的提供者,继承自
*implements AuthenticationProvider, MessageSourceAware*
;作用是如果当前的token为该提供的类型,会调用authenticate方法进行登录操作 - 自定义TokenGranter,在
getOAuth2Authentication
方法中传入自定义的token进行验证
自定义token类型,直接复制 UsernamePasswordAuthenticationToken
package com.Lonni.oauth.token;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 继承自AbstractAuthenticationToken 用于自定义的token
*
*/
public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {
// ~ Instance fields
// ================================================================================================
/**
* 认证之前 存的是手机号
* 认证之后 存的是用户信息
*/
private final Object principal;
private Object credentials;
public MobileCodeAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
//设置没有认证
setAuthenticated(false);
}
public MobileCodeAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// must use super, as we override
//设置已经认证
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
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();
credentials = null;
}
}
自定义模式提供者
package com.Lonni.oauth.provider;
import com.Lonni.common.constant.AuthConstant;
import com.Lonni.core.Constants.AppConstans;
import com.Lonni.oauth.service.UserDetailsServiceImpl;
import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.apache.commons.lang.NullArgumentException;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
/**
* 自定义手机模式的提供者
* 判断token类型是否为MobileCodeAuthenticationToken,如果是则会使用此provider
*/
public class MobileCodeAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
@Override
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
/**
* userDetailsService的实现类 不需要自动注入 直接传入
*/
private UserDetailsServiceImpl userDetailsService;
private RedisTemplate redisTemplate;
/**
* 业务处理的方法
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//未认证之前是手机号 认证之后是客户信息
String principal =(String) authentication.getPrincipal();
if (StringUtils.isEmpty(principal)){
throw new BadCredentialsException("手机号不能为空");
}
// 这里的Credentials是先通过AbstractTokenGranter组装 new MobileCodeAuthenticationToken()传入的
String code = (String) authentication.getCredentials();
if (code == null) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "手机验证码不能为空"));
}
//正式环境放开注释
// String key=AuthConstant.AUTH_SMS_CACHE_KEY+principal+code;
// Object cacheCode = redisTemplate.opsForValue().get(key);
// if (cacheCode == null || !cacheCode.toString().equals(code)) {
// throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "验证码无效"));
// }
//清除redis中的短信验证码
//stringRedisTemplate.delete(RedisConstant.SMS_CODE_PREFIX + mobile);
UserDetails user;
try {
user = userDetailsService.loadUserByPhone(principal);
} catch (UsernameNotFoundException var6) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "查询用户信息失败!"));
}
check(user);
MobileCodeAuthenticationToken authenticationToken = new MobileCodeAuthenticationToken(user, code, user.getAuthorities());
authenticationToken.setDetails(authenticationToken.getDetails());
return authenticationToken;
}
/**
* 账号禁用、锁定、超时校验
*
* @param user
*/
private void check(UserDetails user) {
if (user==null){
throw new NullArgumentException(this.messages.getMessage("未查询到用户", "未查询到用户"));
}
if (!user.isAccountNonLocked()) {
throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
} else if (!user.isEnabled()) {
throw new DisabledException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
/**
* 判断是否为MobileCodeAuthenticationToken类型 如果是 直接调用 切断过滤器链
* 如果不是 继续查找
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return MobileCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setUserDetailsService(UserDetailsServiceImpl userDetailsService){
this.userDetailsService=userDetailsService;
}
public void setRedisTemplate( RedisTemplate redisTemplate){
this.redisTemplate=redisTemplate;
}
}
自定义 MobileCodeTokenGranter
package com.Lonni.oauth.granter;
import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
public class MobileCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "mobile";
private final AuthenticationManager authenticationManager;
public MobileCodeTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
private MobileCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String code = parameters.get("code");
//调用自定义的token扩展 实现用户认证
Authentication userAuth = new MobileCodeAuthenticationToken(mobile,code);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
将自定义的TokenGanter加入到security中
复制上一节的 TokenGranterExt扩展类 加入手机验证码模式
//添加手机验证码
//granters.add(new SmsCodeTokenGranter(authenticationManager, userDetailsService, redisTemplate, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));
granters.add(new MobileCodeTokenGranter(authenticationManager, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));