本文僅助那些在Security集成OAuth2.0路上踩坑的人理解認證過程。
OAuth2AuthenticationManager
首先,我覺得分析OAuth2AuthenticationManager,我們需要先理解它是如何被使用,何時被使用,那我們就從這里開始逐一分析吧,我手畫了一張圖作為索引,以便我們理解,其中藍色的為類,綠色的為接口,箭頭指向的方向是實現類或者父類。
因為里面接口和方法過多,所以不做展示,下面具體來進行代碼分析。
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
private ResourceServerTokenServices tokenServices;
private ClientDetailsService clientDetailsService;
private String resourceId;
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
public void afterPropertiesSet() {
Assert.state(tokenServices != null, "TokenServices are required");
}
(3) public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
(3).1 String token = (String) authentication.getPrincipal();
(3).2 OAuth2Authentication auth = tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}
(3).3 checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
(3).4 auth.setAuthenticated(true);
return auth;
}
private void checkClientDetails(OAuth2Authentication auth) {
if (clientDetailsService != null) {
ClientDetails client;
try {
client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());
}
catch (ClientRegistrationException e) {
throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");
}
Set<String> allowed = client.getScope();
for (String scope : auth.getOAuth2Request().getScope()) {
if (!allowed.contains(scope)) {
throw new OAuth2AccessDeniedException(
"Invalid token contains disallowed scope (" + scope + ") for this client");
}
}
}
}
}
OAuth2AuthenticationManager的成員變量
ResourceServerTokenServices
以上是它的關系圖,關於該接口,了解即可,不必過於深究,有興趣的小伙伴們可以去翻看源碼,學習一下代碼結構也是不錯的。
DefaultTokenServices 利用Security內置的令牌存儲器(Tokenstore)接口進行數據庫的CRUD操作,查看數據庫結構及表說明
ClientDetailsService
ClientDetailsService 用於加載客戶端,有兩種實現方式,一種是基於內存,一種是基於存儲庫的方式。
auth-server: http://localhost:18081/uac
server:
port: 18082
security:
oauth2:
client:
client-id: client1
client-secret: 201314
user-authorization-uri: ${auth-server}/oauth/authorize
access-token-uri: ${auth-server}/oauth/token
resource:
jwt:
key-uri: ${auth-server}/oauth/token_key
key-value: 201314
上面是關於客戶端的配置,auth-server 為資源服務器路徑。
authenticate()
- (3).1處的代碼,期望傳入的身份驗證請求具有一個主體值,該主體值是一個訪問令牌值(一般在(authorization header)請求頭中)
- (3).2處的代碼,ResourceServerTokenServices通過查詢數據庫中 oauth_client_details該表,加載身份驗證。
- (3).3處的代碼,檢查資源id是否包含在授權請求中。
- (3).4,通過身份認證,Security將OAuth2Authentication對象存入Session中,然后跳轉到AuthorizationEndpoint的authorize()。該方法跳出一個授權頁面,提供授權的通過權或否決權。
以上就是我對於OAuth2AuthenticationManager源碼的理解,僅供參考,如有不正確的地方,請提出指正。