Merge remote-tracking branch 'upstream/1.3.x' into 1.3.x
commit
1cc3b8f287
38
CHANGELOG.md
38
CHANGELOG.md
|
@ -1,22 +1,30 @@
|
|||
Unreleased:
|
||||
- Added changelog
|
||||
- Set default redirect URI resolver strict matching to true
|
||||
- Fixed XSS vulnerability on redirect URI display on approval page
|
||||
|
||||
*1.3.2:
|
||||
- Added changelog
|
||||
- Set default redirect URI resolver strict matching to true
|
||||
- Fixed XSS vulnerability on redirect URI display on approval page
|
||||
- Removed MITRE from copyright
|
||||
- Disallow unsigned JWTs on client authentication
|
||||
- Upgraded Nimbus revision
|
||||
- Added French translation
|
||||
- Added hooks for custom JWT claims
|
||||
- Removed "Not Yet Implemented" tag from post-logout redirect URI
|
||||
|
||||
*1.3.1*:
|
||||
- Added End Session endpoint
|
||||
- Fixed discovery endpoint
|
||||
- Downgrade MySQL connector dependency version from developer preview to GA release
|
||||
- Added End Session endpoint
|
||||
- Fixed discovery endpoint
|
||||
- Downgrade MySQL connector dependency version from developer preview to GA release
|
||||
|
||||
*1.3.0*:
|
||||
- Added device flow support
|
||||
- Added PKCE support
|
||||
- Modularized UI to allow better overlay and extensions
|
||||
- Modularized data import/export API
|
||||
- Added software statements to dynamic client registration
|
||||
- Added assertion processing framework
|
||||
- Removed ID tokens from storage
|
||||
- Removed structured scopes
|
||||
- Added device flow support
|
||||
- Added PKCE support
|
||||
- Modularized UI to allow better overlay and extensions
|
||||
- Modularized data import/export API
|
||||
- Added software statements to dynamic client registration
|
||||
- Added assertion processing framework
|
||||
- Removed ID tokens from storage
|
||||
- Removed structured scopes
|
||||
|
||||
*1.2.6*:
|
||||
- Added string HEART compliance mode
|
||||
- Added strict HEART compliance mode
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-client</artifactId>
|
||||
|
|
|
@ -92,7 +92,7 @@ public class TestSignedAuthRequestUrlBuilder {
|
|||
@Before
|
||||
public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null);
|
||||
RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null, null);
|
||||
Map<String, JWK> keys = Maps.newHashMap();
|
||||
keys.put("client", key);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-common</artifactId>
|
||||
|
|
|
@ -99,8 +99,10 @@ public class SymmetricKeyJWTValidatorCacheService {
|
|||
try {
|
||||
|
||||
String id = "SYMMETRIC-KEY";
|
||||
|
||||
JWK jwk = new OctetSequenceKey(Base64URL.encode(key), KeyUse.SIGNATURE, null, null, id, null, null, null, null);
|
||||
JWK jwk = new OctetSequenceKey.Builder(Base64URL.encode(key))
|
||||
.keyUse(KeyUse.SIGNATURE)
|
||||
.keyID(id)
|
||||
.build();
|
||||
Map<String, JWK> keys = ImmutableMap.of(id, jwk);
|
||||
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keys);
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ public class TestJWKSetKeyStore {
|
|||
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
|
||||
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
|
||||
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null);
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null);
|
||||
|
||||
private String RSAkid_rsa2 = "rsa_2";
|
||||
private JWK RSAjwk_rsa2 = new RSAKey(
|
||||
|
@ -78,7 +78,7 @@ public class TestJWKSetKeyStore {
|
|||
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
|
||||
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
|
||||
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_rsa2, null, null, null, null);
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_rsa2, null, null, null, null, null);
|
||||
|
||||
|
||||
List<JWK> keys_list = new LinkedList<>();
|
||||
|
|
|
@ -106,7 +106,7 @@ public class TestDefaultJWTEncryptionAndDecryptionService {
|
|||
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
|
||||
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
|
||||
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null);
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null);
|
||||
|
||||
private String RSAkid_2 = "rsa3210";
|
||||
private JWK RSAjwk_2 = new RSAKey(
|
||||
|
@ -123,12 +123,12 @@ public class TestDefaultJWTEncryptionAndDecryptionService {
|
|||
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
|
||||
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
|
||||
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_2, null, null, null, null);
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_2, null, null, null, null, null);
|
||||
|
||||
private String AESkid = "aes123";
|
||||
private JWK AESjwk = new OctetSequenceKey(new Base64URL("GawgguFyGrWKav7AX4VKUg"),
|
||||
KeyUse.ENCRYPTION, null, JWEAlgorithm.A128KW,
|
||||
AESkid, null, null, null, null);
|
||||
AESkid, null, null, null, null, null);
|
||||
|
||||
|
||||
private Map<String, JWK> keys = new ImmutableMap.Builder<String, JWK>()
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>openid-connect-server-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS access_token (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
token_value VARCHAR(4096),
|
||||
expiration TIMESTAMP,
|
||||
token_type VARCHAR(256),
|
||||
|
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS access_token_permissions (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS address (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
formatted VARCHAR(256),
|
||||
street_address VARCHAR(256),
|
||||
locality VARCHAR(256),
|
||||
|
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS address (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS approved_site (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(256),
|
||||
client_id VARCHAR(256),
|
||||
creation_date TIMESTAMP,
|
||||
|
@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS approved_site_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS authentication_holder (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_auth_id BIGINT,
|
||||
approved BOOLEAN,
|
||||
redirect_uri VARCHAR(2048),
|
||||
|
@ -85,7 +85,7 @@ CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saved_user_auth (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(1024),
|
||||
authenticated BOOLEAN,
|
||||
source_class VARCHAR(2048)
|
||||
|
@ -102,7 +102,7 @@ CREATE TABLE IF NOT EXISTS client_authority (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS authorization_code (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(256),
|
||||
auth_holder_id BIGINT,
|
||||
expiration TIMESTAMP
|
||||
|
@ -119,12 +119,12 @@ CREATE TABLE IF NOT EXISTS client_response_type (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blacklisted_site (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
uri VARCHAR(2048)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_details (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
||||
client_description VARCHAR(1024),
|
||||
reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL,
|
||||
|
@ -210,7 +210,7 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS refresh_token (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
token_value VARCHAR(4096),
|
||||
expiration TIMESTAMP,
|
||||
auth_holder_id BIGINT,
|
||||
|
@ -233,7 +233,7 @@ CREATE TABLE IF NOT EXISTS token_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_scope (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
scope VARCHAR(256) NOT NULL,
|
||||
description VARCHAR(4096),
|
||||
icon VARCHAR(256),
|
||||
|
@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS system_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_info (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
sub VARCHAR(256),
|
||||
preferred_username VARCHAR(256),
|
||||
name VARCHAR(256),
|
||||
|
@ -270,7 +270,7 @@ CREATE TABLE IF NOT EXISTS user_info (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS whitelisted_site (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
creator_user_id VARCHAR(256),
|
||||
client_id VARCHAR(256)
|
||||
);
|
||||
|
@ -281,14 +281,14 @@ CREATE TABLE IF NOT EXISTS whitelisted_site_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pairwise_identifier (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
identifier VARCHAR(256),
|
||||
sub VARCHAR(256),
|
||||
sector_identifier VARCHAR(2048)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_set (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(1024) NOT NULL,
|
||||
uri VARCHAR(1024),
|
||||
icon_uri VARCHAR(1024),
|
||||
|
@ -303,14 +303,14 @@ CREATE TABLE IF NOT EXISTS resource_set_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permission_ticket (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ticket VARCHAR(256) NOT NULL,
|
||||
permission_id BIGINT NOT NULL,
|
||||
expiration TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permission (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
resource_set_id BIGINT
|
||||
);
|
||||
|
||||
|
@ -320,7 +320,7 @@ CREATE TABLE IF NOT EXISTS permission_scope (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS claim (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(256),
|
||||
friendly_name VARCHAR(1024),
|
||||
claim_type VARCHAR(1024),
|
||||
|
@ -338,7 +338,7 @@ CREATE TABLE IF NOT EXISTS claim_to_permission_ticket (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS policy (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(1024),
|
||||
resource_set_id BIGINT
|
||||
);
|
||||
|
@ -359,13 +359,13 @@ CREATE TABLE IF NOT EXISTS claim_issuer (
|
|||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saved_registered_client (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
issuer VARCHAR(1024),
|
||||
registered_client VARCHAR(8192)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_code (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_code VARCHAR(1024),
|
||||
user_code VARCHAR(1024),
|
||||
expiration TIMESTAMP NULL,
|
||||
|
|
|
@ -65,6 +65,9 @@
|
|||
$(document).ready(function() {
|
||||
$('#stats').hide();
|
||||
var base = $('base').attr('href');
|
||||
if (base.substr(-1) !== '/') {
|
||||
base += '/';
|
||||
}
|
||||
|
||||
$.getJSON(base + 'api/stats/summary', function(data) {
|
||||
var stats = data;
|
||||
|
|
|
@ -548,6 +548,9 @@ $(function() {
|
|||
});
|
||||
|
||||
var base = $('base').attr('href');
|
||||
if (base.substr(-1) !== '/') {
|
||||
base += '/';
|
||||
}
|
||||
$.getJSON(base + '.well-known/openid-configuration', function(data) {
|
||||
app.serverConfiguration = data;
|
||||
var baseUrl = $.url(app.serverConfiguration.issuer);
|
||||
|
|
|
@ -211,7 +211,7 @@
|
|||
"no-clients": "There are no registered clients on this server.",
|
||||
"no-matches": "There are no clients that match your search criteria.",
|
||||
"no-redirect": "NO REDIRECT URI",
|
||||
"registered": "Registrered",
|
||||
"registered": "Registered",
|
||||
"search": "Search...",
|
||||
"whitelist": "Whitelist",
|
||||
"unknown": "at an unknown time"
|
||||
|
@ -325,7 +325,7 @@
|
|||
"confirm": "Are you sure sure you would like to delete this scope? Clients that have this scope will still be able to ask for it.",
|
||||
"new": "New Scope",
|
||||
"text": "There are no system scopes defined. Clients may still have custom scopes.",
|
||||
"tooltip-restricted": "This scope can be used only by adminisrtators. It is not available for dynamic registration.",
|
||||
"tooltip-restricted": "This scope can be used only by administrators. It is not available for dynamic registration.",
|
||||
"tooltip-default": "This scope is automatically assigned to newly registered clients."
|
||||
}
|
||||
},
|
||||
|
@ -337,7 +337,7 @@
|
|||
"associated-refresh": "This access token was issued with an associated refresh token.",
|
||||
"click-to-display": "Click to display full token value",
|
||||
"confirm": "Are you sure sure you would like to revoke this token?",
|
||||
"confirm-refresh": "Are you sure sure you would like to revoke this refresh token and its associated access tokens?",
|
||||
"confirm-refresh": "Are you sure you would like to revoke this refresh token and its associated access tokens?",
|
||||
"expires": "Expires",
|
||||
"no-access": "There are no active access tokens.",
|
||||
"no-refresh": "There are no active refresh tokens.",
|
||||
|
@ -522,7 +522,7 @@
|
|||
"title": "Logout requested",
|
||||
"header": "Logout Requested",
|
||||
"requested": "Logout has been requested by ",
|
||||
"explanation": "Do you wan to log out of the identity provider? This will not affect your session at any other systems.",
|
||||
"explanation": "Do you want to log out of the identity provider? This will not affect your session at any other systems.",
|
||||
"submit": "Log Out",
|
||||
"deny": "Stay Logged In"
|
||||
},
|
||||
|
|
|
@ -795,7 +795,7 @@
|
|||
</div>
|
||||
|
||||
<div class="control-group" id="postLogoutRedirectUris">
|
||||
<label class="control-label"><span class="label label-default nyi"><i class="icon-road icon-white"></i> NYI </span> <span data-i18n="client.client-form.post-logout">Post-Logout Redirect</span></label>
|
||||
<label class="control-label"><span data-i18n="client.client-form.post-logout">Post-Logout Redirect</span></label>
|
||||
<div class="controls">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<build>
|
||||
|
|
|
@ -91,15 +91,19 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
JWT jwt = jwtAuth.getJwt();
|
||||
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
|
||||
|
||||
if (!(jwt instanceof SignedJWT)) {
|
||||
throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName());
|
||||
}
|
||||
|
||||
// check the signature with nimbus
|
||||
if (jwt instanceof SignedJWT) {
|
||||
SignedJWT jws = (SignedJWT)jwt;
|
||||
SignedJWT jws = (SignedJWT) jwt;
|
||||
|
||||
JWSAlgorithm alg = jws.getHeader().getAlgorithm();
|
||||
|
||||
if (client.getTokenEndpointAuthSigningAlg() != null &&
|
||||
!client.getTokenEndpointAuthSigningAlg().equals(alg)) {
|
||||
throw new InvalidClientException("Client's registered request object signing algorithm (" + client.getRequestObjectSigningAlg() + ") does not match request object's actual algorithm (" + alg.getName() + ")");
|
||||
throw new AuthenticationServiceException("Client's registered token endpoint signing algorithm (" + client.getTokenEndpointAuthSigningAlg()
|
||||
+ ") does not match token's actual algorithm (" + alg.getName() + ")");
|
||||
}
|
||||
|
||||
if (client.getTokenEndpointAuthMethod() == null ||
|
||||
|
@ -142,7 +146,6 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
} else {
|
||||
throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
|
||||
}
|
||||
}
|
||||
|
||||
// check the issuer
|
||||
if (jwtClaims.getIssuer() == null) {
|
||||
|
|
|
@ -151,6 +151,8 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
idClaims.claim("at_hash", at_hash);
|
||||
}
|
||||
|
||||
addCustomIdTokenClaims(idClaims, client, request, sub, accessToken);
|
||||
|
||||
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
|
||||
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
|
||||
&& (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {
|
||||
|
@ -335,4 +337,18 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
this.authenticationHolderRepository = authenticationHolderRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses that allows adding custom claims to the JWT
|
||||
* that will be used as id token.
|
||||
* @param idClaims the builder holding the current claims
|
||||
* @param client information about the requesting client
|
||||
* @param request request that caused the id token to be created
|
||||
* @param sub subject auf the id token
|
||||
* @param accessToken the access token
|
||||
* @param authentication current authentication
|
||||
*/
|
||||
protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request,
|
||||
String sub, OAuth2AccessTokenEntity accessToken) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -92,6 +92,8 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
builder.audience(Lists.newArrayList(audience));
|
||||
}
|
||||
|
||||
addCustomAccessTokenClaims(builder, token, authentication);
|
||||
|
||||
JWTClaimsSet claims = builder.build();
|
||||
|
||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||
|
@ -161,4 +163,14 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hook for subclasses that allows adding custom claims to the JWT that will be used as access token.
|
||||
* @param builder the builder holding the current claims
|
||||
* @param token the un-enhanced token
|
||||
* @param authentication current authentication
|
||||
*/
|
||||
protected void addCustomAccessTokenClaims(JWTClaimsSet.Builder builder, OAuth2AccessTokenEntity token,
|
||||
OAuth2Authentication authentication) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
package org.mitre.openid.connect.assertion;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.nimbusds.jose.EncryptionMethod;
|
||||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWEHeader;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jwt.EncryptedJWT;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.PlainJWT;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TestJWTBearerAuthenticationProvider {
|
||||
|
||||
private static final String CLIENT_ID = "client";
|
||||
private static final String SUBJECT = "subject";
|
||||
|
||||
@Mock
|
||||
private ClientKeyCacheService validators;
|
||||
@Mock
|
||||
private ClientDetailsEntityService clientService;
|
||||
@Mock
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@InjectMocks
|
||||
private JWTBearerAuthenticationProvider jwtBearerAuthenticationProvider;
|
||||
|
||||
@Mock
|
||||
private JWTBearerAssertionAuthenticationToken token;
|
||||
@Mock
|
||||
private ClientDetailsEntity client;
|
||||
@Mock
|
||||
private JWTSigningAndValidationService validator;
|
||||
|
||||
private GrantedAuthority authority1 = new SimpleGrantedAuthority("1");
|
||||
private GrantedAuthority authority2 = new SimpleGrantedAuthority("2");
|
||||
private GrantedAuthority authority3 = new SimpleGrantedAuthority("3");
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(clientService.loadClientByClientId(CLIENT_ID)).thenReturn(client);
|
||||
|
||||
when(token.getName()).thenReturn(CLIENT_ID);
|
||||
|
||||
when(client.getClientId()).thenReturn(CLIENT_ID);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.NONE);
|
||||
when(client.getAuthorities()).thenReturn(ImmutableSet.of(authority1, authority2, authority3));
|
||||
|
||||
when(validators.getValidator(client, JWSAlgorithm.RS256)).thenReturn(validator);
|
||||
when(validator.validateSignature(any(SignedJWT.class))).thenReturn(true);
|
||||
|
||||
when(config.getIssuer()).thenReturn("http://issuer.com/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_not_support_UsernamePasswordAuthenticationToken() {
|
||||
assertThat(jwtBearerAuthenticationProvider.supports(UsernamePasswordAuthenticationToken.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_JWTBearerAssertionAuthenticationToken() {
|
||||
assertThat(jwtBearerAuthenticationProvider.supports(JWTBearerAssertionAuthenticationToken.class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_UsernameNotFoundException_when_clientService_throws_InvalidClientException() {
|
||||
when(clientService.loadClientByClientId(CLIENT_ID)).thenThrow(new InvalidClientException("invalid client"));
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(UsernameNotFoundException.class));
|
||||
assertThat(thrown.getMessage(), is("Could not find client: " + CLIENT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_PlainJWT() {
|
||||
mockPlainJWTAuthAttempt();
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Unsupported JWT type: " + PlainJWT.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_EncryptedJWT() {
|
||||
mockEncryptedJWTAuthAttempt();
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Unsupported JWT type: " + EncryptedJWT.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_signing_algorithms_do_not_match() {
|
||||
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256);
|
||||
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.ES384);
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Client's registered token endpoint signing algorithm (RS256) does not match token's actual algorithm (ES384)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_unsupported_authentication_method_for_SignedJWT() {
|
||||
List<AuthMethod> unsupportedAuthMethods =
|
||||
Arrays.asList(null, AuthMethod.NONE, AuthMethod.SECRET_BASIC, AuthMethod.SECRET_POST);
|
||||
|
||||
for (AuthMethod unsupportedAuthMethod : unsupportedAuthMethods) {
|
||||
SignedJWT signedJWT = createSignedJWT();
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(unsupportedAuthMethod);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Client does not support this authentication method."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_PRIVATE_KEY_auth_method() {
|
||||
List<JWSAlgorithm> invalidAlgorithms = Arrays.asList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512);
|
||||
|
||||
for (JWSAlgorithm algorithm : invalidAlgorithms) {
|
||||
SignedJWT signedJWT = createSignedJWT(algorithm);
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY);
|
||||
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_SECRET_JWT_auth_method() {
|
||||
List<JWSAlgorithm> invalidAlgorithms = Arrays.asList(
|
||||
JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512,
|
||||
JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512,
|
||||
JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512);
|
||||
|
||||
for (JWSAlgorithm algorithm : invalidAlgorithms) {
|
||||
SignedJWT signedJWT = createSignedJWT(algorithm);
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT);
|
||||
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_in_heart_mode_and_auth_method_is_not_PRIVATE_KEY() {
|
||||
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.HS256);
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.HS256);
|
||||
when(config.isHeartMode()).thenReturn(true);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("[HEART mode] Invalid authentication method"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_null_validator() {
|
||||
mockSignedJWTAuthAttempt();
|
||||
when(validators.getValidator(any(ClientDetailsEntity.class), any(JWSAlgorithm.class))).thenReturn(null);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_signature() {
|
||||
SignedJWT signedJWT = mockSignedJWTAuthAttempt();
|
||||
when(validator.validateSignature(signedJWT)).thenReturn(false);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Signature did not validate for presented JWT authentication."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_null_issuer() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(null).build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Assertion Token Issuer is null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_not_matching_issuer() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer("not matching").build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Issuers do not match"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_null_expiration_time() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(null).build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), is("Assertion Token does not have required expiration claim"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_expired_jwt() {
|
||||
Date expiredDate = new Date(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(500));
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(expiredDate).build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Assertion Token is expired"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_jwt_valid_in_future() {
|
||||
Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500));
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).notBeforeTime(futureDate).build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Assertion Token not valid until"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_jwt_issued_in_future() {
|
||||
Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500));
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).issueTime(futureDate).build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Assertion Token was issued in the future"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_AuthenticationServiceException_when_unmatching_audience() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(new Date()).audience("invalid").build();
|
||||
mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Throwable thrown = authenticateAndReturnThrownException();
|
||||
|
||||
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
|
||||
assertThat(thrown.getMessage(), startsWith("Audience does not match"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_valid_token_when_audience_contains_token_endpoint() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
|
||||
.issuer(CLIENT_ID)
|
||||
.subject(SUBJECT)
|
||||
.expirationTime(new Date())
|
||||
.audience(ImmutableList.of("http://issuer.com/token", "invalid"))
|
||||
.build();
|
||||
JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
|
||||
Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token);
|
||||
|
||||
assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class));
|
||||
|
||||
JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication;
|
||||
assertThat(token.getName(), is(SUBJECT));
|
||||
assertThat(token.getJwt(), is(jwt));
|
||||
assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3));
|
||||
assertThat(token.getAuthorities().size(), is(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_valid_token_when_issuer_does_not_end_with_slash_and_audience_contains_token_endpoint() {
|
||||
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
|
||||
.issuer(CLIENT_ID)
|
||||
.subject(SUBJECT)
|
||||
.expirationTime(new Date())
|
||||
.audience(ImmutableList.of("http://issuer.com/token"))
|
||||
.build();
|
||||
JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet);
|
||||
when(config.getIssuer()).thenReturn("http://issuer.com/");
|
||||
|
||||
Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token);
|
||||
|
||||
assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class));
|
||||
|
||||
JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication;
|
||||
assertThat(token.getName(), is(SUBJECT));
|
||||
assertThat(token.getJwt(), is(jwt));
|
||||
assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3));
|
||||
assertThat(token.getAuthorities().size(), is(4));
|
||||
}
|
||||
|
||||
private void mockPlainJWTAuthAttempt() {
|
||||
PlainJWT plainJWT = new PlainJWT(createJwtClaimsSet());
|
||||
when(token.getJwt()).thenReturn(plainJWT);
|
||||
}
|
||||
|
||||
private void mockEncryptedJWTAuthAttempt() {
|
||||
JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256GCM).build();
|
||||
EncryptedJWT encryptedJWT = new EncryptedJWT(jweHeader, createJwtClaimsSet());
|
||||
when(token.getJwt()).thenReturn(encryptedJWT);
|
||||
}
|
||||
|
||||
private SignedJWT mockSignedJWTAuthAttempt() {
|
||||
return mockSignedJWTAuthAttempt(createJwtClaimsSet());
|
||||
}
|
||||
|
||||
private SignedJWT mockSignedJWTAuthAttempt(JWTClaimsSet jwtClaimsSet) {
|
||||
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.RS256, jwtClaimsSet);
|
||||
when(token.getJwt()).thenReturn(signedJWT);
|
||||
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY);
|
||||
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256);
|
||||
return signedJWT;
|
||||
}
|
||||
|
||||
private Throwable authenticateAndReturnThrownException() {
|
||||
try {
|
||||
jwtBearerAuthenticationProvider.authenticate(token);
|
||||
} catch (Throwable throwable) {
|
||||
return throwable;
|
||||
}
|
||||
throw new AssertionError("No exception thrown when expected");
|
||||
}
|
||||
|
||||
private SignedJWT createSignedJWT() {
|
||||
return createSignedJWT(JWSAlgorithm.RS256);
|
||||
}
|
||||
|
||||
private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm) {
|
||||
JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build();
|
||||
JWTClaimsSet claims = createJwtClaimsSet();
|
||||
|
||||
return new SignedJWT(jwsHeader, claims);
|
||||
}
|
||||
|
||||
private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm, JWTClaimsSet jwtClaimsSet) {
|
||||
JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build();
|
||||
|
||||
return new SignedJWT(jwsHeader, jwtClaimsSet);
|
||||
}
|
||||
|
||||
private JWTClaimsSet createJwtClaimsSet() {
|
||||
return new JWTClaimsSet.Builder()
|
||||
.issuer(CLIENT_ID)
|
||||
.expirationTime(new Date())
|
||||
.audience("http://issuer.com/")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2018 The MIT Internet Trust Consortium
|
||||
*
|
||||
* 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.mitre.openid.connect.service.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TestDefaultOIDCTokenService {
|
||||
private static final String CLIENT_ID = "client";
|
||||
private static final String KEY_ID = "key";
|
||||
|
||||
private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean();
|
||||
private ClientDetailsEntity client = new ClientDetailsEntity();
|
||||
private OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity();
|
||||
private OAuth2Request request = new OAuth2Request(CLIENT_ID) { };
|
||||
|
||||
@Mock
|
||||
private JWTSigningAndValidationService jwtService;
|
||||
|
||||
@Before
|
||||
public void prepare() {
|
||||
configBean.setIssuer("https://auth.example.org/");
|
||||
|
||||
client.setClientId(CLIENT_ID);
|
||||
Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256);
|
||||
Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesCustomClaimsHook() throws java.text.ParseException {
|
||||
DefaultOIDCTokenService s = new DefaultOIDCTokenService() {
|
||||
@Override
|
||||
protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request,
|
||||
String sub, OAuth2AccessTokenEntity accessToken) {
|
||||
idClaims.claim("test", "foo");
|
||||
}
|
||||
};
|
||||
configure(s);
|
||||
|
||||
JWT token = s.createIdToken(client, request, new Date(), "sub", accessToken);
|
||||
Assert.assertEquals("foo", token.getJWTClaimsSet().getClaim("test"));
|
||||
}
|
||||
|
||||
|
||||
private void configure(DefaultOIDCTokenService s) {
|
||||
s.setConfigBean(configBean);
|
||||
s.setJwtService(jwtService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2017 The MIT Internet Trust Consortium
|
||||
*
|
||||
* Portions copyright 2011-2013 The MITRE Corporation
|
||||
*
|
||||
* 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.mitre.openid.connect.token;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.mitre.openid.connect.service.OIDCTokenService;
|
||||
import org.mitre.openid.connect.service.UserInfoService;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet.Builder;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TestConnectTokenEnhancer {
|
||||
|
||||
private static final String CLIENT_ID = "client";
|
||||
private static final String KEY_ID = "key";
|
||||
|
||||
private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean();
|
||||
|
||||
@Mock
|
||||
private JWTSigningAndValidationService jwtService;
|
||||
|
||||
@Mock
|
||||
private ClientDetailsEntityService clientService;
|
||||
|
||||
@Mock
|
||||
private UserInfoService userInfoService;
|
||||
|
||||
@Mock
|
||||
private OIDCTokenService connectTokenService;
|
||||
|
||||
@Mock
|
||||
private OAuth2Authentication authentication;
|
||||
|
||||
private OAuth2Request request = new OAuth2Request(CLIENT_ID) { };
|
||||
|
||||
@InjectMocks
|
||||
private ConnectTokenEnhancer enhancer = new ConnectTokenEnhancer();
|
||||
|
||||
@Before
|
||||
public void prepare() {
|
||||
configBean.setIssuer("https://auth.example.org/");
|
||||
enhancer.setConfigBean(configBean);
|
||||
|
||||
ClientDetailsEntity client = new ClientDetailsEntity();
|
||||
client.setClientId(CLIENT_ID);
|
||||
Mockito.when(clientService.loadClientByClientId(Mockito.anyString())).thenReturn(client);
|
||||
Mockito.when(authentication.getOAuth2Request()).thenReturn(request);
|
||||
Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256);
|
||||
Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesCustomClaimsHook() throws ParseException {
|
||||
configure(enhancer = new ConnectTokenEnhancer() {
|
||||
@Override
|
||||
protected void addCustomAccessTokenClaims(Builder builder, OAuth2AccessTokenEntity token,
|
||||
OAuth2Authentication authentication) {
|
||||
builder.claim("test", "foo");
|
||||
}
|
||||
});
|
||||
|
||||
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
|
||||
|
||||
OAuth2AccessTokenEntity enhanced = (OAuth2AccessTokenEntity) enhancer.enhance(token, authentication);
|
||||
Assert.assertEquals("foo", enhanced.getJwt().getJWTClaimsSet().getClaim("test"));
|
||||
}
|
||||
|
||||
private void configure(ConnectTokenEnhancer e) {
|
||||
e.setConfigBean(configBean);
|
||||
e.setJwtService(jwtService);
|
||||
e.setClientService(clientService);
|
||||
}
|
||||
}
|
6
pom.xml
6
pom.xml
|
@ -20,7 +20,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<name>MITREid Connect</name>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
|
@ -386,7 +386,7 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>4.2.2.RELEASE</version>
|
||||
<version>4.2.4.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -585,7 +585,7 @@
|
|||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
<version>4.34.2</version>
|
||||
<version>5.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server-webapp</artifactId>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.3.2-SNAPSHOT</version>
|
||||
<version>1.3.3-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server</artifactId>
|
||||
|
|
Loading…
Reference in New Issue