OAuth 2.1 PKCE
This commit is contained in:
@@ -89,9 +89,21 @@ public class OAuth2Constants {
|
||||
public static final String CODE_CHALLENGE_METHOD = "code_challenge_method" ;
|
||||
|
||||
public static final String CODE_VERIFIER = "code_verifier" ;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class PKCE_TYPE{
|
||||
public static final String PKCE_TYPE_YES = "YES" ;
|
||||
public static final String PKCE_TYPE_NO = "NO" ;
|
||||
}
|
||||
|
||||
public static class CODE_CHALLENGE_METHOD_TYPE{
|
||||
public static final String PLAIN = "plain" ;
|
||||
public static final String S256 = "S256" ;
|
||||
}
|
||||
|
||||
public static class ENDPOINT{
|
||||
|
||||
public final static String ENDPOINT_BASE = "/authz/oauth/v20";
|
||||
|
||||
@@ -164,7 +164,7 @@ public class AuthorizationRequest extends BaseRequest implements Serializable {
|
||||
}
|
||||
|
||||
public OAuth2Request createOAuth2Request() {
|
||||
return new OAuth2Request(getRequestParameters(), getClientId(), getAuthorities(), isApproved(), getScope(), getResourceIds(), getRedirectUri(), getResponseTypes(), getExtensions());
|
||||
return new OAuth2Request(getRequestParameters(), getClientId(), getAuthorities(), isApproved(), getScope(), getResourceIds(), getRedirectUri(), getResponseTypes(), getCodeChallenge(),getCodeChallengeMethod(),getExtensions());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,6 +102,7 @@ public class OAuth2Request extends BaseRequest implements Serializable {
|
||||
public OAuth2Request(Map<String, String> requestParameters, String clientId,
|
||||
Collection<? extends GrantedAuthority> authorities, boolean approved, Set<String> scope,
|
||||
Set<String> resourceIds, String redirectUri, Set<String> responseTypes,
|
||||
String codeChallenge,String codeChallengeMethod,
|
||||
Map<String, Serializable> extensionProperties) {
|
||||
setClientId(clientId);
|
||||
setRequestParameters(requestParameters);
|
||||
@@ -117,6 +118,8 @@ public class OAuth2Request extends BaseRequest implements Serializable {
|
||||
if (responseTypes != null) {
|
||||
this.responseTypes = new HashSet<String>(responseTypes);
|
||||
}
|
||||
this.codeChallenge = codeChallenge;
|
||||
this.codeChallengeMethod = codeChallengeMethod;
|
||||
this.redirectUri = redirectUri;
|
||||
if (extensionProperties != null) {
|
||||
this.extensions = extensionProperties;
|
||||
@@ -125,8 +128,9 @@ public class OAuth2Request extends BaseRequest implements Serializable {
|
||||
|
||||
protected OAuth2Request(OAuth2Request other) {
|
||||
this(other.getRequestParameters(), other.getClientId(), other.getAuthorities(), other.isApproved(), other
|
||||
.getScope(), other.getResourceIds(), other.getRedirectUri(), other.getResponseTypes(), other
|
||||
.getExtensions());
|
||||
.getScope(), other.getResourceIds(), other.getRedirectUri(), other.getResponseTypes(),
|
||||
other.getCodeChallenge(),other.getCodeChallengeMethod(),
|
||||
other.getExtensions());
|
||||
}
|
||||
|
||||
protected OAuth2Request(String clientId) {
|
||||
@@ -177,7 +181,7 @@ public class OAuth2Request extends BaseRequest implements Serializable {
|
||||
*/
|
||||
public OAuth2Request createOAuth2Request(Map<String, String> parameters) {
|
||||
return new OAuth2Request(parameters, getClientId(), authorities, approved, getScope(), resourceIds,
|
||||
redirectUri, responseTypes, extensions);
|
||||
redirectUri, responseTypes, codeChallenge, codeChallengeMethod,extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,14 +193,14 @@ public class OAuth2Request extends BaseRequest implements Serializable {
|
||||
*/
|
||||
public OAuth2Request narrowScope(Set<String> scope) {
|
||||
OAuth2Request request = new OAuth2Request(getRequestParameters(), getClientId(), authorities, approved, scope,
|
||||
resourceIds, redirectUri, responseTypes, extensions);
|
||||
resourceIds, redirectUri, responseTypes, codeChallenge, codeChallengeMethod, extensions);
|
||||
request.refresh = this.refresh;
|
||||
return request;
|
||||
}
|
||||
|
||||
public OAuth2Request refresh(TokenRequest tokenRequest) {
|
||||
OAuth2Request request = new OAuth2Request(getRequestParameters(), getClientId(), authorities, approved,
|
||||
getScope(), resourceIds, redirectUri, responseTypes, extensions);
|
||||
getScope(), resourceIds, redirectUri, responseTypes, codeChallenge, codeChallengeMethod,extensions);
|
||||
request.refresh = tokenRequest;
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class TokenRequest extends BaseRequest {
|
||||
// Add grant type so it can be retrieved from OAuth2Request
|
||||
modifiable.put("grant_type", grantType);
|
||||
return new OAuth2Request(modifiable, client.getClientId(), client.getAuthorities(), true, this.getScope(),
|
||||
client.getResourceIds(), null, null, null);
|
||||
client.getResourceIds(), null, null, null, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,14 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.maxkey.authz.oauth2.common.OAuth2Constants;
|
||||
import org.maxkey.authz.oauth2.common.OAuth2Constants.CODE_CHALLENGE_METHOD_TYPE;
|
||||
import org.maxkey.authz.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.maxkey.authz.oauth2.common.exceptions.InvalidGrantException;
|
||||
import org.maxkey.authz.oauth2.common.exceptions.InvalidRequestException;
|
||||
import org.maxkey.authz.oauth2.common.exceptions.OAuth2Exception;
|
||||
import org.maxkey.authz.oauth2.common.exceptions.RedirectMismatchException;
|
||||
import org.maxkey.authz.oauth2.common.util.OAuth2Utils;
|
||||
import org.maxkey.authz.oauth2.provider.ClientDetailsService;
|
||||
import org.maxkey.authz.oauth2.provider.OAuth2Authentication;
|
||||
import org.maxkey.authz.oauth2.provider.OAuth2Request;
|
||||
@@ -32,6 +35,8 @@ import org.maxkey.authz.oauth2.provider.OAuth2RequestFactory;
|
||||
import org.maxkey.authz.oauth2.provider.TokenRequest;
|
||||
import org.maxkey.authz.oauth2.provider.token.AbstractTokenGranter;
|
||||
import org.maxkey.authz.oauth2.provider.token.AuthorizationServerTokenServices;
|
||||
import org.maxkey.constants.ConstantsProtocols;
|
||||
import org.maxkey.crypto.DigestUtils;
|
||||
import org.maxkey.entity.apps.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
@@ -57,13 +62,15 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
|
||||
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
|
||||
|
||||
Map<String, String> parameters = tokenRequest.getRequestParameters();
|
||||
String authorizationCode = parameters.get("code");
|
||||
String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);
|
||||
String authorizationCode = parameters.get(OAuth2Constants.PARAMETER.CODE);
|
||||
String redirectUri = parameters.get(OAuth2Constants.PARAMETER.REDIRECT_URI);
|
||||
String codeVerifier = parameters.get(OAuth2Constants.PARAMETER.CODE_VERIFIER);
|
||||
|
||||
if (authorizationCode == null) {
|
||||
throw new InvalidRequestException("An authorization code must be supplied.");
|
||||
}
|
||||
|
||||
//consume AuthorizationCode
|
||||
logger.trace("consume AuthorizationCode...");
|
||||
OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
|
||||
if (storedAuth == null) {
|
||||
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
|
||||
@@ -72,8 +79,9 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
|
||||
OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();
|
||||
// https://jira.springsource.org/browse/SECOAUTH-333
|
||||
// This might be null, if the authorization was done without the redirect_uri parameter
|
||||
String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get(
|
||||
OAuth2Utils.REDIRECT_URI);
|
||||
String redirectUriApprovalParameter =
|
||||
pendingOAuth2Request.getRequestParameters().get(
|
||||
OAuth2Constants.PARAMETER.REDIRECT_URI);
|
||||
|
||||
String pendingClientId = pendingOAuth2Request.getClientId();
|
||||
String clientId = tokenRequest.getClientId();
|
||||
@@ -85,7 +93,7 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
|
||||
*/
|
||||
Set<String> redirectUris = client.getRegisteredRedirectUri();
|
||||
boolean redirectMismatch=false;
|
||||
|
||||
//match the stored RedirectUri with request redirectUri parameter
|
||||
for(String storedRedirectUri : redirectUris){
|
||||
if(redirectUri.startsWith(storedRedirectUri)){
|
||||
redirectMismatch=true;
|
||||
@@ -95,8 +103,8 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
|
||||
if ((redirectUri != null || redirectUriApprovalParameter != null)
|
||||
&& !redirectMismatch) {
|
||||
logger.info("storedAuth redirectUri "+pendingOAuth2Request.getRedirectUri());
|
||||
logger.info("redirectUri "+ redirectUri);
|
||||
logger.info("storedRedirectUri "+ redirectUris);
|
||||
logger.info("redirectUri parameter "+ redirectUri);
|
||||
logger.info("stored RedirectUri "+ redirectUris);
|
||||
throw new RedirectMismatchException("Redirect URI mismatch.");
|
||||
}
|
||||
/*
|
||||
@@ -112,6 +120,31 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
|
||||
// just a sanity check.
|
||||
throw new InvalidClientException("Client ID mismatch");
|
||||
}
|
||||
|
||||
//OAuth 2.1 and PKCE Support
|
||||
logger.debug("client Protocol "+client.getProtocol()+", PKCE Support "+
|
||||
(client.getPkce().equalsIgnoreCase(OAuth2Constants.PKCE_TYPE.PKCE_TYPE_YES)));
|
||||
if(client.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH21)
|
||||
|| client.getPkce().equalsIgnoreCase(OAuth2Constants.PKCE_TYPE.PKCE_TYPE_YES)) {
|
||||
logger.trace("stored CodeChallengeMethod "+ pendingOAuth2Request.getCodeChallengeMethod());
|
||||
logger.trace("stored CodeChallenge "+ pendingOAuth2Request.getCodeChallenge());
|
||||
logger.trace("stored codeVerifier "+ codeVerifier);
|
||||
if(StringUtils.isBlank(codeVerifier)) {
|
||||
throw new OAuth2Exception("code_verifier can not null.");
|
||||
}
|
||||
|
||||
if(StringUtils.isBlank(pendingOAuth2Request.getCodeChallenge())) {
|
||||
throw new OAuth2Exception("code_challenge can not null.");
|
||||
}
|
||||
|
||||
if(CODE_CHALLENGE_METHOD_TYPE.S256.equalsIgnoreCase(pendingOAuth2Request.getCodeChallengeMethod())) {
|
||||
codeVerifier = DigestUtils.digestBase64Url(codeVerifier,DigestUtils.Algorithm.SHA256);
|
||||
}
|
||||
|
||||
if(!codeVerifier.equals(pendingOAuth2Request.getCodeChallenge())) {
|
||||
throw new OAuth2Exception("code_verifier not match.");
|
||||
}
|
||||
}
|
||||
|
||||
// Secret is not required in the authorization request, so it won't be available
|
||||
// in the pendingAuthorizationRequest. We do want to check that a secret is provided
|
||||
|
||||
@@ -123,8 +123,11 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
|
||||
@ApiOperation(value = "OAuth 2.0 认证接口", notes = "传递参数client_id,response_type,redirect_uri等",httpMethod="GET")
|
||||
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_AUTHORIZE, method = RequestMethod.GET)
|
||||
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
|
||||
SessionStatus sessionStatus) {
|
||||
public ModelAndView authorize(
|
||||
Map<String, Object> model,
|
||||
@RequestParam Map<String, String> parameters,
|
||||
SessionStatus sessionStatus) {
|
||||
|
||||
Principal principal=(Principal)WebContext.getAuthentication();
|
||||
// Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
|
||||
// query off of the authorization request instead of referring back to the parameters map. The contents of the
|
||||
@@ -152,7 +155,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
|
||||
// The resolved redirect URI is either the redirect_uri from the parameters or the one from
|
||||
// clientDetails. Either way we need to store it on the AuthorizationRequest.
|
||||
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
|
||||
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Constants.PARAMETER.REDIRECT_URI);
|
||||
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
|
||||
if (!StringUtils.hasText(resolvedRedirect)) {
|
||||
logger.info("Client redirectUri "+resolvedRedirect);
|
||||
@@ -202,8 +205,11 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
}
|
||||
|
||||
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_AUTHORIZE, method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
|
||||
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
|
||||
SessionStatus sessionStatus) {
|
||||
public View approveOrDeny(
|
||||
@RequestParam Map<String, String> approvalParameters,
|
||||
Map<String, ?> model,
|
||||
SessionStatus sessionStatus) {
|
||||
|
||||
Principal principal=(Principal)WebContext.getAuthentication();
|
||||
if (!(principal instanceof Authentication)) {
|
||||
sessionStatus.setComplete();
|
||||
@@ -233,8 +239,12 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
}
|
||||
|
||||
if (!authorizationRequest.isApproved()) {
|
||||
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
|
||||
new UserDeniedAuthorizationException("User denied access"), responseTypes.contains(OAuth2Constants.PARAMETER.TOKEN)),
|
||||
return new RedirectView(
|
||||
getUnsuccessfulRedirect(
|
||||
authorizationRequest,
|
||||
new UserDeniedAuthorizationException("User denied access"),
|
||||
responseTypes.contains(OAuth2Constants.PARAMETER.TOKEN)
|
||||
),
|
||||
false, true, false);
|
||||
}
|
||||
|
||||
@@ -268,12 +278,24 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
if (accessToken == null) {
|
||||
throw new UnsupportedResponseTypeException("Unsupported response type: token");
|
||||
}
|
||||
return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
|
||||
false));
|
||||
return new ModelAndView(
|
||||
new RedirectView(
|
||||
appendAccessToken(authorizationRequest, accessToken),
|
||||
false,
|
||||
true,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
catch (OAuth2Exception e) {
|
||||
return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
|
||||
true, false));
|
||||
return new ModelAndView(
|
||||
new RedirectView(
|
||||
getUnsuccessfulRedirect(authorizationRequest, e, true),
|
||||
false,
|
||||
true,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +353,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
|
||||
url.append("&").append(templateUrlVar(OAuth2Constants.PARAMETER.EXPIRES_IN));
|
||||
vars.put(OAuth2Constants.PARAMETER.EXPIRES_IN, expires_in);
|
||||
}
|
||||
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
|
||||
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Constants.PARAMETER.SCOPE);
|
||||
if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
|
||||
url.append("&").append(templateUrlVar(OAuth2Constants.PARAMETER.SCOPE));
|
||||
vars.put(OAuth2Constants.PARAMETER.SCOPE, OAuth2Utils.formatParameterList(accessToken.getScope()));
|
||||
|
||||
@@ -157,9 +157,9 @@ public class TokenEndpoint extends AbstractEndpoint {
|
||||
|
||||
if (isRefreshTokenRequest(parameters)) {
|
||||
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
|
||||
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
|
||||
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Constants.PARAMETER.SCOPE)));
|
||||
}
|
||||
|
||||
//granter grant access token
|
||||
token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
|
||||
if (token == null) {
|
||||
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
|
||||
|
||||
@@ -161,7 +161,7 @@ public class DefaultAccessTokenConverter implements AccessTokenConverter {
|
||||
authorities = AuthorityUtils.createAuthorityList(roles);
|
||||
}
|
||||
OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
|
||||
null);
|
||||
null, null, null);
|
||||
return new OAuth2Authentication(request, user);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user