修复授权码模式
This commit is contained in:
parent
4b68d1bfeb
commit
46a1b2094d
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
15
server/zyjblogs-oauth/src/main/resources/banner.txt
Normal file
15
server/zyjblogs-oauth/src/main/resources/banner.txt
Normal 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.
|
||||
\/ \/
|
@ -104,6 +104,7 @@ security:
|
||||
client:
|
||||
client-id: ${spring.application.name}
|
||||
client-secret: secret
|
||||
scope: user
|
||||
resource:
|
||||
id: ${spring.application.name}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user