/******************************************************************************* * Copyright 2017 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.openid.connect.token; import java.util.Date; import java.util.UUID; 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.oauth2.service.SystemScopeService; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.stereotype.Service; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTClaimsSet.Builder; import com.nimbusds.jwt.SignedJWT; @Service public class ConnectTokenEnhancer implements TokenEnhancer { /** * Logger for this class */ private static final Logger logger = LoggerFactory.getLogger(ConnectTokenEnhancer.class); @Autowired private ConfigurationPropertiesBean configBean; @Autowired private JWTSigningAndValidationService jwtService; @Autowired private ClientDetailsEntityService clientService; @Autowired private UserInfoService userInfoService; @Autowired private OIDCTokenService connectTokenService; @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) accessToken; OAuth2Request originalAuthRequest = authentication.getOAuth2Request(); String clientId = originalAuthRequest.getClientId(); ClientDetailsEntity client = clientService.loadClientByClientId(clientId); Builder builder = new JWTClaimsSet.Builder() .claim("azp", clientId) .issuer(configBean.getIssuer()) .issueTime(new Date()) .expirationTime(token.getExpiration()) .subject(authentication.getName()) .jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it String audience = (String) authentication.getOAuth2Request().getExtensions().get("aud"); if (!Strings.isNullOrEmpty(audience)) { builder.audience(Lists.newArrayList(audience)); } JWTClaimsSet claims = builder.build(); JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, jwtService.getDefaultSignerKeyId(), null, null); SignedJWT signed = new SignedJWT(header, claims); jwtService.signJwt(signed); token.setJwt(signed); /** * Authorization request scope MUST include "openid" in OIDC, but access token request * may or may not include the scope parameter. As long as the AuthorizationRequest * has the proper scope, we can consider this a valid OpenID Connect request. Otherwise, * we consider it to be a vanilla OAuth2 request. * * Also, there must be a user authentication involved in the request for it to be considered * OIDC and not OAuth, so we check for that as well. */ if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE) && !authentication.isClientOnly()) { String username = authentication.getName(); UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, clientId); if (userInfo != null) { JWT idToken = connectTokenService.createIdToken(client, originalAuthRequest, claims.getIssueTime(), userInfo.getSub(), token); // attach the id token to the parent access token token.setIdToken(idToken); } else { // can't create an id token if we can't find the user logger.warn("Request for ID token when no user is present."); } } return token; } public ConfigurationPropertiesBean getConfigBean() { return configBean; } public void setConfigBean(ConfigurationPropertiesBean configBean) { this.configBean = configBean; } public JWTSigningAndValidationService getJwtService() { return jwtService; } public void setJwtService(JWTSigningAndValidationService jwtService) { this.jwtService = jwtService; } public ClientDetailsEntityService getClientService() { return clientService; } public void setClientService(ClientDetailsEntityService clientService) { this.clientService = clientService; } }