added assertion processor to token endpoint
parent
8c021ad403
commit
bd9932d56f
|
@ -29,6 +29,8 @@
|
|||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- validate incoming tokens for JWT assertions -->
|
||||
<bean id="jwtAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
|
||||
|
||||
<!-- validate client software statements for dynamic registration -->
|
||||
<bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and 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.oauth2.assertion;
|
||||
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* Take in an assertion and token request and generate an OAuth2Request from it, including scopes and other important components
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public interface AssertionOAuth2RequestFactory {
|
||||
|
||||
/**
|
||||
* @param client
|
||||
* @param tokenRequest
|
||||
* @param assertion
|
||||
* @return
|
||||
*/
|
||||
OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion);
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and 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.oauth2.assertion.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
|
||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
|
||||
/**
|
||||
* Takes an assertion from a trusted source, looks for the fields:
|
||||
*
|
||||
* - scope, space-separated list of strings
|
||||
* - aud, array of audience IDs
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class DirectCopyRequestFactory implements AssertionOAuth2RequestFactory {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory#createOAuth2Request(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest, com.nimbusds.jwt.JWT)
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion) {
|
||||
|
||||
try {
|
||||
JWTClaimsSet claims = assertion.getJWTClaimsSet();
|
||||
Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim("scope"));
|
||||
|
||||
Set<String> resources = Sets.newHashSet(claims.getAudience());
|
||||
|
||||
return new OAuth2Request(tokenRequest.getRequestParameters(), client.getClientId(), client.getAuthorities(), true, scope, resources, null, null, null);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,9 @@ import java.text.ParseException;
|
|||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
|
@ -31,12 +33,14 @@ import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
|||
import org.mitre.oauth2.service.SystemScopeService;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
|
||||
|
@ -65,6 +69,13 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter {
|
|||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("jwtAssertionValidator")
|
||||
private AssertionValidator validator;
|
||||
|
||||
@Autowired
|
||||
private AssertionOAuth2RequestFactory assertionFactory;
|
||||
|
||||
@Autowired
|
||||
public JWTAssertionTokenGranter(OAuth2TokenEntityService tokenServices, ClientDetailsEntityService clientDetailsService, OAuth2RequestFactory requestFactory) {
|
||||
|
@ -76,129 +87,30 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter {
|
|||
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.AuthorizationRequest)
|
||||
*/
|
||||
@Override
|
||||
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
|
||||
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
|
||||
// read and load up the existing token
|
||||
String incomingTokenValue = tokenRequest.getRequestParameters().get("assertion");
|
||||
OAuth2AccessTokenEntity incomingToken = tokenServices.readAccessToken(incomingTokenValue);
|
||||
|
||||
if (incomingToken.getScope().contains(SystemScopeService.ID_TOKEN_SCOPE)) {
|
||||
|
||||
if (!client.getClientId().equals(tokenRequest.getClientId())) {
|
||||
throw new InvalidClientException("Not the right client for this token");
|
||||
try {
|
||||
String incomingAssertionValue = tokenRequest.getRequestParameters().get("assertion");
|
||||
JWT assertion = JWTParser.parse(incomingAssertionValue);
|
||||
|
||||
if (validator.isValid(assertion)) {
|
||||
|
||||
// our validator says it's OK, time to make a token from it
|
||||
// the real work happens in the assertion factory and the token services
|
||||
return new OAuth2Authentication(assertionFactory.createOAuth2Request(client, tokenRequest, assertion), null);
|
||||
|
||||
} else {
|
||||
logger.warn("Incoming assertion did not pass validator, rejecting");
|
||||
return null;
|
||||
}
|
||||
|
||||
// it's an ID token, process it accordingly
|
||||
|
||||
try {
|
||||
|
||||
// TODO: make this use a more specific idtoken class
|
||||
JWT idToken = JWTParser.parse(incomingTokenValue);
|
||||
|
||||
OAuth2AccessTokenEntity accessToken = tokenServices.getAccessTokenForIdToken(incomingToken);
|
||||
|
||||
if (accessToken != null) {
|
||||
|
||||
//OAuth2AccessTokenEntity newIdToken = tokenServices.get
|
||||
|
||||
OAuth2AccessTokenEntity newIdTokenEntity = new OAuth2AccessTokenEntity();
|
||||
|
||||
// copy over all existing claims
|
||||
JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(idToken.getJWTClaimsSet());
|
||||
|
||||
if (client instanceof ClientDetailsEntity) {
|
||||
|
||||
ClientDetailsEntity clientEntity = (ClientDetailsEntity) client;
|
||||
|
||||
// update expiration and issued-at claims
|
||||
if (clientEntity.getIdTokenValiditySeconds() != null) {
|
||||
Date expiration = new Date(System.currentTimeMillis() + (clientEntity.getIdTokenValiditySeconds() * 1000L));
|
||||
claims.expirationTime(expiration);
|
||||
newIdTokenEntity.setExpiration(expiration);
|
||||
}
|
||||
|
||||
} else {
|
||||
//This should never happen
|
||||
logger.fatal("SEVERE: Client is not an instance of OAuth2AccessTokenEntity.");
|
||||
throw new BadCredentialsException("SEVERE: Client is not an instance of ClientDetailsEntity; JwtAssertionTokenGranter cannot process this request.");
|
||||
}
|
||||
|
||||
claims.issueTime(new Date());
|
||||
claims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
|
||||
SignedJWT newIdToken = new SignedJWT((JWSHeader) idToken.getHeader(), claims.build());
|
||||
jwtService.signJwt(newIdToken);
|
||||
|
||||
newIdTokenEntity.setJwt(newIdToken);
|
||||
newIdTokenEntity.setAuthenticationHolder(incomingToken.getAuthenticationHolder());
|
||||
newIdTokenEntity.setScope(incomingToken.getScope());
|
||||
newIdTokenEntity.setClient(incomingToken.getClient());
|
||||
|
||||
newIdTokenEntity = tokenServices.saveAccessToken(newIdTokenEntity);
|
||||
|
||||
// attach the ID token to the access token entity
|
||||
accessToken.setIdToken(newIdTokenEntity);
|
||||
accessToken = tokenServices.saveAccessToken(accessToken);
|
||||
|
||||
// delete the old ID token
|
||||
tokenServices.revokeAccessToken(incomingToken);
|
||||
|
||||
return newIdTokenEntity;
|
||||
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Couldn't parse id token", e);
|
||||
}
|
||||
|
||||
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Unable to parse incoming assertion");
|
||||
}
|
||||
|
||||
// if we got down here, we didn't actually create any tokens, so return null
|
||||
|
||||
|
||||
// if we had made a token, we'd have returned it by now, so return null here to close out with no created token
|
||||
return null;
|
||||
|
||||
/*
|
||||
* Otherwise, process it like an access token assertion ... which we don't support yet so this is all commented out
|
||||
* /
|
||||
if (jwtService.validateSignature(incomingTokenValue)) {
|
||||
|
||||
Jwt jwt = Jwt.parse(incomingTokenValue);
|
||||
|
||||
|
||||
if (oldToken.getScope().contains("id-token")) {
|
||||
// TODO: things
|
||||
}
|
||||
|
||||
// TODO: should any of these throw an exception instead of returning null?
|
||||
JwtClaims claims = jwt.getClaims();
|
||||
if (!config.getIssuer().equals(claims.getIssuer())) {
|
||||
// issuer isn't us
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!authorizationRequest.getClientId().equals(claims.getAudience())) {
|
||||
// audience isn't the client
|
||||
return null;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (!now.after(claims.getExpiration())) {
|
||||
// token is expired
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// This doesn't work. We need to look up the old token, figure out its scopes and bind it appropriately.
|
||||
// In the case of an ID token, we need to look up its parent access token and change the reference, and revoke the old one, and
|
||||
// that's tricky.
|
||||
// we might need new calls on the token services layer to handle this, and we might
|
||||
// need to handle id tokens separately.
|
||||
return new OAuth2Authentication(authorizationRequest, null);
|
||||
|
||||
} else {
|
||||
return null; // throw error??
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue