修复授权码模式

This commit is contained in:
zhuyijun 2022-10-16 15:55:49 +08:00
parent 4b68d1bfeb
commit 46a1b2094d
8 changed files with 189 additions and 55 deletions

View File

@ -40,7 +40,6 @@ public class AuthorizationServerConfiguration extends AuthorizationServerConfigu
private final DataSource dataSource;
private final JwtTokenEnhancer jwtTokenEnhancer;
private final OauthResponseExceptionTranslator oAuthResponseExceptionTranslator;
private final ClientDetailsService oauthClientDetailsService;
private final RedisTemplateHandler redisTemplateHandler;
/**

View File

@ -60,9 +60,11 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
.antMatchers(String.join(",", whiteListProperties.getAllowPaths()))
.permitAll()
.antMatchers("/v*/user/login", "/v*/auth/refresh/token", "/v*/auth/authorize/code").permitAll()
.antMatchers("/v*/**").access("#oauth2.hasAnyScope('oauth','all')")
//以下请求必须认证通过
.anyRequest()
.authenticated().and()
.httpBasic();
}

View File

@ -10,12 +10,6 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
@ -25,12 +19,7 @@ import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* @author zhuyijun
@ -46,7 +35,7 @@ import java.util.Set;
ignoreUnknown = true
)
@TableName("oauth_client_details")
public class OauthClientDetail implements ClientDetails , Serializable {
public class OauthClientDetail implements ClientDetails, Serializable {
@TableId("client_id")
private String clientId;
@ -121,18 +110,18 @@ public class OauthClientDetail implements ClientDetails , Serializable {
public OauthClientDetail() {
}
public OauthClientDetail(ClientDetails prototype){
public OauthClientDetail(ClientDetails prototype) {
this();
this.setAccessTokenValiditySeconds(prototype.getAccessTokenValiditySeconds());
this.setRefreshTokenValiditySeconds(prototype.getRefreshTokenValiditySeconds());
Collection<GrantedAuthority> authorities = prototype.getAuthorities();
this.setAuthorities(JSON.toJSONString(authorities));
this.setAuthorizedGrantTypes(String.join(",",prototype.getAuthorizedGrantTypes()));
this.setAuthorizedGrantTypes(String.join(",", prototype.getAuthorizedGrantTypes()));
this.setClientId(prototype.getClientId());
this.setClientSecret(prototype.getClientSecret());
this.setWebServerRedirectUri(String.join(",",prototype.getRegisteredRedirectUri()));
this.setScope(String.join(",",prototype.getScope()));
this.setResourceIds(String.join(",",prototype.getResourceIds()));
this.setWebServerRedirectUri(String.join(",", prototype.getRegisteredRedirectUri()));
this.setScope(String.join(",", prototype.getScope()));
this.setResourceIds(String.join(",", prototype.getResourceIds()));
}
@Override
@ -215,7 +204,7 @@ public class OauthClientDetail implements ClientDetails , Serializable {
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
public Set<String> getAuthorizedGrantTypes() {
if (authorizedGrantTypes == null){
if (authorizedGrantTypes == null) {
return new HashSet<>(0);
}
return Set.of(authorizedGrantTypes.split(","));
@ -225,7 +214,7 @@ public class OauthClientDetail implements ClientDetails , Serializable {
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
public Set<String> getRegisteredRedirectUri() {
if (webServerRedirectUri == null){
if (webServerRedirectUri == null) {
return new HashSet<>(0);
}
return Set.of(webServerRedirectUri.split(","));
@ -236,7 +225,7 @@ public class OauthClientDetail implements ClientDetails , Serializable {
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
public Collection<GrantedAuthority> getAuthorities() {
if (StringUtils.isEmpty(authorities)){
if (StringUtils.isEmpty(authorities)) {
return Collections.emptyList();
}
return JSON.parseArray(authorities, GrantedAuthority.class);
@ -259,15 +248,23 @@ public class OauthClientDetail implements ClientDetails , Serializable {
@Override
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
public boolean isAutoApprove(String s) {
return Boolean.getBoolean(autoapprove);
public boolean isAutoApprove(String scope) {
if (scope == null) {
return false;
}
for (String auto : getScope()) {
if (auto.equals("true") || scope.matches(auto)) {
return true;
}
}
return false;
}
@Override
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
public Map<String, Object> getAdditionalInformation() {
if (additionalInformation == null){
if (additionalInformation == null) {
return new HashMap<>(0);
}
return BeanUtils.beanToMap(additionalInformation);

View File

@ -0,0 +1,119 @@
package cn.zyjblogs.server.user.handler;
import cn.zyjblogs.starter.common.entity.context.BaseContext;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.ClientRegistrationException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
@RequiredArgsConstructor
public class OauthRequestValidator {
private final TokenStore tokenStore;
private final JwtAccessTokenConverter jwtAccessTokenConverter;
public void validateScope(Set<String> requestScopes, Set<String> clientScopes) {
if (clientScopes != null && !clientScopes.isEmpty()) {
for (String scope : requestScopes) {
if (!clientScopes.contains(scope)) {
throw new InvalidScopeException("Invalid scope: " + scope, clientScopes);
}
}
}
if (requestScopes.isEmpty()) {
throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)");
}
}
public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, ClientDetails client) {
String clientId = authorizationRequest.getClientId();
Collection<String> requestedScopes = authorizationRequest.getScope();
Set<String> approvedScopes = new HashSet<String>();
Set<String> validUserApprovedScopes = new HashSet<String>();
Set<Approval> approvals = new HashSet<Approval>();
if (client != null) {
try {
for (String scope : requestedScopes) {
if (client.isAutoApprove(scope)) {
approvedScopes.add(scope);
}
}
if (approvedScopes.containsAll(requestedScopes)) {
// gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store.
Date expiry = computeExpiry(-1);
for (String approvedScope : approvedScopes) {
approvals.add(new Approval(BaseContext.getUsername(), authorizationRequest.getClientId(),
approvedScope, expiry, Approval.ApprovalStatus.APPROVED));
}
authorizationRequest.setApproved(true);
return authorizationRequest;
}
} catch (ClientRegistrationException e) {
}
}
// Find the stored approvals for that user and client
Collection<Approval> userApprovals = getApprovals(clientId);
// Look at the scopes and see if they have expired
Date today = new Date();
for (Approval approval : userApprovals) {
if (approval.getExpiresAt().after(today)) {
if (approval.getStatus() == Approval.ApprovalStatus.APPROVED) {
validUserApprovedScopes.add(approval.getScope());
approvedScopes.add(approval.getScope());
}
}
}
// If the requested scopes have already been acted upon by the user,
// this request is approved
if (validUserApprovedScopes.containsAll(requestedScopes)) {
approvedScopes.retainAll(requestedScopes);
// Set only the scopes that have been approved by the user
authorizationRequest.setScope(approvedScopes);
authorizationRequest.setApproved(true);
}
return authorizationRequest;
}
private Date computeExpiry(int approvalExpirySeconds) {
Calendar expiresAt = Calendar.getInstance();
if (approvalExpirySeconds == -1) { // use default of 1 month
expiresAt.add(Calendar.MONTH, 1);
} else {
expiresAt.add(Calendar.SECOND, approvalExpirySeconds);
}
return expiresAt.getTime();
}
private Collection<Approval> getApprovals(String clientId) {
Collection<Approval> result = new HashSet<Approval>();
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(BaseContext.getToken());
OAuth2Authentication authentication = tokenStore.readAuthentication(BaseContext.getToken());
if (authentication != null) {
Date expiresAt = oAuth2AccessToken.getExpiration();
for (String scope : oAuth2AccessToken.getScope()) {
result.add(new Approval(BaseContext.getUsername(), clientId, scope, expiresAt, Approval.ApprovalStatus.APPROVED));
}
}
return result;
}
}

View File

@ -4,6 +4,7 @@ import cn.zyjblogs.server.user.dto.AuthCodeDto;
import cn.zyjblogs.server.user.dto.AuthorizationCodeDto;
import cn.zyjblogs.server.user.dto.AuthorizationDto;
import cn.zyjblogs.server.user.dto.OAuth2AccessTokenDto;
import cn.zyjblogs.server.user.handler.OauthRequestValidator;
import cn.zyjblogs.server.user.handler.OauthRquestHander;
import cn.zyjblogs.server.user.po.OauthUserDetails;
import cn.zyjblogs.server.user.service.AuthService;
@ -21,25 +22,17 @@ import org.springframework.security.oauth2.common.exceptions.UnsupportedResponse
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver;
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* @author zhuyijun
@ -51,16 +44,20 @@ public class AuthServiceImpl implements AuthService {
private final OAuth2RequestFactory oAuth2RequestFactory;
private final AuthorizationCodeServices authorizationCodeServices;
private final PasswordEncoder passwordEncoder;
private RedirectResolver redirectResolver;
private OauthRequestValidator oauthRequestValidator;
private final Object implicitLock = new Object();
public AuthServiceImpl(AuthorizationServerEndpointsConfiguration authorizationServerEndpointsConfiguration,
ClientDetailsService clientDetails,
AuthorizationCodeServices authorizationCodeServices, PasswordEncoder passwordEncoder) {
AuthorizationCodeServices authorizationCodeServices, PasswordEncoder passwordEncoder, OauthRequestValidator oauthRequestValidator) {
this.tokenGranter = authorizationServerEndpointsConfiguration.getEndpointsConfigurer().getTokenGranter();
this.clientDetails = clientDetails;
this.redirectResolver = new DefaultRedirectResolver();
this.oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetails);
this.authorizationCodeServices = authorizationCodeServices;
this.passwordEncoder = passwordEncoder;
this.oauthRequestValidator = oauthRequestValidator;
}
@Value("${security.oauth2.client.client-id}")
@ -116,25 +113,25 @@ public class AuthServiceImpl implements AuthService {
return OauthRquestHander.getUnsuccessfulRedirect(redirect_url, new OAuth2Exception("无此客户端"), false);
}
if (!passwordEncoder.matches(authorizationCodeDto.getClient_secret(), clientDetail.getClientSecret())) {
return OauthRquestHander.getUnsuccessfulRedirect(redirect_url, new OAuth2Exception("客户端" + authorizationCodeDto.getClient_id() + "认证失败"), false);
throw new AuthRuntimeException(HttpCode.BAD_REQUEST, "该客户端认证失败");
}
String resolvedRedirect = redirectResolver.resolveRedirect(authorizationCodeDto.getRedirect_url(), clientDetail);
String scope = authorizationCodeDto.getScope();
Set<String> scopes = StringUtils.hasLength(scope) ? Set.of(scope.split(",")) : new HashSet<>();
oauthRequestValidator.validateScope(scopes, clientDetail.getScope());
Map<String, String> parameters = new HashMap<>(16);
parameters.put(OAuth2Utils.CLIENT_ID, authorizationCodeDto.getClient_id());
parameters.put("client_secret", authorizationCodeDto.getClient_secret());
parameters.put(OAuth2Utils.REDIRECT_URI, redirect_url);
parameters.put(OAuth2Utils.REDIRECT_URI, resolvedRedirect);
parameters.put(OAuth2Utils.RESPONSE_TYPE, "code");
parameters.put(OAuth2Utils.SCOPE, authorizationCodeDto.getScope());
parameters.put(OAuth2Utils.STATE, authorizationCodeDto.getState());
parameters.put(OAuth2Utils.USER_OAUTH_APPROVAL, authorizationCodeDto.getUser_oauth_approval());
AuthorizationRequest authorizationRequest = createAuthorizationRequest(parameters, clientDetail);
Set<String> registeredRedirectUri = clientDetail.getRegisteredRedirectUri();
if (!redirect_url.equals(registeredRedirectUri.toArray()[0].toString())) {
return OauthRquestHander.getUnsuccessfulRedirect(authorizationRequest,
new UserDeniedAuthorizationException("回调地址不一致"), false);
}
oauthRequestValidator.checkForPreApproval(authorizationRequest, clientDetail);
String flag = parameters.get(OAuth2Utils.USER_OAUTH_APPROVAL);
boolean isApproved = "true".equalsIgnoreCase(flag);
authorizationRequest.setApproved(isApproved);
authorizationRequest.setApproved(authorizationRequest.isApproved() && isApproved);
if (!isApproved) {
return OauthRquestHander.getUnsuccessfulRedirect(authorizationRequest,
new UserDeniedAuthorizationException("User denied access"), false);
@ -142,7 +139,6 @@ public class AuthServiceImpl implements AuthService {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
try {
AuthorizationDto authorizationDto = new AuthorizationDto();
OauthUserDetails oauthUserDetails = new OauthUserDetails();
oauthUserDetails.setId(BaseContext.getUserId());
@ -151,6 +147,7 @@ public class AuthServiceImpl implements AuthService {
oauthUserDetails.setTenantId(BaseContext.getTenantId());
authorizationDto.setPrincipal(oauthUserDetails);
authorizationDto.setAuthenticated(true);
try {
return OauthRquestHander.getSuccessfulRedirect(authorizationRequest,
generateCode(authorizationRequest, authorizationDto));
} catch (OAuth2Exception e) {

View File

@ -0,0 +1,15 @@
//
\\ //
\\ //
##DDDDDDDDDDDDDDDDDDDDDD##
## DDDDDDDDDDDDDDDDDDDD ## ________ ___ ___ ___ ________ ___ ___ ___
## hh hh ## |\ __ \ |\ \ |\ \ |\ \ |\ __ \ |\ \ |\ \ |\ \
## hh // \\ hh ## \ \ \|\ /_\ \ \\ \ \ \ \ \\ \ \|\ /_\ \ \\ \ \ \ \ \
## hh // \\ hh ## \ \ __ \\ \ \\ \ \ \ \ \\ \ __ \\ \ \\ \ \ \ \ \
## hh hh ## \ \ \|\ \\ \ \\ \ \____ \ \ \\ \ \|\ \\ \ \\ \ \____ \ \ \
## hh wwww hh ## \ \_______\\ \__\\ \_______\\ \__\\ \_______\\ \__\\ \_______\\ \__\
## hh hh ## \|_______| \|__| \|_______| \|__| \|_______| \|__| \|_______| \|__|
## MMMMMMMMMMMMMMMMMMMM ##
##MMMMMMMMMMMMMMMMMMMMMM## Release 1.6.11. Powered by jinkela-core 2.8.9.
\/ \/

View File

@ -104,6 +104,7 @@ security:
client:
client-id: ${spring.application.name}
client-secret: secret
scope: user
resource:
id: ${spring.application.name}

View File

@ -26,6 +26,8 @@ import org.springframework.security.oauth2.provider.token.TokenStore;
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${security.oauth2.resource.id}")
private String resourceId;
@Value("${security.oauth2.client.scope}")
private String scope;
private final TokenStore tokenStore;
private final WhiteListProperties whiteListProperties;
@ -42,9 +44,11 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
String scopeRs = "#oauth2.hasAnyScope(" + '\'' + scope + '\'' + ",'all')";
http.csrf().disable()
.authorizeRequests()
.antMatchers("/webjars/**", "/swagger-ui.html/**", "/swagger-resources/**", "/v2/api-docs/**", String.join(",", whiteListProperties.getAllowPaths())).permitAll()
.antMatchers("/**").access(scopeRs)
.anyRequest()
.authenticated()
.and()