Skip to content

Commit bd9932d

Browse files
committed
added assertion processor to token endpoint
1 parent 8c021ad commit bd9932d

File tree

4 files changed

+138
-118
lines changed

4 files changed

+138
-118
lines changed

openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
3030
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
3131

32+
<!-- validate incoming tokens for JWT assertions -->
33+
<bean id="jwtAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
3234

3335
<!-- validate client software statements for dynamic registration -->
3436
<bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*******************************************************************************
2+
* Copyright 2016 The MITRE Corporation
3+
* and the MIT Internet Trust Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*******************************************************************************/
17+
18+
package org.mitre.oauth2.assertion;
19+
20+
import org.springframework.security.oauth2.provider.ClientDetails;
21+
import org.springframework.security.oauth2.provider.OAuth2Request;
22+
import org.springframework.security.oauth2.provider.TokenRequest;
23+
24+
import com.nimbusds.jwt.JWT;
25+
26+
/**
27+
* Take in an assertion and token request and generate an OAuth2Request from it, including scopes and other important components
28+
*
29+
* @author jricher
30+
*
31+
*/
32+
public interface AssertionOAuth2RequestFactory {
33+
34+
/**
35+
* @param client
36+
* @param tokenRequest
37+
* @param assertion
38+
* @return
39+
*/
40+
OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion);
41+
42+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*******************************************************************************
2+
* Copyright 2016 The MITRE Corporation
3+
* and the MIT Internet Trust Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*******************************************************************************/
17+
18+
package org.mitre.oauth2.assertion.impl;
19+
20+
import java.text.ParseException;
21+
import java.util.List;
22+
import java.util.Set;
23+
24+
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
25+
import org.springframework.security.oauth2.common.util.OAuth2Utils;
26+
import org.springframework.security.oauth2.provider.ClientDetails;
27+
import org.springframework.security.oauth2.provider.OAuth2Request;
28+
import org.springframework.security.oauth2.provider.TokenRequest;
29+
30+
import com.google.common.collect.Sets;
31+
import com.nimbusds.jwt.JWT;
32+
import com.nimbusds.jwt.JWTClaimsSet;
33+
34+
/**
35+
* Takes an assertion from a trusted source, looks for the fields:
36+
*
37+
* - scope, space-separated list of strings
38+
* - aud, array of audience IDs
39+
*
40+
* @author jricher
41+
*
42+
*/
43+
public class DirectCopyRequestFactory implements AssertionOAuth2RequestFactory {
44+
45+
/* (non-Javadoc)
46+
* @see org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory#createOAuth2Request(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest, com.nimbusds.jwt.JWT)
47+
*/
48+
@Override
49+
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion) {
50+
51+
try {
52+
JWTClaimsSet claims = assertion.getJWTClaimsSet();
53+
Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim("scope"));
54+
55+
Set<String> resources = Sets.newHashSet(claims.getAudience());
56+
57+
return new OAuth2Request(tokenRequest.getRequestParameters(), client.getClientId(), client.getAuthorities(), true, scope, resources, null, null, null);
58+
} catch (ParseException e) {
59+
return null;
60+
}
61+
62+
}
63+
64+
}

openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java

Lines changed: 30 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,24 @@
2323
import java.util.Date;
2424
import java.util.UUID;
2525

26+
import org.mitre.jwt.assertion.AssertionValidator;
2627
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
28+
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
2729
import org.mitre.oauth2.model.ClientDetailsEntity;
2830
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
2931
import org.mitre.oauth2.service.ClientDetailsEntityService;
3032
import org.mitre.oauth2.service.OAuth2TokenEntityService;
3133
import org.mitre.oauth2.service.SystemScopeService;
3234
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
3335
import org.springframework.beans.factory.annotation.Autowired;
36+
import org.springframework.beans.factory.annotation.Qualifier;
3437
import org.springframework.security.authentication.BadCredentialsException;
3538
import org.springframework.security.core.AuthenticationException;
3639
import org.springframework.security.oauth2.common.OAuth2AccessToken;
3740
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
3841
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
3942
import org.springframework.security.oauth2.provider.ClientDetails;
43+
import org.springframework.security.oauth2.provider.OAuth2Authentication;
4044
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
4145
import org.springframework.security.oauth2.provider.TokenRequest;
4246
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
@@ -65,6 +69,13 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter {
6569

6670
@Autowired
6771
private ConfigurationPropertiesBean config;
72+
73+
@Autowired
74+
@Qualifier("jwtAssertionValidator")
75+
private AssertionValidator validator;
76+
77+
@Autowired
78+
private AssertionOAuth2RequestFactory assertionFactory;
6879

6980
@Autowired
7081
public JWTAssertionTokenGranter(OAuth2TokenEntityService tokenServices, ClientDetailsEntityService clientDetailsService, OAuth2RequestFactory requestFactory) {
@@ -76,129 +87,30 @@ public JWTAssertionTokenGranter(OAuth2TokenEntityService tokenServices, ClientDe
7687
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.AuthorizationRequest)
7788
*/
7889
@Override
79-
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
90+
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
8091
// read and load up the existing token
81-
String incomingTokenValue = tokenRequest.getRequestParameters().get("assertion");
82-
OAuth2AccessTokenEntity incomingToken = tokenServices.readAccessToken(incomingTokenValue);
83-
84-
if (incomingToken.getScope().contains(SystemScopeService.ID_TOKEN_SCOPE)) {
85-
86-
if (!client.getClientId().equals(tokenRequest.getClientId())) {
87-
throw new InvalidClientException("Not the right client for this token");
88-
}
89-
90-
// it's an ID token, process it accordingly
91-
92-
try {
93-
94-
// TODO: make this use a more specific idtoken class
95-
JWT idToken = JWTParser.parse(incomingTokenValue);
96-
97-
OAuth2AccessTokenEntity accessToken = tokenServices.getAccessTokenForIdToken(incomingToken);
98-
99-
if (accessToken != null) {
100-
101-
//OAuth2AccessTokenEntity newIdToken = tokenServices.get
102-
103-
OAuth2AccessTokenEntity newIdTokenEntity = new OAuth2AccessTokenEntity();
104-
105-
// copy over all existing claims
106-
JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(idToken.getJWTClaimsSet());
107-
108-
if (client instanceof ClientDetailsEntity) {
109-
110-
ClientDetailsEntity clientEntity = (ClientDetailsEntity) client;
111-
112-
// update expiration and issued-at claims
113-
if (clientEntity.getIdTokenValiditySeconds() != null) {
114-
Date expiration = new Date(System.currentTimeMillis() + (clientEntity.getIdTokenValiditySeconds() * 1000L));
115-
claims.expirationTime(expiration);
116-
newIdTokenEntity.setExpiration(expiration);
117-
}
118-
119-
} else {
120-
//This should never happen
121-
logger.fatal("SEVERE: Client is not an instance of OAuth2AccessTokenEntity.");
122-
throw new BadCredentialsException("SEVERE: Client is not an instance of ClientDetailsEntity; JwtAssertionTokenGranter cannot process this request.");
123-
}
124-
125-
claims.issueTime(new Date());
126-
claims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
127-
128-
129-
SignedJWT newIdToken = new SignedJWT((JWSHeader) idToken.getHeader(), claims.build());
130-
jwtService.signJwt(newIdToken);
131-
132-
newIdTokenEntity.setJwt(newIdToken);
133-
newIdTokenEntity.setAuthenticationHolder(incomingToken.getAuthenticationHolder());
134-
newIdTokenEntity.setScope(incomingToken.getScope());
135-
newIdTokenEntity.setClient(incomingToken.getClient());
136-
137-
newIdTokenEntity = tokenServices.saveAccessToken(newIdTokenEntity);
138-
139-
// attach the ID token to the access token entity
140-
accessToken.setIdToken(newIdTokenEntity);
141-
accessToken = tokenServices.saveAccessToken(accessToken);
142-
143-
// delete the old ID token
144-
tokenServices.revokeAccessToken(incomingToken);
145-
146-
return newIdTokenEntity;
147-
148-
}
149-
} catch (ParseException e) {
150-
logger.warn("Couldn't parse id token", e);
92+
try {
93+
String incomingAssertionValue = tokenRequest.getRequestParameters().get("assertion");
94+
JWT assertion = JWTParser.parse(incomingAssertionValue);
95+
96+
if (validator.isValid(assertion)) {
97+
98+
// our validator says it's OK, time to make a token from it
99+
// the real work happens in the assertion factory and the token services
100+
return new OAuth2Authentication(assertionFactory.createOAuth2Request(client, tokenRequest, assertion), null);
101+
102+
} else {
103+
logger.warn("Incoming assertion did not pass validator, rejecting");
104+
return null;
151105
}
152-
106+
107+
} catch (ParseException e) {
108+
logger.warn("Unable to parse incoming assertion");
153109
}
154-
155-
// if we got down here, we didn't actually create any tokens, so return null
156-
110+
111+
// if we had made a token, we'd have returned it by now, so return null here to close out with no created token
157112
return null;
158113

159-
/*
160-
* Otherwise, process it like an access token assertion ... which we don't support yet so this is all commented out
161-
* /
162-
if (jwtService.validateSignature(incomingTokenValue)) {
163-
164-
Jwt jwt = Jwt.parse(incomingTokenValue);
165-
166-
167-
if (oldToken.getScope().contains("id-token")) {
168-
// TODO: things
169-
}
170-
171-
// TODO: should any of these throw an exception instead of returning null?
172-
JwtClaims claims = jwt.getClaims();
173-
if (!config.getIssuer().equals(claims.getIssuer())) {
174-
// issuer isn't us
175-
return null;
176-
}
177-
178-
if (!authorizationRequest.getClientId().equals(claims.getAudience())) {
179-
// audience isn't the client
180-
return null;
181-
}
182-
183-
Date now = new Date();
184-
if (!now.after(claims.getExpiration())) {
185-
// token is expired
186-
return null;
187-
}
188-
189-
// FIXME
190-
// This doesn't work. We need to look up the old token, figure out its scopes and bind it appropriately.
191-
// 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
192-
// that's tricky.
193-
// we might need new calls on the token services layer to handle this, and we might
194-
// need to handle id tokens separately.
195-
return new OAuth2Authentication(authorizationRequest, null);
196-
197-
} else {
198-
return null; // throw error??
199-
}
200-
*/
201-
202114
}
203115

204116

0 commit comments

Comments
 (0)