提交 8b3c0351 编写于 作者: MaxKey单点登录官方's avatar MaxKey单点登录官方

OAuth 2.1 PKCE

上级 eca33676
......@@ -61,6 +61,25 @@ public final class DigestUtils {
}
return cipherBASE64;
}
/**
* @param simple
* @param algorithm MD5,SHA,SHA-1|SHA-256|SHA-384|SHA-512 then encodeBase64
* @return cipher
*/
public static String digestBase64Url(String simple,String algorithm) {
MessageDigest messageDigest;
String cipherBASE64="";
try {
messageDigest = MessageDigest.getInstance(algorithm.toUpperCase());
messageDigest.update(simple.getBytes());
byte[] bCipher=messageDigest.digest();
cipherBASE64=Base64Utils.base64UrlEncode(bCipher);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return cipherBASE64;
}
//B64
public static String md5B64(String simple) {
......
......@@ -42,7 +42,9 @@ public class Base64UtilsTest {
System.out.println(Base64Utils.decode("AAMkADU2OWY1MGQ3LWEyNWQtNDFmOC04MWFiLTI5YTE2NGM5YTZmNABGAAAAAABPKgpqnlfYQ7BVC/BfH2XIBwCS0xhUjzMYSLVky9bw7LddAAAAjov5AACS0xhUjzMYSLVky9bw7LddAAADzoyxAAA="));
String b = "UsWdAIe4opTqcrX6~SrIMhBu5Gc9oZKEnnSDFRx9JwBINK8XTgnXUs2A3b7QmxDM9nRu8~mGsikVEoISLg.JTIHYRwv-Bp5ljIADLwUHv9iJAWo1delBOlW0Hd7nIVF0";
System.out.println(DigestUtils.digestBase64Url(b,DigestUtils.Algorithm.SHA256));
}
}
......@@ -34,10 +34,13 @@ public final class ConstantsProtocols {
// OAuth
public static final String OAUTH20 = "OAuth_v2.0";
public static final String OAUTH21 = "OAuth_v2.1";
// SAML
public static final String SAML20 = "SAML_v2.0";
public static final String OPEN_ID_CONNECT = "OpenID_Connect";
public static final String OPEN_ID_CONNECT10 = "OpenID_Connect_v1.0";
public static final String CAS = "CAS";
......
......@@ -64,8 +64,10 @@ public class AuthorizeEndpoint extends AuthorizeBaseEndpoint{
modelAndView=WebContext.forward("/authz/formbased/"+id);
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH20)){
modelAndView=WebContext.forward("/authz/oauth/v20/"+application.getId());
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OPEN_ID_CONNECT)){
// modelAndView=new ModelAndView("openid connect");
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH21)){
modelAndView=WebContext.redirect(application.getLoginUrl());
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OPEN_ID_CONNECT10)){
modelAndView=WebContext.forward("/authz/oauth/v20/"+application.getId());
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.SAML20)){
modelAndView=WebContext.forward("/authz/saml20/idpinit/"+application.getId());
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.TOKENBASED)){
......
......@@ -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);
}
......
......@@ -17,6 +17,7 @@
package org.maxkey.web.apps.contorller;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.provider.client.JdbcClientDetailsService;
import org.maxkey.constants.ConstantsOperateMessage;
import org.maxkey.constants.ConstantsProtocols;
......@@ -65,6 +66,9 @@ public class OAuth20DetailsController extends BaseAppContorller {
public ModelAndView insert(@ModelAttribute("oauth20Details") AppsOAuth20Details oauth20Details ) {
_logger.debug("-Add :" + oauth20Details);
if(oauth20Details.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH21)) {
oauth20Details.setPkce(OAuth2Constants.PKCE_TYPE.PKCE_TYPE_YES);
}
transform(oauth20Details);
oauth20Details.setClientSecret(oauth20Details.getSecret());
......@@ -103,6 +107,9 @@ public class OAuth20DetailsController extends BaseAppContorller {
//
_logger.debug("-update application :" + oauth20Details);
_logger.debug("-update oauth20Details use oauth20JdbcClientDetails" );
if(oauth20Details.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH21)) {
oauth20Details.setPkce(OAuth2Constants.PKCE_TYPE.PKCE_TYPE_YES);
}
oauth20Details.setClientSecret(oauth20Details.getSecret());
oauth20JdbcClientDetailsService.updateClientDetails(oauth20Details.clientDetailsRowMapper());
oauth20JdbcClientDetailsService.updateClientSecret(oauth20Details.getClientId(), oauth20Details.getClientSecret());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册