Backward compatibility for lower-case bearer type in token responses (#9538)
Closes #9537
This commit is contained in:
parent
2fd1593abf
commit
4c747047ce
7 changed files with 67 additions and 2 deletions
|
@ -17,10 +17,11 @@
|
|||
|
||||
package org.keycloak.protocol.oidc;
|
||||
|
||||
import static org.keycloak.protocol.oidc.OIDCConfigAttributes.USE_LOWER_CASE_IN_TOKEN_RESPONSE;
|
||||
|
||||
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
@ -177,6 +178,14 @@ public class OIDCAdvancedConfigWrapper {
|
|||
setAttribute(OIDCConfigAttributes.USE_REFRESH_TOKEN, val);
|
||||
}
|
||||
|
||||
public boolean isUseLowerCaseInTokenResponse() {
|
||||
return Boolean.parseBoolean(getAttribute(USE_LOWER_CASE_IN_TOKEN_RESPONSE, "false"));
|
||||
}
|
||||
|
||||
public void setUseLowerCaseInTokenResponse(boolean useRefreshToken) {
|
||||
setAttribute(USE_LOWER_CASE_IN_TOKEN_RESPONSE, String.valueOf(useRefreshToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, then Client Credentials Grant generates refresh token and creates user session. This is not per specs, so it is false by default
|
||||
* For the details @see https://tools.ietf.org/html/rfc6749#section-4.4.3
|
||||
|
|
|
@ -70,6 +70,8 @@ public final class OIDCConfigAttributes {
|
|||
|
||||
public static final String USE_REFRESH_TOKEN = "use.refresh.tokens";
|
||||
|
||||
public static final String USE_LOWER_CASE_IN_TOKEN_RESPONSE = "token.response.type.bearer.lower-case";
|
||||
|
||||
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";
|
||||
|
|
|
@ -1178,7 +1178,7 @@ public class TokenManager {
|
|||
if (accessToken != null) {
|
||||
String encodedToken = session.tokens().encode(accessToken);
|
||||
res.setToken(encodedToken);
|
||||
res.setTokenType(TokenUtil.TOKEN_TYPE_BEARER);
|
||||
res.setTokenType(formatTokenType(client));
|
||||
res.setSessionState(accessToken.getSessionState());
|
||||
if (accessToken.getExpiration() != 0) {
|
||||
res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
|
||||
|
@ -1239,6 +1239,13 @@ public class TokenManager {
|
|||
|
||||
}
|
||||
|
||||
private String formatTokenType(ClientModel client) {
|
||||
if (OIDCAdvancedConfigWrapper.fromClientModel(client).isUseLowerCaseInTokenResponse()) {
|
||||
return TokenUtil.TOKEN_TYPE_BEARER.toLowerCase();
|
||||
}
|
||||
return TokenUtil.TOKEN_TYPE_BEARER;
|
||||
}
|
||||
|
||||
public static class RefreshResult {
|
||||
|
||||
private final AccessTokenResponse response;
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientScopeResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
|
@ -51,6 +52,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
|
||||
|
@ -114,6 +116,7 @@ import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMappe
|
|||
import static org.keycloak.testsuite.Assert.assertExpiration;
|
||||
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
/**
|
||||
|
@ -254,6 +257,28 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenResponseUsingLowerCaseType() throws Exception {
|
||||
ClientsResource clients = realmsResouce().realm("test").clients();
|
||||
ClientRepresentation client = clients.findByClientId(oauth.getClientId()).get(0);
|
||||
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseLowerCaseInTokenResponse(true);
|
||||
|
||||
clients.get(client.getId()).update(client);
|
||||
|
||||
try {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
assertEquals(TokenUtil.TOKEN_TYPE_BEARER.toLowerCase(), response.getTokenType());
|
||||
} finally {
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseLowerCaseInTokenResponse(false);
|
||||
clients.get(client.getId()).update(client);
|
||||
}
|
||||
}
|
||||
|
||||
// KEYCLOAK-3692
|
||||
@Test
|
||||
public void accessTokenWrongCode() throws Exception {
|
||||
|
|
|
@ -446,6 +446,8 @@ 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.
|
||||
use-lower-case-bearer-in-token-responses=Use lower-case bearer type in token responses
|
||||
use-lower-case-bearer-in-token-responses.tooltip=If this is on, token responses will be set the with the type "bearer" in lower-case. By default, the server sets the type as "Bearer" as defined by RFC6750.
|
||||
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
|
||||
|
|
|
@ -1439,6 +1439,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
$scope.useRefreshTokenForClientCredentialsGrant = false;
|
||||
}
|
||||
|
||||
var useLowerCaseBearerTypeInTokenResponse = $scope.client.attributes["token.response.type.bearer.lower-case"];
|
||||
if (useLowerCaseBearerTypeInTokenResponse === "true") {
|
||||
$scope.useLowerCaseBearerTypeInTokenResponse = true;
|
||||
} else {
|
||||
$scope.useLowerCaseBearerTypeInTokenResponse = false;
|
||||
}
|
||||
|
||||
if ($scope.client.attributes["display.on.consent.screen"]) {
|
||||
if ($scope.client.attributes["display.on.consent.screen"] == "true") {
|
||||
$scope.displayOnConsentScreen = true;
|
||||
|
@ -1955,6 +1962,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
|
|||
$scope.clientEdit.attributes["client_credentials.use_refresh_token"] = "false";
|
||||
}
|
||||
|
||||
if ($scope.useLowerCaseBearerTypeInTokenResponse === true) {
|
||||
$scope.clientEdit.attributes["token.response.type.bearer.lower-case"] = "true";
|
||||
} else {
|
||||
$scope.clientEdit.attributes["token.response.type.bearer.lower-case"] = "false";
|
||||
}
|
||||
|
||||
if ($scope.displayOnConsentScreen == true) {
|
||||
$scope.clientEdit.attributes["display.on.consent.screen"] = "true";
|
||||
} else {
|
||||
|
|
|
@ -750,6 +750,13 @@
|
|||
</div>
|
||||
<kc-tooltip>{{:: 'use-refresh-token-for-client-credentials-grant.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="useLowerCaseBearerTypeInTokenResponse">{{:: 'use-lower-case-bearer-in-token-responses' | translate}}</label>
|
||||
<div class="col-md-6">
|
||||
<input ng-model="useLowerCaseBearerTypeInTokenResponse" ng-click="switchChange()" name="useLowerCaseBearerTypeInTokenResponse" id="useLowerCaseBearerTypeInTokenResponse" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'use-lower-case-bearer-in-token-responses.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
|
|
Loading…
Reference in a new issue