Backward compatibility for lower-case bearer type in token responses (#9538)

Closes #9537
This commit is contained in:
Pedro Igor 2022-01-12 23:34:45 -08:00 committed by GitHub
parent 2fd1593abf
commit 4c747047ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 2 deletions

View file

@ -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

View file

@ -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";

View file

@ -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;

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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>