KEYCLOAK-18452 FAPI JARM: JWT Secured Authorization Response Mode for OAuth 2.0
This commit is contained in:
parent
04ff2c327b
commit
e5ae113453
24 changed files with 957 additions and 27 deletions
|
@ -133,6 +133,9 @@ public interface OAuth2Constants {
|
|||
String DISPLAY_CONSOLE = "console";
|
||||
String INTERVAL = "interval";
|
||||
String USER_CODE = "user_code";
|
||||
|
||||
// https://openid.net/specs/openid-financial-api-jarm-ID1.html
|
||||
String RESPONSE = "response";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -22,5 +22,6 @@ public enum TokenCategory {
|
|||
ID,
|
||||
ADMIN,
|
||||
USERINFO,
|
||||
LOGOUT
|
||||
LOGOUT,
|
||||
AUTHORIZATION_RESPONSE
|
||||
}
|
||||
|
|
|
@ -97,6 +97,15 @@ public class OIDCConfigurationRepresentation {
|
|||
@JsonProperty("introspection_endpoint_auth_signing_alg_values_supported")
|
||||
private List<String> introspectionEndpointAuthSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("authorization_signing_alg_values_supported")
|
||||
private List<String> authorizationSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("authorization_encryption_alg_values_supported")
|
||||
private List<String> authorizationEncryptionAlgValuesSupported;
|
||||
|
||||
@JsonProperty("authorization_encryption_enc_values_supported")
|
||||
private List<String> authorizationEncryptionEncValuesSupported;
|
||||
|
||||
@JsonProperty("claims_supported")
|
||||
private List<String> claimsSupported;
|
||||
|
||||
|
@ -489,4 +498,28 @@ public class OIDCConfigurationRepresentation {
|
|||
public String getDeviceAuthorizationEndpoint() {
|
||||
return deviceAuthorizationEndpoint;
|
||||
}
|
||||
|
||||
public List<String> getAuthorizationSigningAlgValuesSupported() {
|
||||
return authorizationSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public void setAuthorizationSigningAlgValuesSupported(List<String> authorizationSigningAlgValuesSupported) {
|
||||
this.authorizationSigningAlgValuesSupported = authorizationSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public List<String> getAuthorizationEncryptionAlgValuesSupported() {
|
||||
return authorizationEncryptionAlgValuesSupported;
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptionAlgValuesSupported(List<String> authorizationEncryptionAlgValuesSupported) {
|
||||
this.authorizationEncryptionAlgValuesSupported = authorizationEncryptionAlgValuesSupported;
|
||||
}
|
||||
|
||||
public List<String> getAuthorizationEncryptionEncValuesSupported() {
|
||||
return authorizationEncryptionEncValuesSupported;
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptionEncValuesSupported(List<String> authorizationEncryptionEncValuesSupported) {
|
||||
this.authorizationEncryptionEncValuesSupported = authorizationEncryptionEncValuesSupported;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.representations;
|
||||
|
||||
import org.keycloak.TokenCategory;
|
||||
|
||||
public class AuthorizationResponseToken extends JsonWebToken{
|
||||
|
||||
@Override
|
||||
public TokenCategory getCategory() {
|
||||
return TokenCategory.AUTHORIZATION_RESPONSE;
|
||||
}
|
||||
}
|
|
@ -130,6 +130,13 @@ public class OIDCClientRepresentation {
|
|||
|
||||
private String backchannel_authentication_request_signing_alg;
|
||||
|
||||
// FAPI JARM
|
||||
private String authorization_signed_response_alg;
|
||||
|
||||
private String authorization_encrypted_response_alg;
|
||||
|
||||
private String authorization_encrypted_response_enc;
|
||||
|
||||
public List<String> getRedirectUris() {
|
||||
return redirect_uris;
|
||||
}
|
||||
|
@ -507,4 +514,28 @@ public class OIDCClientRepresentation {
|
|||
public void setBackchannelAuthenticationRequestSigningAlg(String backchannel_authentication_request_signing_alg) {
|
||||
this.backchannel_authentication_request_signing_alg = backchannel_authentication_request_signing_alg;
|
||||
}
|
||||
|
||||
public String getAuthorizationSignedResponseAlg() {
|
||||
return authorization_signed_response_alg;
|
||||
}
|
||||
|
||||
public void setAuthorizationSignedResponseAlg(String authorization_signed_response_alg) {
|
||||
this.authorization_signed_response_alg = authorization_signed_response_alg;
|
||||
}
|
||||
|
||||
public String getAuthorizationEncryptedResponseAlg() {
|
||||
return authorization_encrypted_response_alg;
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptedResponseAlg(String authorization_encrypted_response_alg) {
|
||||
this.authorization_encrypted_response_alg = authorization_encrypted_response_alg;
|
||||
}
|
||||
|
||||
public String getAuthorizationEncryptedResponseEnc() {
|
||||
return authorization_encrypted_response_enc;
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptedResponseEnc(String authorization_encrypted_response_enc) {
|
||||
this.authorization_encrypted_response_enc = authorization_encrypted_response_enc;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,8 @@ public class DefaultTokenManager implements TokenManager {
|
|||
return getSignatureAlgorithm(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG);
|
||||
case USERINFO:
|
||||
return getSignatureAlgorithm(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG);
|
||||
case AUTHORIZATION_RESPONSE:
|
||||
return getSignatureAlgorithm(OIDCConfigAttributes.AUTHORIZATION_SIGNED_RESPONSE_ALG);
|
||||
default:
|
||||
throw new RuntimeException("Unknown token type");
|
||||
}
|
||||
|
@ -211,6 +213,8 @@ public class DefaultTokenManager implements TokenManager {
|
|||
case ID:
|
||||
case LOGOUT:
|
||||
return getCekManagementAlgorithm(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG);
|
||||
case AUTHORIZATION_RESPONSE:
|
||||
return getCekManagementAlgorithm(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -232,6 +236,8 @@ public class DefaultTokenManager implements TokenManager {
|
|||
case ID:
|
||||
case LOGOUT:
|
||||
return getEncryptAlgorithm(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC);
|
||||
case AUTHORIZATION_RESPONSE:
|
||||
return getEncryptAlgorithm(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -195,6 +195,29 @@ public class OIDCAdvancedConfigWrapper {
|
|||
setAttribute(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC, encName);
|
||||
}
|
||||
|
||||
public String getAuthorizationSignedResponseAlg() {
|
||||
return getAttribute(OIDCConfigAttributes.AUTHORIZATION_SIGNED_RESPONSE_ALG);
|
||||
}
|
||||
public void setAuthorizationSignedResponseAlg(String algName) {
|
||||
setAttribute(OIDCConfigAttributes.AUTHORIZATION_SIGNED_RESPONSE_ALG, algName);
|
||||
}
|
||||
|
||||
public String getAuthorizationEncryptedResponseAlg() {
|
||||
return getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG);
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptedResponseAlg(String algName) {
|
||||
setAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG, algName);
|
||||
}
|
||||
|
||||
public String getAuthorizationEncryptedResponseEnc() {
|
||||
return getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC);
|
||||
}
|
||||
|
||||
public void setAuthorizationEncryptedResponseEnc(String encName) {
|
||||
setAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC, encName);
|
||||
}
|
||||
|
||||
public String getTokenEndpointAuthSigningAlg() {
|
||||
return getAttribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ public final class OIDCConfigAttributes {
|
|||
|
||||
public static final String ID_TOKEN_AS_DETACHED_SIGNATURE = "id.token.as.detached.signature";
|
||||
|
||||
public static final String AUTHORIZATION_SIGNED_RESPONSE_ALG = "authorization.signed.response.alg";
|
||||
public static final String AUTHORIZATION_ENCRYPTED_RESPONSE_ALG = "authorization.encrypted.response.alg";
|
||||
public static final String AUTHORIZATION_ENCRYPTED_RESPONSE_ENC = "authorization.encrypted.response.enc";
|
||||
|
||||
private OIDCConfigAttributes() {
|
||||
}
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
setupResponseTypeAndMode(responseTypeParam, responseModeParam);
|
||||
|
||||
String redirect = authSession.getRedirectUri();
|
||||
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
|
||||
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode, session, clientSession);
|
||||
String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
|
||||
logger.debugv("redirectAccessCode: state: {0}", state);
|
||||
if (state != null)
|
||||
|
@ -287,14 +287,14 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||
if (isOAuth2DeviceVerificationFlow(authSession)) {
|
||||
return denyOAuth2DeviceAuthorization(authSession, error, session);
|
||||
}
|
||||
|
||||
String responseTypeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
|
||||
String responseModeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
|
||||
setupResponseTypeAndMode(responseTypeParam, responseModeParam);
|
||||
|
||||
String redirect = authSession.getRedirectUri();
|
||||
String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
|
||||
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
|
||||
|
||||
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode, session, null);
|
||||
|
||||
if (error != Error.CANCELLED_AIA_SILENT) {
|
||||
redirectUri.addParam(OAuth2Constants.ERROR, translateError(error));
|
||||
|
|
|
@ -74,7 +74,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
|
||||
public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public", "pairwise");
|
||||
|
||||
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post");
|
||||
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post", "query.jwt", "fragment.jwt", "form_post.jwt", "jwt");
|
||||
|
||||
public static final List<String> DEFAULT_CLIENT_AUTH_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.RS256.toString());
|
||||
|
||||
|
@ -126,8 +126,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(backendUriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
|
||||
|
||||
config.setIdTokenSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
|
||||
config.setIdTokenEncryptionAlgValuesSupported(getSupportedIdTokenEncryptionAlg(false));
|
||||
config.setIdTokenEncryptionEncValuesSupported(getSupportedIdTokenEncryptionEnc(false));
|
||||
config.setIdTokenEncryptionAlgValuesSupported(getSupportedEncryptionAlg(false));
|
||||
config.setIdTokenEncryptionEncValuesSupported(getSupportedEncryptionEnc(false));
|
||||
config.setUserInfoSigningAlgValuesSupported(getSupportedSigningAlgorithms(true));
|
||||
config.setRequestObjectSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(true));
|
||||
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
|
||||
|
@ -140,6 +140,10 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
config.setIntrospectionEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
|
||||
config.setIntrospectionEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
|
||||
|
||||
config.setAuthorizationSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
|
||||
config.setAuthorizationEncryptionAlgValuesSupported(getSupportedEncryptionAlg(false));
|
||||
config.setAuthorizationEncryptionEncValuesSupported(getSupportedEncryptionEnc(false));
|
||||
|
||||
config.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED);
|
||||
config.setClaimTypesSupported(DEFAULT_CLAIM_TYPES_SUPPORTED);
|
||||
config.setClaimsParameterSupported(true);
|
||||
|
@ -228,11 +232,11 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
return getSupportedAsymmetricAlgorithms();
|
||||
}
|
||||
|
||||
private List<String> getSupportedIdTokenEncryptionAlg(boolean includeNone) {
|
||||
private List<String> getSupportedEncryptionAlg(boolean includeNone) {
|
||||
return getSupportedAlgorithms(CekManagementProvider.class, includeNone);
|
||||
}
|
||||
|
||||
private List<String> getSupportedIdTokenEncryptionEnc(boolean includeNone) {
|
||||
private List<String> getSupportedEncryptionEnc(boolean includeNone) {
|
||||
return getSupportedAlgorithms(ContentEncryptionProvider.class, includeNone);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
||||
|
@ -53,6 +54,7 @@ import org.keycloak.services.resources.LoginActionsService;
|
|||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
|
@ -61,7 +63,6 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -289,6 +290,14 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
return redirectErrorToClient(OIDCResponseMode.QUERY, OAuthErrorException.INVALID_REQUEST, "Response_mode 'query' not allowed for implicit or hybrid flow");
|
||||
}
|
||||
|
||||
if(parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY_JWT &&
|
||||
(!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ALG)) ||
|
||||
!StringUtil.isNotBlank(client.getAttribute(OIDCConfigAttributes.AUTHORIZATION_ENCRYPTED_RESPONSE_ENC)))) {
|
||||
ServicesLogger.LOGGER.responseModeQueryJwtNotAllowed();
|
||||
event.error(Errors.INVALID_REQUEST);
|
||||
return redirectErrorToClient(OIDCResponseMode.QUERY_JWT, OAuthErrorException.INVALID_REQUEST, "Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted");
|
||||
}
|
||||
|
||||
if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
|
||||
ServicesLogger.LOGGER.flowNotAllowed("Standard");
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
|
@ -422,7 +431,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
private Response redirectErrorToClient(OIDCResponseMode responseMode, String error, String errorDescription) {
|
||||
OIDCRedirectUriBuilder errorResponseBuilder = OIDCRedirectUriBuilder.fromUri(redirectUri, responseMode)
|
||||
OIDCRedirectUriBuilder errorResponseBuilder = OIDCRedirectUriBuilder.fromUri(redirectUri, responseMode, session, null)
|
||||
.addParam(OAuth2Constants.ERROR, error);
|
||||
|
||||
if (errorDescription != null) {
|
||||
|
|
|
@ -20,6 +20,11 @@ package org.keycloak.protocol.oidc.utils;
|
|||
import org.keycloak.common.util.Encode;
|
||||
import org.keycloak.common.util.HtmlUtils;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -43,13 +48,17 @@ public abstract class OIDCRedirectUriBuilder {
|
|||
public abstract Response build();
|
||||
|
||||
|
||||
public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode) {
|
||||
public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode, KeycloakSession session, AuthenticatedClientSessionModel clientSession) {
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseUri);
|
||||
|
||||
switch (responseMode) {
|
||||
case QUERY: return new QueryRedirectUriBuilder(uriBuilder);
|
||||
case FRAGMENT: return new FragmentRedirectUriBuilder(uriBuilder);
|
||||
case FORM_POST: return new FormPostRedirectUriBuilder(uriBuilder);
|
||||
case QUERY_JWT:
|
||||
case FRAGMENT_JWT:
|
||||
case FORM_POST_JWT:
|
||||
return new JWTRedirectUriBuilder(uriBuilder, responseMode, session, clientSession);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Not possible to end here");
|
||||
|
@ -171,5 +180,88 @@ public abstract class OIDCRedirectUriBuilder {
|
|||
|
||||
}
|
||||
|
||||
// https://openid.net/specs/openid-financial-api-jarm-ID1.html
|
||||
private static class JWTRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
|
||||
private OIDCResponseMode responseMode;
|
||||
private AuthorizationResponseToken responseJWT;
|
||||
private KeycloakSession session;
|
||||
private AuthenticatedClientSessionModel clientSession;
|
||||
|
||||
public JWTRedirectUriBuilder(KeycloakUriBuilder uriBuilder, OIDCResponseMode responseMode, KeycloakSession session, AuthenticatedClientSessionModel clientSession) {
|
||||
super(uriBuilder);
|
||||
this.responseMode = responseMode;
|
||||
this.session = session;
|
||||
this.clientSession = clientSession;
|
||||
responseJWT = new AuthorizationResponseToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
|
||||
responseJWT.getOtherClaims().put(paramName, paramValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response build() {
|
||||
if(clientSession != null) {
|
||||
responseJWT.issuer(clientSession.getNote(OIDCLoginProtocol.ISSUER));
|
||||
responseJWT.audience(clientSession.getClient().getClientId());
|
||||
responseJWT.setOtherClaims("scope", clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM));
|
||||
responseJWT.exp((long) (Time.currentTime() + clientSession.getRealm().getAccessCodeLifespan()));
|
||||
}
|
||||
switch (responseMode) {
|
||||
case QUERY_JWT:
|
||||
return buildQueryResponse();
|
||||
case FRAGMENT_JWT:
|
||||
return buildFragmentResponse();
|
||||
case FORM_POST_JWT:
|
||||
return buildFormPostResponse();
|
||||
}
|
||||
throw new IllegalStateException("Not possible to end here");
|
||||
}
|
||||
|
||||
private Response buildQueryResponse() {
|
||||
uriBuilder.queryParam("response", session.tokens().encodeAndEncrypt(responseJWT));
|
||||
URI redirectUri = uriBuilder.build();
|
||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri);
|
||||
return location.build();
|
||||
}
|
||||
|
||||
private Response buildFragmentResponse() {
|
||||
uriBuilder.encodedFragment("response=" + Encode.encodeQueryParamAsIs(session.tokens().encodeAndEncrypt(responseJWT)));
|
||||
URI redirectUri = uriBuilder.build();
|
||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri);
|
||||
return location.build();
|
||||
}
|
||||
|
||||
private Response buildFormPostResponse() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
URI redirectUri = uriBuilder.build();
|
||||
|
||||
builder.append("<HTML>");
|
||||
builder.append(" <HEAD>");
|
||||
builder.append(" <TITLE>OIDC Form_Post Response</TITLE>");
|
||||
builder.append(" </HEAD>");
|
||||
builder.append(" <BODY Onload=\"document.forms[0].submit()\">");
|
||||
|
||||
builder.append(" <FORM METHOD=\"POST\" ACTION=\"" + redirectUri.toString() + "\">");
|
||||
|
||||
builder.append(" <INPUT TYPE=\"HIDDEN\" NAME=\"response\" VALUE=\"")
|
||||
.append(HtmlUtils.escapeAttribute(session.tokens().encodeAndEncrypt(responseJWT)))
|
||||
.append("\" />");
|
||||
|
||||
builder.append(" <NOSCRIPT>");
|
||||
builder.append(" <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>");
|
||||
builder.append(" <INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
||||
builder.append(" </NOSCRIPT>");
|
||||
builder.append(" </FORM>");
|
||||
builder.append(" </BODY>");
|
||||
builder.append("</HTML>");
|
||||
|
||||
return Response.status(Response.Status.OK)
|
||||
.type(MediaType.TEXT_HTML_TYPE)
|
||||
.entity(builder.toString()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,42 @@ package org.keycloak.protocol.oidc.utils;
|
|||
*/
|
||||
public enum OIDCResponseMode {
|
||||
|
||||
QUERY, FRAGMENT, FORM_POST;
|
||||
QUERY("query"),
|
||||
FRAGMENT("fragment"),
|
||||
FORM_POST("form_post"),
|
||||
QUERY_JWT("query.jwt"),
|
||||
FRAGMENT_JWT("fragment.jwt"),
|
||||
FORM_POST_JWT("form_post.jwt");
|
||||
|
||||
private String value;
|
||||
|
||||
OIDCResponseMode(String v) {
|
||||
value = v;
|
||||
}
|
||||
|
||||
public static OIDCResponseMode parse(String responseMode, OIDCResponseType responseType) {
|
||||
if (responseMode == null) {
|
||||
return getDefaultResponseMode(responseType);
|
||||
} else if(responseMode.equals("jwt")) {
|
||||
return getDefaultJarmResponseMode(responseType);
|
||||
} else {
|
||||
return Enum.valueOf(OIDCResponseMode.class, responseMode.toUpperCase());
|
||||
return fromValue(responseMode);
|
||||
}
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static OIDCResponseMode fromValue(String v) {
|
||||
for (OIDCResponseMode c : OIDCResponseMode.values()) {
|
||||
if (c.value.equals(v)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(v);
|
||||
}
|
||||
|
||||
private static OIDCResponseMode getDefaultResponseMode(OIDCResponseType responseType) {
|
||||
if (responseType.isImplicitOrHybridFlow()) {
|
||||
return OIDCResponseMode.FRAGMENT;
|
||||
|
@ -39,4 +65,12 @@ public enum OIDCResponseMode {
|
|||
return OIDCResponseMode.QUERY;
|
||||
}
|
||||
}
|
||||
|
||||
private static OIDCResponseMode getDefaultJarmResponseMode(OIDCResponseType responseType) {
|
||||
if (responseType.isImplicitOrHybridFlow()) {
|
||||
return OIDCResponseMode.FRAGMENT_JWT;
|
||||
} else {
|
||||
return OIDCResponseMode.QUERY_JWT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -459,4 +459,7 @@ public interface ServicesLogger extends BasicLogger {
|
|||
@Message(id=104, value="Not creating user %s. It already exists.")
|
||||
void notCreatingExistingUser(String userName);
|
||||
|
||||
@LogMessage(level = ERROR)
|
||||
@Message(id=105, value="Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted")
|
||||
void responseModeQueryJwtNotAllowed();
|
||||
}
|
||||
|
|
|
@ -162,6 +162,10 @@ public class DescriptionConverter {
|
|||
configWrapper.setIdTokenEncryptedResponseEnc(clientOIDC.getIdTokenEncryptedResponseEnc());
|
||||
}
|
||||
|
||||
configWrapper.setAuthorizationSignedResponseAlg(clientOIDC.getAuthorizationSignedResponseAlg());
|
||||
configWrapper.setAuthorizationEncryptedResponseAlg(clientOIDC.getAuthorizationEncryptedResponseAlg());
|
||||
configWrapper.setAuthorizationEncryptedResponseEnc(clientOIDC.getAuthorizationEncryptedResponseEnc());
|
||||
|
||||
if (clientOIDC.getRequestUris() != null) {
|
||||
configWrapper.setRequestUris(clientOIDC.getRequestUris());
|
||||
}
|
||||
|
@ -330,6 +334,15 @@ public class DescriptionConverter {
|
|||
if (config.getIdTokenEncryptedResponseEnc() != null) {
|
||||
response.setIdTokenEncryptedResponseEnc(config.getIdTokenEncryptedResponseEnc());
|
||||
}
|
||||
if (config.getAuthorizationSignedResponseAlg() != null) {
|
||||
response.setAuthorizationSignedResponseAlg(config.getAuthorizationSignedResponseAlg());
|
||||
}
|
||||
if (config.getAuthorizationEncryptedResponseAlg() != null) {
|
||||
response.setAuthorizationEncryptedResponseAlg(config.getAuthorizationEncryptedResponseAlg());
|
||||
}
|
||||
if (config.getAuthorizationEncryptedResponseEnc() != null) {
|
||||
response.setAuthorizationEncryptedResponseEnc(config.getAuthorizationEncryptedResponseEnc());
|
||||
}
|
||||
if (config.getRequestUris() != null) {
|
||||
response.setRequestUris(config.getRequestUris());
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
|||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
@ -1053,6 +1054,10 @@ public class OAuthClient {
|
|||
return verifyToken(token, IDToken.class);
|
||||
}
|
||||
|
||||
public AuthorizationResponseToken verifyAuthorizationResponseToken(String token) {
|
||||
return verifyToken(token, AuthorizationResponseToken.class);
|
||||
}
|
||||
|
||||
public RefreshToken parseRefreshToken(String refreshToken) {
|
||||
try {
|
||||
return new JWSInput(refreshToken).readJsonContent(RefreshToken.class);
|
||||
|
@ -1467,18 +1472,20 @@ public class OAuthClient {
|
|||
private String tokenType;
|
||||
private String expiresIn;
|
||||
|
||||
// Just during FAPI JARM response mode JWT
|
||||
private String response;
|
||||
|
||||
public AuthorizationEndpointResponse(OAuthClient client) {
|
||||
boolean fragment;
|
||||
try {
|
||||
fragment = client.responseType != null && OIDCResponseType.parse(client.responseType).isImplicitOrHybridFlow();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
fragment = false;
|
||||
if (client.responseMode == null || "jwt".equals(client.responseMode)) {
|
||||
try {
|
||||
fragment = client.responseType != null && OIDCResponseType.parse(client.responseType).isImplicitOrHybridFlow();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
fragment = false;
|
||||
}
|
||||
} else {
|
||||
fragment = "fragment".equals(client.responseMode) || "fragment.jwt".equals(client.responseMode);
|
||||
}
|
||||
|
||||
if ("fragment".equals(client.responseMode)) {
|
||||
fragment = true;
|
||||
}
|
||||
|
||||
init (client, fragment);
|
||||
}
|
||||
|
||||
|
@ -1499,6 +1506,7 @@ public class OAuthClient {
|
|||
idToken = params.get(OAuth2Constants.ID_TOKEN);
|
||||
tokenType = params.get(OAuth2Constants.TOKEN_TYPE);
|
||||
expiresIn = params.get(OAuth2Constants.EXPIRES_IN);
|
||||
response = params.get(OAuth2Constants.RESPONSE);
|
||||
}
|
||||
|
||||
public boolean isRedirected() {
|
||||
|
@ -1540,6 +1548,10 @@ public class OAuthClient {
|
|||
public String getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public String getResponse() {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthenticationRequestAcknowledgement {
|
||||
|
|
|
@ -389,6 +389,80 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationResponseSigningAlg() throws Exception {
|
||||
OIDCClientRepresentation response = null;
|
||||
OIDCClientRepresentation updated = null;
|
||||
try {
|
||||
OIDCClientRepresentation clientRep = createRep();
|
||||
clientRep.setAuthorizationSignedResponseAlg(Algorithm.PS256.toString());
|
||||
|
||||
response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(Algorithm.PS256.toString(), response.getAuthorizationSignedResponseAlg());
|
||||
|
||||
ClientRepresentation kcClient = getClient(response.getClientId());
|
||||
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(Algorithm.PS256.toString(), config.getAuthorizationSignedResponseAlg());
|
||||
|
||||
reg.auth(Auth.token(response));
|
||||
response.setAuthorizationSignedResponseAlg(null);
|
||||
updated = reg.oidc().update(response);
|
||||
Assert.assertEquals(null, response.getAuthorizationSignedResponseAlg());
|
||||
|
||||
kcClient = getClient(updated.getClientId());
|
||||
config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(null, config.getAuthorizationSignedResponseAlg());
|
||||
} finally {
|
||||
// revert
|
||||
reg.auth(Auth.token(updated));
|
||||
updated.setAuthorizationSignedResponseAlg(null);
|
||||
reg.oidc().update(updated);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptedResponse() throws Exception {
|
||||
OIDCClientRepresentation response = null;
|
||||
OIDCClientRepresentation updated = null;
|
||||
try {
|
||||
OIDCClientRepresentation clientRep = createRep();
|
||||
clientRep.setAuthorizationEncryptedResponseAlg(JWEConstants.RSA1_5);
|
||||
clientRep.setAuthorizationEncryptedResponseEnc(JWEConstants.A128CBC_HS256);
|
||||
|
||||
// create
|
||||
response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(JWEConstants.RSA1_5, response.getAuthorizationEncryptedResponseAlg());
|
||||
Assert.assertEquals(JWEConstants.A128CBC_HS256, response.getAuthorizationEncryptedResponseEnc());
|
||||
|
||||
// Test Keycloak representation
|
||||
ClientRepresentation kcClient = getClient(response.getClientId());
|
||||
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(JWEConstants.RSA1_5, config.getAuthorizationEncryptedResponseAlg());
|
||||
Assert.assertEquals(JWEConstants.A128CBC_HS256, config.getAuthorizationEncryptedResponseEnc());
|
||||
|
||||
// update
|
||||
reg.auth(Auth.token(response));
|
||||
response.setAuthorizationEncryptedResponseAlg(null);
|
||||
response.setAuthorizationEncryptedResponseEnc(null);
|
||||
updated = reg.oidc().update(response);
|
||||
Assert.assertNull(updated.getAuthorizationEncryptedResponseAlg());
|
||||
Assert.assertNull(updated.getAuthorizationEncryptedResponseEnc());
|
||||
|
||||
// Test Keycloak representation
|
||||
kcClient = getClient(updated.getClientId());
|
||||
config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertNull(config.getAuthorizationEncryptedResponseAlg());
|
||||
Assert.assertNull(config.getAuthorizationEncryptedResponseEnc());
|
||||
|
||||
} finally {
|
||||
// revert
|
||||
reg.auth(Auth.token(updated));
|
||||
updated.setAuthorizationEncryptedResponseAlg(null);
|
||||
updated.setAuthorizationEncryptedResponseEnc(null);
|
||||
reg.oidc().update(updated);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCIBASettings() throws Exception {
|
||||
OIDCClientRepresentation clientRep = null;
|
||||
|
|
|
@ -161,7 +161,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
|
|||
// KEYCLOAK-3281
|
||||
@Test
|
||||
public void authorizationRequestFormPostResponseMode() throws IOException {
|
||||
oauth.responseMode(OIDCResponseMode.FORM_POST.toString().toLowerCase());
|
||||
oauth.responseMode(OIDCResponseMode.FORM_POST.value());
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
|
@ -179,7 +179,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void authorizationRequestFormPostResponseModeWithCustomState() throws IOException {
|
||||
oauth.responseMode(OIDCResponseMode.FORM_POST.toString().toLowerCase());
|
||||
oauth.responseMode(OIDCResponseMode.FORM_POST.value());
|
||||
oauth.stateParamHardcoded("\"><foo>bar_baz(2)far</foo>");
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
|
@ -198,7 +198,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
|
|||
@Test
|
||||
public void authorizationRequestFragmentResponseModeNotKept() throws Exception {
|
||||
// Set response_mode=fragment and login
|
||||
oauth.responseMode(OIDCResponseMode.FRAGMENT.toString().toLowerCase());
|
||||
oauth.responseMode(OIDCResponseMode.FRAGMENT.value());
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
Assert.assertNotNull(response.getCode());
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.crypto.AesCbcHmacShaContentEncryptionProvider;
|
||||
import org.keycloak.crypto.AesGcmContentEncryptionProvider;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.RsaCekManagementProvider;
|
||||
import org.keycloak.jose.jwe.JWEConstants;
|
||||
import org.keycloak.jose.jwe.JWEException;
|
||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
|
||||
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.pages.*;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.TokenSignatureUtil;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Map;
|
||||
|
||||
public class AuthorizationTokenEncryptionTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected AccountUpdateProfilePage profilePage;
|
||||
|
||||
@Page
|
||||
protected OAuthGrantPage grantPage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA128CBC_HS256() {
|
||||
// add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.ES256, JWEConstants.RSA1_5, JWEConstants.A128CBC_HS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA192CBC_HS384() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS256, JWEConstants.RSA1_5, JWEConstants.A192CBC_HS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA256CBC_HS512() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS384, JWEConstants.RSA1_5, JWEConstants.A256CBC_HS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA128GCM() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.RS384, JWEConstants.RSA1_5, JWEConstants.A128GCM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA192GCM() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.RS512, JWEConstants.RSA1_5, JWEConstants.A192GCM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA1_5EncA256GCM() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.RS256, JWEConstants.RSA1_5, JWEConstants.A256GCM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA128CBC_HS256() {
|
||||
// add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
|
||||
TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.ES512, JWEConstants.RSA_OAEP, JWEConstants.A128CBC_HS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA192CBC_HS384() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS256, JWEConstants.RSA_OAEP, JWEConstants.A192CBC_HS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA256CBC_HS512() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS512, JWEConstants.RSA_OAEP, JWEConstants.A256CBC_HS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEP256EncA128CBC_HS256() {
|
||||
// add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
|
||||
TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.ES512, JWEConstants.RSA_OAEP_256, JWEConstants.A128CBC_HS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEP256EncA192CBC_HS384() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS256, JWEConstants.RSA_OAEP_256, JWEConstants.A192CBC_HS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEP256EncA256CBC_HS512() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS512, JWEConstants.RSA_OAEP_256, JWEConstants.A256CBC_HS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA128GCM() {
|
||||
// add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.ES256, JWEConstants.RSA_OAEP, JWEConstants.A128GCM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA192GCM() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS384, JWEConstants.RSA_OAEP, JWEConstants.A192GCM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEncryptionAlgRSA_OAEPEncA256GCM() {
|
||||
testAuthorizationTokenSignatureAndEncryption(Algorithm.PS512, JWEConstants.RSA_OAEP, JWEConstants.A256GCM);
|
||||
}
|
||||
|
||||
private void testAuthorizationTokenSignatureAndEncryption(String sigAlgorithm, String algAlgorithm, String encAlgorithm) {
|
||||
ClientResource clientResource;
|
||||
ClientRepresentation clientRep;
|
||||
try {
|
||||
// generate and register encryption key onto client
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.generateKeys(algAlgorithm);
|
||||
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
clientRep = clientResource.toRepresentation();
|
||||
// set authorization response signature algorithm and encryption algorithms
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationSignedResponseAlg(sigAlgorithm);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseAlg(algAlgorithm);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseEnc(encAlgorithm);
|
||||
// use and set jwks_url
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||
String jwksUrl = TestApplicationResourceUrls.clientJwksUri();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// get authorization response
|
||||
oauth.responseMode("jwt");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
// parse JWE and JOSE Header
|
||||
String jweStr = response.getResponse();
|
||||
String[] parts = jweStr.split("\\.");
|
||||
Assert.assertEquals(parts.length, 5);
|
||||
|
||||
// get decryption key
|
||||
// not publickey , use privateKey
|
||||
Map<String, String> keyPair = oidcClientEndpointsResource.getKeysAsPem();
|
||||
PrivateKey decryptionKEK = PemUtils.decodePrivateKey(keyPair.get("privateKey"));
|
||||
|
||||
// verify and decrypt JWE
|
||||
JWEAlgorithmProvider algorithmProvider = getJweAlgorithmProvider(algAlgorithm);
|
||||
JWEEncryptionProvider encryptionProvider = getJweEncryptionProvider(encAlgorithm);
|
||||
byte[] decodedString = TokenUtil.jweKeyEncryptionVerifyAndDecode(decryptionKEK, jweStr, algorithmProvider, encryptionProvider);
|
||||
String authorizationTokenString = new String(decodedString, "UTF-8");
|
||||
|
||||
// verify JWS
|
||||
AuthorizationResponseToken authorizationToken = oauth.verifyAuthorizationResponseToken(authorizationTokenString);
|
||||
Assert.assertEquals("test-app", authorizationToken.getAudience()[0]);
|
||||
Assert.assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", authorizationToken.getOtherClaims().get("state"));
|
||||
Assert.assertNotNull(authorizationToken.getOtherClaims().get("code"));
|
||||
} catch (JWEException | UnsupportedEncodingException e) {
|
||||
Assert.fail();
|
||||
} finally {
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
clientRep = clientResource.toRepresentation();
|
||||
// revert id token signature algorithm and encryption algorithms
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationSignedResponseAlg(Algorithm.RS256);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseAlg(null);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseEnc(null);
|
||||
// revert jwks_url settings
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(false);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(null);
|
||||
clientResource.update(clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
private JWEAlgorithmProvider getJweAlgorithmProvider(String algAlgorithm) {
|
||||
JWEAlgorithmProvider jweAlgorithmProvider = null;
|
||||
if (JWEConstants.RSA1_5.equals(algAlgorithm) || JWEConstants.RSA_OAEP.equals(algAlgorithm) ||
|
||||
JWEConstants.RSA_OAEP_256.equals(algAlgorithm)) {
|
||||
jweAlgorithmProvider = new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
|
||||
}
|
||||
return jweAlgorithmProvider;
|
||||
}
|
||||
private JWEEncryptionProvider getJweEncryptionProvider(String encAlgorithm) {
|
||||
JWEEncryptionProvider jweEncryptionProvider = null;
|
||||
switch(encAlgorithm) {
|
||||
case JWEConstants.A128GCM:
|
||||
case JWEConstants.A192GCM:
|
||||
case JWEConstants.A256GCM:
|
||||
jweEncryptionProvider = new AesGcmContentEncryptionProvider(null, encAlgorithm).jweEncryptionProvider();
|
||||
break;
|
||||
case JWEConstants.A128CBC_HS256:
|
||||
case JWEConstants.A192CBC_HS384:
|
||||
case JWEConstants.A256CBC_HS512:
|
||||
jweEncryptionProvider = new AesCbcHmacShaContentEncryptionProvider(null, encAlgorithm).jweEncryptionProvider();
|
||||
break;
|
||||
}
|
||||
return jweEncryptionProvider;
|
||||
}
|
||||
|
||||
@Test
|
||||
@UncaughtServerErrorExpected
|
||||
public void testAuthorizationEncryptionWithoutEncryptionKEK() throws MalformedURLException, URISyntaxException {
|
||||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
try {
|
||||
// generate and register signing/verifying key onto client, not encryption key
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.generateKeys(Algorithm.RS256);
|
||||
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
clientRep = clientResource.toRepresentation();
|
||||
// set id token signature algorithm and encryption algorithms
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationSignedResponseAlg(Algorithm.RS256);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseAlg(JWEConstants.RSA1_5);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseEnc(JWEConstants.A128CBC_HS256);
|
||||
// use and set jwks_url
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||
String jwksUrl = TestApplicationResourceUrls.clientJwksUri();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// get authorization response but failed
|
||||
oauth.responseMode("jwt");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse errorResponse = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
System.out.println(driver.getPageSource().contains("Unexpected error when handling authentication request to identity provider."));
|
||||
|
||||
} finally {
|
||||
// Revert
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationSignedResponseAlg(Algorithm.RS256);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseAlg(null);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationEncryptedResponseEnc(null);
|
||||
// Revert jwks_url settings
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(false);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(null);
|
||||
clientResource.update(clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.oidc;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AuthorizationResponseToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class AuthorizationTokenResponseModeTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Test
|
||||
public void authorizationRequestQueryJWTResponseMode() throws Exception {
|
||||
oauth.responseMode(OIDCResponseMode.QUERY_JWT.value());
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
assertTrue(response.isRedirected());
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(response.getResponse());
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestJWTResponseMode() throws Exception {
|
||||
// jwt response_mode. It should fallback to query.jwt
|
||||
oauth.responseMode("jwt");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
assertTrue(response.isRedirected());
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(response.getResponse());
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
URI currentUri = new URI(driver.getCurrentUrl());
|
||||
Assert.assertNotNull(currentUri.getRawQuery());
|
||||
Assert.assertNull(currentUri.getRawFragment());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestFragmentJWTResponseMode() throws Exception {
|
||||
oauth.responseMode(OIDCResponseMode.FRAGMENT_JWT.value());
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
assertTrue(response.isRedirected());
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(response.getResponse());
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
URI currentUri = new URI(driver.getCurrentUrl());
|
||||
Assert.assertNull(currentUri.getRawQuery());
|
||||
Assert.assertNotNull(currentUri.getRawFragment());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestFormPostJWTResponseMode() throws IOException {
|
||||
oauth.responseMode(OIDCResponseMode.FORM_POST_JWT.value());
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
String sources = driver.getPageSource();
|
||||
System.out.println(sources);
|
||||
|
||||
String responseTokenEncoded = driver.findElement(By.id("response")).getText();
|
||||
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(responseTokenEncoded);
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestJWTResponseModeIdTokenResponseType() throws Exception {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").implicitFlow(true);
|
||||
// jwt response_mode. It should fallback to fragment.jwt when its hybrid flow
|
||||
oauth.responseMode("jwt");
|
||||
oauth.responseType("code id_token");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
oauth.nonce("123456");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
assertTrue(response.isRedirected());
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(response.getResponse());
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("id_token"));
|
||||
String idTokenEncoded = (String) responseToken.getOtherClaims().get("id_token");
|
||||
IDToken idToken = oauth.verifyIDToken(idTokenEncoded);
|
||||
assertEquals("123456", idToken.getNonce());
|
||||
|
||||
URI currentUri = new URI(driver.getCurrentUrl());
|
||||
Assert.assertNull(currentUri.getRawQuery());
|
||||
Assert.assertNotNull(currentUri.getRawFragment());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestJWTResponseModeAccessTokenResponseType() throws Exception {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").implicitFlow(true);
|
||||
// jwt response_mode. It should fallback to fragment.jwt when its hybrid flow
|
||||
oauth.responseMode("jwt");
|
||||
oauth.responseType("token id_token");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
oauth.nonce("123456");
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
assertTrue(response.isRedirected());
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(response.getResponse());
|
||||
|
||||
assertEquals("test-app", responseToken.getAudience()[0]);
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("code"));
|
||||
assertEquals("OpenIdConnect.AuthenticationProperties=2302984sdlk", responseToken.getOtherClaims().get("state"));
|
||||
Assert.assertNull(responseToken.getOtherClaims().get("error"));
|
||||
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("id_token"));
|
||||
String idTokenEncoded = (String) responseToken.getOtherClaims().get("id_token");
|
||||
IDToken idToken = oauth.verifyIDToken(idTokenEncoded);
|
||||
assertEquals("123456", idToken.getNonce());
|
||||
|
||||
Assert.assertNotNull(responseToken.getOtherClaims().get("access_token"));
|
||||
String accessTokenEncoded = (String) responseToken.getOtherClaims().get("access_token");
|
||||
AccessToken accessToken = oauth.verifyToken(accessTokenEncoded);
|
||||
assertEquals("123456", accessToken.getNonce());
|
||||
|
||||
URI currentUri = new URI(driver.getCurrentUrl());
|
||||
Assert.assertNull(currentUri.getRawQuery());
|
||||
Assert.assertNotNull(currentUri.getRawFragment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationRequestFailInvalidResponseModeQueryJWT() throws Exception {
|
||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app").implicitFlow(true);
|
||||
oauth.responseMode("query.jwt");
|
||||
oauth.responseType("code id_token");
|
||||
oauth.stateParamHardcoded("OpenIdConnect.AuthenticationProperties=2302984sdlk");
|
||||
oauth.nonce("123456");
|
||||
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
|
||||
driver.navigate().to(b.build().toURL());
|
||||
|
||||
OAuthClient.AuthorizationEndpointResponse errorResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
|
||||
AuthorizationResponseToken responseToken = oauth.verifyAuthorizationResponseToken(errorResponse.getResponse());
|
||||
Assert.assertEquals(OAuthErrorException.INVALID_REQUEST, responseToken.getOtherClaims().get("error"));
|
||||
Assert.assertEquals("Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted", responseToken.getOtherClaims().get("error_description"));
|
||||
|
||||
events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().assertEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
}
|
|
@ -124,7 +124,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
assertContains(oidcConfig.getResponseTypesSupported(), OAuth2Constants.CODE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
|
||||
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT,
|
||||
OAuth2Constants.DEVICE_CODE_GRANT_TYPE);
|
||||
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment");
|
||||
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment", "form_post", "jwt", "query.jwt", "fragment.jwt", "form_post.jwt");
|
||||
|
||||
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
|
||||
|
||||
|
@ -132,10 +132,13 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
|
||||
// Encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||
Assert.assertNames(oidcConfig.getAuthorizationEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||
|
||||
// Client authentication
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
|
||||
|
|
|
@ -433,6 +433,12 @@ use-refresh-tokens=Use Refresh Tokens
|
|||
use-refresh-tokens.tooltip=If this is on, a refresh_token will be created and added to the token response. If this is off then no refresh_token will be generated.
|
||||
use-refresh-token-for-client-credentials-grant=Use Refresh Tokens For Client Credentials Grant
|
||||
use-refresh-token-for-client-credentials-grant.tooltip=If this is on, a refresh_token will be created and added to the token response if the client_credentials grant is used. The OAuth 2.0 RFC6749 Section 4.4.3 states that a refresh_token should not be generated when client_credentials grant is used. If this is off then no refresh_token will be generated and the associated user session will be removed.
|
||||
authorization-signed-response-alg=Authorization Response Signature Algorithm
|
||||
authorization-signed-response-alg.tooltip=JWA algorithm used for signing authorization response tokens when the response mode is jwt.
|
||||
authorization-encrypted-response-alg=Authorization Response Encryption Key Management Algorithm
|
||||
authorization-encrypted-response-alg.tooltip=JWA Algorithm used for key management in encrypting the authorization response when the response mode is jwt. This option is needed if you want encrypted authorization response. If left empty, the authorization response is just signed, but not encrypted.
|
||||
authorization-encrypted-response-enc=Authorization Response Encryption Content Encryption Algorithm
|
||||
authorization-encrypted-response-enc.tooltip=JWA Algorithm used for content encryption in encrypting the authorization response when the response mode is jwt. This option is needed if you want encrypted authorization response. If left empty, the authorization response is just signed, but not encrypted.
|
||||
|
||||
# client import
|
||||
import-client=Import Client
|
||||
|
|
|
@ -1292,6 +1292,9 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
$scope.idTokenSignedResponseAlg = $scope.client.attributes['id.token.signed.response.alg'];
|
||||
$scope.idTokenEncryptedResponseAlg = $scope.client.attributes['id.token.encrypted.response.alg'];
|
||||
$scope.idTokenEncryptedResponseEnc = $scope.client.attributes['id.token.encrypted.response.enc'];
|
||||
$scope.authorizationSignedResponseAlg = $scope.client.attributes['authorization.signed.response.alg'];
|
||||
$scope.authorizationEncryptedResponseAlg = $scope.client.attributes['authorization.encrypted.response.alg'];
|
||||
$scope.authorizationEncryptedResponseEnc = $scope.client.attributes['authorization.encrypted.response.enc'];
|
||||
|
||||
var attrVal1 = $scope.client.attributes['user.info.response.signature.alg'];
|
||||
$scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1;
|
||||
|
@ -1524,6 +1527,18 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
}
|
||||
};
|
||||
|
||||
$scope.changeAuthorizationSignedResponseAlg = function() {
|
||||
$scope.clientEdit.attributes['authorization.signed.response.alg'] = $scope.authorizationSignedResponseAlg;
|
||||
};
|
||||
|
||||
$scope.changeAuthorizationEncryptedResponseAlg = function() {
|
||||
$scope.clientEdit.attributes['authorization.encrypted.response.alg'] = $scope.authorizationEncryptedResponseAlg;
|
||||
};
|
||||
|
||||
$scope.changeAuthorizationEncryptedResponseEnc = function() {
|
||||
$scope.clientEdit.attributes['authorization.encrypted.response.enc'] = $scope.authorizationEncryptedResponseEnc;
|
||||
};
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
|
|
|
@ -605,6 +605,48 @@
|
|||
</div>
|
||||
<kc-tooltip>{{:: 'request-uris.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
|
||||
<label class="col-md-2 control-label" for="authorizationSignedResponseAlg">{{:: 'authorization-signed-response-alg' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<div>
|
||||
<select class="form-control" id="authorizationSignedResponseAlg"
|
||||
ng-change="changeAuthorizationSignedResponseAlg()"
|
||||
ng-model="authorizationSignedResponseAlg">
|
||||
<option value=""></option>
|
||||
<option ng-repeat="provider in serverInfo.listProviderIds('signature')" value="{{provider}}">{{provider}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'authorization-signed-response-alg.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label class="col-md-2 control-label" for="authorizationEncryptedResponseAlg">{{:: 'authorization-encrypted-response-alg' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<div>
|
||||
<select class="form-control" id="authorizationEncryptedResponseAlg"
|
||||
ng-change="changeAuthorizationEncryptedResponseAlg()"
|
||||
ng-model="authorizationEncryptedResponseAlg">
|
||||
<option value=""></option>
|
||||
<option ng-repeat="provider in serverInfo.listProviderIds('cekmanagement')" value="{{provider}}">{{provider}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'authorization-encrypted-response-alg.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label class="col-md-2 control-label" for="authorizationEncryptedResponseEnc">{{:: 'authorization-encrypted-response-enc' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<div>
|
||||
<select class="form-control" id="authorizationEncryptedResponseEnc"
|
||||
ng-change="changeAuthorizationEncryptedResponseEnc()"
|
||||
ng-model="authorizationEncryptedResponseEnc">
|
||||
<option value=""></option>
|
||||
<option ng-repeat="provider in serverInfo.listProviderIds('contentencryption')" value="{{provider}}">{{provider}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'authorization-encrypted-response-enc.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset data-ng-show="protocol == 'openid-connect'">
|
||||
|
|
Loading…
Reference in a new issue