第一部分:關於授權類型 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
驗證碼模式其實就是用戶名密碼模式,無非在校驗驗證碼的時候先檢查驗證碼是否正確
所有我們直接復制
ResourceOwnerPasswordTokenGranter
,在getOAuth2Authentication
中增加驗證碼相關邏輯
實現圖片驗證碼模式
- 新建一個
CaptchaTokenGranter
繼承AbstractTokenGranter
申明GRANT_TYPE為驗證碼類型*captcha*
,復制上訴密碼模式的TokenGranter - 其中有兩個構造函數,第一個是在配置CaptchaTokenGranter時調用,寧外一個是自己內部調用
package com.Lonni.oauth.granter;
import com.Lonni.common.constant.AuthConstant;
import com.Lonni.common.utils.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
/**
* 圖片驗證碼類型 tokenGranter
*/
public class CaptchaTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE="captcha";
private final AuthenticationManager authenticationManager;
private RedisTemplate redisTemplate;
public CaptchaTokenGranter(AuthenticationManager authenticationManager,
RedisTemplate redisTemplate,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.redisTemplate=redisTemplate;
}
protected CaptchaTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory,
String grantType) {
//調用父類 接管GRANT_TYPE類型
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 username = parameters.get("username");
String password = parameters.get("password");
String captcha=parameters.get("captcha");
if (StringUtils.isEmpty(captcha)){
throw new InvalidGrantException("驗證碼不能為空!");
}
//正式環境放行
// String key= AuthConstant.AUTH_CATCHA_CACHE_KEY+captcha;
// Object o = redisTemplate.opsForValue().get(key);
// if (o==null){
// throw new InvalidGrantException("驗證碼不存在!");
// }
// if (!o.toString().equals(captcha)){
// throw new InvalidGrantException("驗證碼錯誤!");
// }
// Protect from downstream leaks of password
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((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 user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
建立TokenGranter擴展類,將自定義的TokenGranter加入到系統默認的列表中
package com.Lonni.oauth.granter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* TokenGranter 擴展 將自定義的grant_type類型添加到oauth2中
* 使用方法:
* 在configure(AuthorizationServerEndpointsConfigurer endpoints)中:
* //獲取自定義tokenGranter
* TokenGranter tokenGranter = TokenGranterExt.getTokenGranter(authenticationManager, endpoints, baseRedis, userClient, socialProperties);
* endpoints.tokenGranter(tokenGranter);
*
*/
public class TokenGranterExt {
public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager,
final AuthorizationServerEndpointsConfigurer endpointsConfigurer,
RedisTemplate redisTemplate
) {
// 默認tokenGranter集合 security 自帶的
List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpointsConfigurer.getTokenGranter()));
//添加驗證碼
granters.add(new CaptchaTokenGranter(authenticationManager, redisTemplate, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));
return new CompositeTokenGranter(granters);
}
}
在授權服務配置中加入所有的TokenGranter
在 AuthorizationServerConfigurerAdapter 實現類的
configure*(*AuthorizationServerEndpointsConfigurer endpoints*)*
方法增加
//獲取grant_type類型組合
TokenGranter tokenGranter = TokenGranterExt.getTokenGranter(authenticationManager, endpoints, redisTemplate);
endpoints
//設置密碼模式認證器
.authenticationManager(authenticationManager)
//設置授權碼模式認證器
.authorizationCodeServices(this.authorizationCodeServices())
//.authorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource))
//設置令牌策略
.tokenServices(tokenServices())
//設置查詢用戶的userDetilService
.userDetailsService(userDetailsService)
//設置grant_type類型集合
.tokenGranter(tokenGranter)
//允許post get提交認證
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);