第一部分:關於授權類型 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()));