Spring Security OAuth2之scopes配置详解
官方文档说明
地址:https://projects.spring.io/spring-security-oauth/docs/oauth2.html
scope: The scope to which the client is limited. If scope is undefined or empty (the default) the client is not limited by scope.
- 用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围
- scope中文翻译就是作用域,用来限制客户端权限访问的范围,可以用来设置角色或者权限,也可以不设置
虽然官方网站说是服务器端的client配置scopes可以为空,但是经过实际操作及跟踪源码来看password模式下调用
/oauth/token
端点拿用户token信息服务端可以为空,
但是客户端必须传scopes
;refresh_token模式服务端及client端的scopes都需要配置,所以即使我们用不到scopes前后端最好都配置上scopes("all");
资料信息:https://stackoverflow.com/questions/39756748/spring-oauth-authorization-server-requires-scope
scopes的校验是在TokenEndpoint进行的
@RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
//OAuth2RequestFactory接口的实现类DefaultOAuth2RequestFactory创建token请求对象
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
} else {
//校验scope客户端和服务器端的设置是否匹配
if (authenticatedClient != null) {
this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
} else if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
} else {
if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
this.logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.emptySet());
}
if (this.isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
}
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
}
}
}
}
进入this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
查看处理逻辑
oAuth2RequestValidator
对象是DefaultOAuth2RequestValidator
的实例,进入看下实现逻辑:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.oauth2.provider.request;
import java.util.Iterator;
import java.util.Set;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2RequestValidator;
import org.springframework.security.oauth2.provider.TokenRequest;
public class DefaultOAuth2RequestValidator implements OAuth2RequestValidator {
public DefaultOAuth2RequestValidator() {
}
public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client) throws InvalidScopeException {
this.validateScope(authorizationRequest.getScope(), client.getScope());
}
//校验客户端scope和服务端scope方法
public void validateScope(TokenRequest tokenRequest, ClientDetails client) throws InvalidScopeException {
this.validateScope(tokenRequest.getScope(), client.getScope());
}
//实际的校验方法
private void validateScope(Set<String> requestScopes, Set<String> clientScopes) {
//客户端scope不为空并且scope在服务端scope限制范围之内通过校验
//客户端scope不为空,服务端为空或不设置通过校验
if (clientScopes != null && !clientScopes.isEmpty()) {
Iterator var3 = requestScopes.iterator();
while(var3.hasNext()) {
String scope = (String)var3.next();
if (!clientScopes.contains(scope)) {
throw new InvalidScopeException("Invalid scope: " + scope, clientScopes);
}
}
}
//如果客户端的scope为空将会抛出异常,所以客户端不可以为空
if (requestScopes.isEmpty()) {
throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)");
}
}
}
DefaultOAuth2RequestFactory实现类组装token请求及校验scopes
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.oauth2.provider.request;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.DefaultSecurityContextAccessor;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.SecurityContextAccessor;
import org.springframework.security.oauth2.provider.TokenRequest;
public class DefaultOAuth2RequestFactory implements OAuth2RequestFactory {
private final ClientDetailsService clientDetailsService;
private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor();
private boolean checkUserScopes = false;
public DefaultOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) {
this.securityContextAccessor = securityContextAccessor;
}
public void setCheckUserScopes(boolean checkUserScopes) {
this.checkUserScopes = checkUserScopes;
}
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
String clientId = (String)authorizationParameters.get("client_id");
String state = (String)authorizationParameters.get("state");
String redirectUri = (String)authorizationParameters.get("redirect_uri");
Set<String> responseTypes = OAuth2Utils.parseParameterList((String)authorizationParameters.get("response_type"));
Set<String> scopes = this.extractScopes(authorizationParameters, clientId);
AuthorizationRequest request = new AuthorizationRequest(authorizationParameters, Collections.emptyMap(), clientId, scopes, (Set)null, (Collection)null, false, state, redirectUri, responseTypes);
ClientDetails clientDetails = this.clientDetailsService.loadClientByClientId(clientId);
request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails);
return request;
}
public OAuth2Request createOAuth2Request(AuthorizationRequest request) {
return request.createOAuth2Request();
}
//创建请求入口类
public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
String clientId = (String)requestParameters.get("client_id");
if (clientId == null) {
clientId = authenticatedClient.getClientId();
} else if (!clientId.equals(authenticatedClient.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
}
String grantType = (String)requestParameters.get("grant_type");
//获取客户端传递或者服务端的scope
Set<String> scopes = this.extractScopes(requestParameters, clientId);
TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
return tokenRequest;
}
public TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType) {
TokenRequest tokenRequest = new TokenRequest(authorizationRequest.getRequestParameters(), authorizationRequest.getClientId(), authorizationRequest.getScope(), grantType);
return tokenRequest;
}
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest) {
return tokenRequest.createOAuth2Request(client);
}
//如果参数中的scope为null,则从服务端配置的scope中取,并且根据this.checkUserScopes的值判断是否校验scopes的有效性
private Set<String> extractScopes(Map<String, String> requestParameters, String clientId) {
Set<String> scopes = OAuth2Utils.parseParameterList((String)requestParameters.get("scope"));
ClientDetails clientDetails = this.clientDetailsService.loadClientByClientId(clientId);
if (scopes == null || scopes.isEmpty()) {
scopes = clientDetails.getScope();
}
if (this.checkUserScopes) {
scopes = this.checkUserScopes(scopes, clientDetails);
}
return scopes;
}
private Set<String> checkUserScopes(Set<String> scopes, ClientDetails clientDetails) {
if (!this.securityContextAccessor.isUser()) {
return scopes;
} else {
Set<String> result = new LinkedHashSet();
Set<String> authorities = AuthorityUtils.authorityListToSet(this.securityContextAccessor.getAuthorities());
Iterator var5 = scopes.iterator();
while(true) {
String scope;
do {
if (!var5.hasNext()) {
return result;
}
scope = (String)var5.next();
} while(!authorities.contains(scope) && !authorities.contains(scope.toUpperCase()) && !authorities.contains("ROLE_" + scope.toUpperCase()));
result.add(scope);
}
}
}
}
本文转载参考 原文 并加以调试
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/20/detailed-explanation-of-scope-configuration-for-spring-security-oauth2/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论