替换默认的客户端授权处理类,以方便获取客户端的 scope

This commit is contained in:
b2baccline
2021-09-15 22:01:08 +08:00
parent 235adeefe4
commit 9872a38171
6 changed files with 157 additions and 23 deletions

View File

@@ -1,6 +1,7 @@
package com.hccake.ballcat.auth; package com.hccake.ballcat.auth;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import com.hccake.ballcat.common.security.userdetails.ClientPrincipal;
import com.hccake.ballcat.common.security.userdetails.User; import com.hccake.ballcat.common.security.userdetails.User;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@@ -31,6 +32,14 @@ public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
response.put("scope", CollectionUtil.join(scopes, " ")); response.put("scope", CollectionUtil.join(scopes, " "));
} }
// 是否是客户端
boolean isClient = authentication.getPrincipal().getClass().isAssignableFrom(ClientPrincipal.class);
response.put("is_client", isClient);
if (isClient) {
return response;
}
// TODO 使用 Scope 进行校验
// 默认的 CustomTokenEnhancer 在登录获取 token 时只在 attribute 中存放了 ROLE 和 PERMISSION // 默认的 CustomTokenEnhancer 在登录获取 token 时只在 attribute 中存放了 ROLE 和 PERMISSION
// 如果是自己系统内部认可的远程 资源服务器,在拥有权限的情况下,把所有的属性都返回回去 // 如果是自己系统内部认可的远程 资源服务器,在拥有权限的情况下,把所有的属性都返回回去
// 因为实际业务中,可能会在 attributes 中存放一些敏感信息,比如数据权限相关属性 // 因为实际业务中,可能会在 attributes 中存放一些敏感信息,比如数据权限相关属性

View File

@@ -0,0 +1,42 @@
package com.hccake.ballcat.auth.authentication;
import com.hccake.ballcat.common.security.userdetails.ClientPrincipal;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.HashMap;
/**
* client_credentials 客户端凭证模式的授权处理器
*
* @author hccake
*/
public class CustomClientCredentialsTokenGranter extends ClientCredentialsTokenGranter {
public CustomClientCredentialsTokenGranter(AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
super(tokenServices, clientDetailsService, requestFactory);
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request oAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
ClientPrincipal clientPrincipal = new ClientPrincipal(oAuth2Request.getClientId(), new HashMap<>(8),
client.getAuthorities());
clientPrincipal.setScope(client.getScope());
OAuth2ClientAuthenticationToken userAuthentication = new OAuth2ClientAuthenticationToken(clientPrincipal, null);
return new OAuth2Authentication(oAuth2Request, userAuthentication);
}
}

View File

@@ -0,0 +1,42 @@
package com.hccake.ballcat.auth.authentication;
import com.hccake.ballcat.common.security.userdetails.ClientPrincipal;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import java.util.Collections;
/**
* An {@link Authentication} implementation used for OAuth 2.0 Client Authentication.
*
* @author hccake
* @see AbstractAuthenticationToken
*/
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
private final ClientPrincipal clientPrincipal;
private final Object credentials;
public OAuth2ClientAuthenticationToken(ClientPrincipal clientPrincipal, @Nullable Object credentials) {
super(Collections.emptyList());
this.clientPrincipal = clientPrincipal;
this.credentials = credentials;
setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.clientPrincipal;
}
@Nullable
@Override
public Object getCredentials() {
return this.credentials;
}
}

View File

@@ -1,6 +1,6 @@
package com.hccake.ballcat.auth.configurer; package com.hccake.ballcat.auth.configurer;
import com.hccake.ballcat.auth.mobile.MobileTokenGranter; import com.hccake.ballcat.auth.authentication.CustomClientCredentialsTokenGranter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@@ -14,17 +14,24 @@ import org.springframework.security.oauth2.config.annotation.configurers.ClientD
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter; import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter; import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter; import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -94,18 +101,32 @@ public class CustomAuthorizationServerConfigurer implements AuthorizationServerC
// 自定义的认证时异常转换 // 自定义的认证时异常转换
.exceptionTranslator(webResponseExceptionTranslator) .exceptionTranslator(webResponseExceptionTranslator)
// 自定义tokenGranter // 自定义tokenGranter
.tokenGranter(tokenGranter(endpoints)); .tokenGranter(tokenGranter(endpoints))
// 使用自定义的 TokenConverter方便在 checkToken 时,返回更多的信息
.accessTokenConverter(accessTokenConverter);
// @formatter:on // @formatter:on
} }
private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) { private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) {
// 使用自定义的 TokenConverter方便在 checkToken 时,返回更多的信息 // OAuth2 规范的四大授权类型
endpoints.accessTokenConverter(accessTokenConverter); ClientDetailsService clientDetailsService = endpoints.getClientDetailsService();
// 获取默认的granter集合 AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter())); AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
granters.add(new MobileTokenGranter(authenticationManager, endpoints.getTokenServices(), OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(granters); List<TokenGranter> tokenGranters = new ArrayList<>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices,
clientDetailsService, requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new CustomClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetailsService, requestFactory));
}
return new CompositeTokenGranter(tokenGranters);
} }
/** /**

View File

@@ -19,22 +19,31 @@ import cn.hutool.core.collection.CollectionUtil;
import com.hccake.ballcat.common.security.constant.TokenAttributeNameConstants; import com.hccake.ballcat.common.security.constant.TokenAttributeNameConstants;
import com.hccake.ballcat.common.security.userdetails.ClientPrincipal; import com.hccake.ballcat.common.security.userdetails.ClientPrincipal;
import com.hccake.ballcat.common.security.userdetails.User; import com.hccake.ballcat.common.security.userdetails.User;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.TokenIntrospectionResponse; import com.nimbusds.oauth2.sdk.TokenIntrospectionResponse;
import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse; import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse;
import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.id.Audience; import com.nimbusds.oauth2.sdk.id.Audience;
import net.minidev.json.JSONArray; import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject; import net.minidev.json.JSONObject;
import org.apache.commons.logging.Log; import org.slf4j.Logger;
import org.apache.commons.logging.LogFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.http.*; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthenticationInterceptor; import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.*; import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@@ -44,7 +53,12 @@ import org.springframework.web.client.RestTemplate;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* *
@@ -62,7 +76,7 @@ import java.util.*;
*/ */
public class RemoteOpaqueTokenIntrospector implements OpaqueTokenIntrospector { public class RemoteOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final Log logger = LogFactory.getLog(getClass()); private final Logger logger = LoggerFactory.getLogger(getClass());
private Converter<String, RequestEntity<?>> requestEntityConverter; private Converter<String, RequestEntity<?>> requestEntityConverter;
@@ -198,6 +212,9 @@ public class RemoteOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
} }
claims.put(OAuth2IntrospectionClaimNames.AUDIENCE, Collections.unmodifiableList(audiences)); claims.put(OAuth2IntrospectionClaimNames.AUDIENCE, Collections.unmodifiableList(audiences));
} }
if (response.getClientID() != null) {
claims.put(OAuth2IntrospectionClaimNames.CLIENT_ID, response.getClientID().getValue());
}
if (response.getExpirationTime() != null) { if (response.getExpirationTime() != null) {
Instant exp = response.getExpirationTime().toInstant(); Instant exp = response.getExpirationTime().toInstant();
claims.put(OAuth2IntrospectionClaimNames.EXPIRES_AT, exp); claims.put(OAuth2IntrospectionClaimNames.EXPIRES_AT, exp);
@@ -218,14 +235,16 @@ public class RemoteOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
claims.put(OAuth2IntrospectionClaimNames.SCOPE, scopes); claims.put(OAuth2IntrospectionClaimNames.SCOPE, scopes);
} }
boolean isClient = response.getClientID() != null; boolean isClient;
if (isClient) { try {
claims.put(OAuth2IntrospectionClaimNames.CLIENT_ID, response.getClientID().getValue()); isClient = response.getBooleanParameter("is_client");
return buildClient(claims);
} }
else { catch (ParseException e) {
return buildUser(response.toJSONObject(), claims); logger.warn("自定端点返回的 is_client 属性解析异常: {}, 请求信息:[{}]", e.getMessage(), response.toJSONObject());
isClient = false;
} }
return isClient ? buildClient(claims) : buildUser(response.toJSONObject(), claims);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@@ -4,6 +4,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import java.io.Serializable;
import java.util.*; import java.util.*;
/** /**
@@ -11,7 +12,7 @@ import java.util.*;
* *
* @author hccake * @author hccake
*/ */
public class ClientPrincipal implements OAuth2AuthenticatedPrincipal { public class ClientPrincipal implements OAuth2AuthenticatedPrincipal, Serializable {
private final String clientId; private final String clientId;