Commit 41bb5599 authored by john's avatar john Committed by 陈健

OAUTH-3147 review comments, property naming change

added lombok to simplify pojos
Use WellKnownConfig object in more places
parent f199cde9
......@@ -38,20 +38,20 @@ choosing an encryption method in [OpenID Connect configuration](https://docs.one
Modify `application.properties` in _/src/main/resources_ or use one of the mechanisms Spring Boot supports to [override property values](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html).
The following properties must be set:
* onegini.oauth2.clientId: the client identifier of the Web client that supports OpenID Connect
* onegini.oauth2.clientSecret: the client secret of the Web client that supports OpenID Connect
* onegini.oauth2.issuer: the base URL of the Token Server instance
* onegini.oidc.clientId: the client identifier of the Web client that supports OpenID Connect
* onegini.oidc.clientSecret: the client secret of the Web client that supports OpenID Connect
* onegini.oidc.issuer: the base URL of the Token Server instance
Optional properties:
* id-token-encryption.enabled: boolean for enabling ID token encryption. This should match the server side configuration
* onegini.oidc.idTokenEncryptionEnabled: boolean for enabling ID token encryption. This should match the server side configuration
___Example configuration___
```
onegini.oauth2.clientId=openid-client
onegini.oauth2.clientSecret=secret
onegini.oauth2.issuer=http://localhost:7878/oauth
id-token-encryption.enabled=true
onegini.oidc.clientId=openid-client
onegini.oidc.clientSecret=secret
onegini.oidc.issuer=http://localhost:7878/oauth
onegini.oidc.idTokenEncryptionEnabled=true
```
## Run and test
......@@ -114,7 +114,7 @@ This should align with your key rotation strategy. It also validates that the ke
exposed by the [OpenID Provider Metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata) This example generates keys every time
the application is started and stores them in memory. In a production situation, keys should be persisted in some way and proper key rotation followed. See
[JSON Web Key (JWK) RFC-7517](https://tools.ietf.org/html/rfc7517) for more information. This controller is only exposed when the property
`id-token-encryption.enabled` is set to `true`. If your client is not configured for encryption, there is no need for this controller.
`onegini.oidc.idTokenEncryptionEnabled` is set to `true`. If your client is not configured for encryption, there is no need for this controller.
### JweKeyGenerator
The [JweKeyGenerator.java](src/main/java/com/onegini/oidc/encryption/JweKeyGenerator.java) is responsible for key generation. It shows how to generate the RSA
......@@ -127,4 +127,6 @@ environment it should be modified to grab the keys from where they have been sto
### JweDecrypterService
The [JweDecrypterService.java](src/main/java/com/onegini/oidc/encryption/JweDecrypterService.java) does the decryption of the ID token. The `decrypt`
method consumes the encrypted JWT and tries to decrypt it by finding the relevant key. It then passes that key with the encrypted JWT to `nimbusds-jose-jwt`
library which decrypts it and returns the Signed JWT.
\ No newline at end of file
library which decrypts it and returns the Signed JWT.
## Troubleshooting
\ No newline at end of file
......@@ -20,52 +20,48 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>5.63</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
......
......@@ -19,7 +19,7 @@ import com.onegini.oidc.model.OpenIdWellKnownConfiguration;
import net.minidev.json.JSONObject;
@RestController
@ConditionalOnProperty(value = "id-token-encryption.enabled", havingValue = "true")
@ConditionalOnProperty(value = "onegini.oidc.idTokenEncryptionEnabled", havingValue = "true")
public class JweWellKnownJwksController {
private static final String JWKS_KEYS_PATH = "/.well-known/jwks.json";
private static final JWEAlgorithm ASYMMETRIC_ENCRYPTION_ALGORITHM = ECDH_ES;
......
......@@ -3,14 +3,11 @@ package com.onegini.oidc;
import static org.springframework.web.servlet.view.UrlBasedViewResolver.REDIRECT_URL_PREFIX;
import java.security.Principal;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
......@@ -20,29 +17,24 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import com.onegini.oidc.config.ApplicationProperties;
import com.onegini.oidc.model.OpenIdWellKnownConfiguration;
import com.onegini.oidc.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j
public class LogoutController {
public static final String PAGE_LOGOUT = "/logout";
private static final Logger LOG = LoggerFactory.getLogger(LogoutController.class);
@SuppressWarnings("squid:S1075")
private static final String WELL_KNOWN_CONFIG_PATH = "/.well-known/openid-configuration";
private static final String KEY_END_SESSION_ENDPOINT = "end_session_endpoint";
private static final String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri";
private static final String PARAM_ID_TOKEN_HINT = "id_token_hint";
private static final String PAGE_SIGNOUT_CALLBACK_OIDC = "/signout-callback-oidc";
private static final String REDIRECT_TO_INDEX = "redirect:/";
@Resource
private ApplicationProperties applicationProperties;
@Resource
private RestTemplate restTemplate;
private OpenIdWellKnownConfiguration openIdWellKnownConfiguration;
@GetMapping(PAGE_LOGOUT)
private String logout(final HttpServletRequest request, final HttpServletResponse response, final Principal principal) {
......@@ -52,11 +44,8 @@ public class LogoutController {
endSessionInSpringSecurity(request, response);
if (userInfo != null && StringUtils.hasLength(userInfo.getIdToken())) {
LOG.info("Has idToken {}", userInfo.getIdToken());
final Map configuration = restTemplate.getForObject(applicationProperties.getIssuer() + WELL_KNOWN_CONFIG_PATH, Map.class);
@SuppressWarnings("squid:S2583") final String endSessionEndpoint = configuration == null ? null : (String) configuration.get(KEY_END_SESSION_ENDPOINT);
log.info("Has idToken {}", userInfo.getIdToken());
final String endSessionEndpoint = openIdWellKnownConfiguration.getEndSessionEndpoint();
if (StringUtils.hasLength(endSessionEndpoint)) {
return endOpenIdSession(userInfo, endSessionEndpoint);
}
......@@ -67,7 +56,7 @@ public class LogoutController {
@GetMapping(PAGE_SIGNOUT_CALLBACK_OIDC)
public String callbackOidc() {
LOG.info("Signout callback from OP");
log.info("Signout callback from OP");
return REDIRECT_TO_INDEX;
}
......@@ -82,7 +71,7 @@ public class LogoutController {
private void endSessionInSpringSecurity(final HttpServletRequest request, final HttpServletResponse response) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
LOG.info("End user session in Spring Security");
log.info("End user session in Spring Security");
new SecurityContextLogoutHandler().logout(request, response, auth);
}
}
......@@ -101,7 +90,7 @@ public class LogoutController {
.queryParams(requestParameters)
.build().toUriString();
LOG.info("Redirect to OP end session");
log.info("Redirect to OP end session");
return REDIRECT_URL_PREFIX + redirectUri;
}
......
......@@ -6,39 +6,20 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import lombok.Getter;
import lombok.Setter;
@Validated
@Configuration
@ConfigurationProperties(prefix = "onegini.oauth2")
@ConfigurationProperties(prefix = "onegini.oidc")
@Getter
@Setter
public class ApplicationProperties {
@NotBlank
private String clientId;
@NotBlank
private String clientSecret;
@NotBlank
private String issuer;
public String getClientId() {
return clientId;
}
public void setClientId(final String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(final String clientSecret) {
this.clientSecret = clientSecret;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(final String issuer) {
this.issuer = issuer;
}
private boolean idTokenEncryptionEnabled;
}
\ No newline at end of file
......@@ -4,8 +4,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.nimbusds.jose.JOSEException;
......@@ -19,10 +17,11 @@ import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWT;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class JweDecrypterService {
private static final Logger LOG = LoggerFactory.getLogger(JweDecrypterService.class);
@Resource
private JwkSetProvider jwkSetProvider;
......@@ -37,8 +36,8 @@ public class JweDecrypterService {
jweObject.decrypt(decrypter);
return jweObject.getPayload().toSignedJWT();
} catch (JOSEException e) {
LOG.error("Could not decrypt the JWT");
throw new IllegalStateException("Could not decrypt the JWT");
log.error("Could not decrypt the JWT");
throw new IllegalStateException("Could not decrypt the JWT", e);
}
}
......@@ -53,11 +52,10 @@ public class JweDecrypterService {
final JWK relevantKey = privateJWKS.getKeyByKeyId(jweObject.getHeader().getKeyID());
if (relevantKey != null) {
return relevantKey;
} else {
//The Server may have cached the JWKSet response and when this app was restarted, it generated new keys which would not match
LOG.error("Could not match the keyId with any of the private keys provided.");
throw new IllegalArgumentException("JWK set does not contain a relevant JWK.");
}
//The Server may have cached the JWKSet response and when this app was restarted, it generated new keys which would not match
log.error("Could not match the keyId with any of the private keys provided.");
throw new IllegalArgumentException("JWK set does not contain a relevant JWK.");
}
private JWEDecrypter getDecrypter(final JWK jwk) {
......@@ -67,9 +65,8 @@ public class JweDecrypterService {
return new RSADecrypter((RSAKey) jwk);
} else if (KeyType.EC.equals(keyType)) {
return new ECDHDecrypter((ECKey) jwk);
} else {
throw new IllegalStateException(String.format("Unsupported KeyType (%s)", jwk.getKeyType()));
}
throw new IllegalStateException(String.format("Unsupported KeyType (%s)", jwk.getKeyType()));
} catch (final JOSEException e) {
final String msg = String.format("Could not create the JWE decrypter for type (%s).", keyType);
throw new IllegalStateException(msg, e);
......
......@@ -15,20 +15,18 @@ import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.onegini.oidc.JweWellKnownJwksController;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
class JweKeyGenerator {
private static final Logger LOG = LoggerFactory.getLogger(JweWellKnownJwksController.class);
private static final int RSA_KEYSIZE = 2048;
JWK generateKey(final JWEAlgorithm jweAlgorithm) {
......@@ -37,7 +35,7 @@ class JweKeyGenerator {
} else if (JWEAlgorithm.Family.ECDH_ES.contains(jweAlgorithm)) {
return generateECKey(jweAlgorithm);
} else {
LOG.error("Unsupported Algorithm ({})", jweAlgorithm);
log.error("Unsupported Algorithm ({})", jweAlgorithm);
return null;
}
}
......@@ -55,7 +53,7 @@ class JweKeyGenerator {
.algorithm(jweAlgorithm)
.build();
} catch (final NoSuchAlgorithmException e) {
LOG.error("Generating a RSA key failed.", e);
log.error("Generating a RSA key failed.", e);
}
return null;
}
......@@ -73,7 +71,7 @@ class JweKeyGenerator {
.algorithm(jweAlgorithm)
.build();
} catch (final NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
LOG.error("Generating a EC key failed.", e);
log.error("Generating a EC key failed.", e);
}
return null;
}
......
......@@ -2,12 +2,15 @@ package com.onegini.oidc.model;
import java.util.Collection;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
public class OpenIdWellKnownConfiguration {
public OpenIdWellKnownConfiguration() {
//nothing
}
@Data
@EqualsAndHashCode
@NoArgsConstructor
public class OpenIdWellKnownConfiguration {
private String issuer;
private String authorizationEndpoint;
......@@ -26,147 +29,4 @@ public class OpenIdWellKnownConfiguration {
private Collection<String> idTokenEncryptionAlgValuesSupported;
private Collection<String> idTokenEncryptionEncValuesSupported;
public String getIssuer() {
return issuer;
}
public OpenIdWellKnownConfiguration setIssuer(final String issuer) {
this.issuer = issuer;
return this;
}
public String getAuthorizationEndpoint() {
return authorizationEndpoint;
}
public OpenIdWellKnownConfiguration setAuthorizationEndpoint(final String authorizationEndpoint) {
this.authorizationEndpoint = authorizationEndpoint;
return this;
}
public String getTokenEndpoint() {
return tokenEndpoint;
}
public OpenIdWellKnownConfiguration setTokenEndpoint(final String tokenEndpoint) {
this.tokenEndpoint = tokenEndpoint;
return this;
}
public String getJwksUri() {
return jwksUri;
}
public OpenIdWellKnownConfiguration setJwksUri(final String jwksUri) {
this.jwksUri = jwksUri;
return this;
}
public String getUserinfoEndpoint() {
return userinfoEndpoint;
}
public OpenIdWellKnownConfiguration setUserinfoEndpoint(final String userinfoEndpoint) {
this.userinfoEndpoint = userinfoEndpoint;
return this;
}
public Collection<String> getResponseTypesSupported() {
return responseTypesSupported;
}
public OpenIdWellKnownConfiguration setResponseTypesSupported(final Collection<String> responseTypesSupported) {
this.responseTypesSupported = responseTypesSupported;
return this;
}
public Collection<String> getSubjectTypesSupported() {
return subjectTypesSupported;
}
public OpenIdWellKnownConfiguration setSubjectTypesSupported(final Collection<String> subjectTypesSupported) {
this.subjectTypesSupported = subjectTypesSupported;
return this;
}
public Collection<String> getIdTokenSigningAlgValues() {
return idTokenSigningAlgValues;
}
public OpenIdWellKnownConfiguration setIdTokenSigningAlgValues(final Collection<String> idTokenSigningAlgValues) {
this.idTokenSigningAlgValues = idTokenSigningAlgValues;
return this;
}
public Collection<String> getScopesSupported() {
return scopesSupported;
}
public OpenIdWellKnownConfiguration setScopesSupported(final Collection<String> scopesSupported) {
this.scopesSupported = scopesSupported;
return this;
}
public Collection<String> getClaimsSupported() {
return claimsSupported;
}
public OpenIdWellKnownConfiguration setClaimsSupported(final Collection<String> claimsSupported) {
this.claimsSupported = claimsSupported;
return this;
}
public String getCheckSessionIframe() {
return checkSessionIframe;
}
public OpenIdWellKnownConfiguration setCheckSessionIframe(final String checkSessionIframe) {
this.checkSessionIframe = checkSessionIframe;
return this;
}
public String getEndSessionEndpoint() {
return endSessionEndpoint;
}
public OpenIdWellKnownConfiguration setEndSessionEndpoint(final String endSessionEndpoint) {
this.endSessionEndpoint = endSessionEndpoint;
return this;
}
public boolean isFrontchannelLogoutSupported() {
return frontchannelLogoutSupported;
}
public OpenIdWellKnownConfiguration setFrontchannelLogoutSupported(final boolean frontchannelLogoutSupported) {
this.frontchannelLogoutSupported = frontchannelLogoutSupported;
return this;
}
public boolean isFrontchannelLogoutSessionSupported() {
return frontchannelLogoutSessionSupported;
}
public OpenIdWellKnownConfiguration setFrontchannelLogoutSessionSupported(final boolean frontchannelLogoutSessionSupported) {
this.frontchannelLogoutSessionSupported = frontchannelLogoutSessionSupported;
return this;
}
public Collection<String> getIdTokenEncryptionAlgValuesSupported() {
return idTokenEncryptionAlgValuesSupported;
}
public OpenIdWellKnownConfiguration setIdTokenEncryptionAlgValuesSupported(final Collection<String> idTokenEncryptionAlgValuesSupported) {
this.idTokenEncryptionAlgValuesSupported = idTokenEncryptionAlgValuesSupported;
return this;
}
public Collection<String> getIdTokenEncryptionEncValuesSupported() {
return idTokenEncryptionEncValuesSupported;
}
public OpenIdWellKnownConfiguration setIdTokenEncryptionEncValuesSupported(final Collection<String> idTokenEncryptionEncValuesSupported) {
this.idTokenEncryptionEncValuesSupported = idTokenEncryptionEncValuesSupported;
return this;
}
}
\ No newline at end of file
package com.onegini.oidc.model;
import com.nimbusds.jwt.JWTClaimsSet;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class TokenDetails {
private final JWTClaimsSet jwtClaimsSet;
public TokenDetails(final JWTClaimsSet jwtClaimsSet) {
this.jwtClaimsSet = jwtClaimsSet;
}
public JWTClaimsSet getJwtClaimsSet() {
return jwtClaimsSet;
}
}
\ No newline at end of file
package com.onegini.oidc.model;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class UserInfo {
private final String id;
......@@ -7,30 +14,7 @@ public class UserInfo {
private final String idToken;
private final String encryptedIdToken;
public UserInfo(final String id, final String name, final String idToken, final String encryptedIdToken) {
this.id = id;
this.name = name;
this.idToken = idToken;
this.encryptedIdToken = encryptedIdToken;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getIdToken() {
return idToken;
}
public String getEncryptedIdToken() {
return encryptedIdToken;
}
public boolean isEncryptionEnabled() {
return encryptedIdToken != null;
return isNotBlank(encryptedIdToken);
}
}
\ No newline at end of file
......@@ -6,7 +6,6 @@ import java.util.Arrays;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
......@@ -19,6 +18,7 @@ import org.springframework.security.oauth2.client.token.grant.code.Authorization
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.web.client.RestTemplate;
import com.onegini.oidc.config.ApplicationProperties;
import com.onegini.oidc.model.OpenIdWellKnownConfiguration;
@Configuration
......@@ -27,15 +27,8 @@ public class OAuth2Client {
private static final String WELL_KNOWN_CONFIG_PATH = "/.well-known/openid-configuration";
@Value("${onegini.oauth2.clientId}")
private String clientId;
@Value("${onegini.oauth2.clientSecret}")
private String clientSecret;
@Value("${onegini.oauth2.issuer}")
private String issuer;
@Resource
private ApplicationProperties applicationProperties;
@SuppressWarnings("SpringJavaAutowiringInspection") // Provided by Spring Boot
@Resource
private OAuth2ClientContext oAuth2ClientContext;
......@@ -44,7 +37,7 @@ public class OAuth2Client {
@Bean
public OpenIdWellKnownConfiguration getOpenIdWellKnownConfiguration() {
return restTemplate.getForObject(issuer + WELL_KNOWN_CONFIG_PATH, OpenIdWellKnownConfiguration.class);
return restTemplate.getForObject(applicationProperties.getIssuer() + WELL_KNOWN_CONFIG_PATH, OpenIdWellKnownConfiguration.class);
}
@Bean
......@@ -54,8 +47,8 @@ public class OAuth2Client {
final AuthorizationCodeResourceDetails conf = new AuthorizationCodeResourceDetails();
conf.setAuthenticationScheme(header);
conf.setClientAuthenticationScheme(header);
conf.setClientId(clientId);
conf.setClientSecret(clientSecret);
conf.setClientId(applicationProperties.getClientId());
conf.setClientSecret(applicationProperties.getClientSecret());
conf.setUserAuthorizationUri(configuration.getAuthorizationEndpoint());
conf.setAccessTokenUri(configuration.getTokenEndpoint());
conf.setScope(Arrays.asList("openid", "profile"));
......
......@@ -9,9 +9,6 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
......@@ -21,19 +18,23 @@ import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
import com.onegini.oidc.config.ApplicationProperties;
import com.onegini.oidc.encryption.JweDecrypterService;
import com.onegini.oidc.model.TokenDetails;
import com.onegini.oidc.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class OpenIdConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOG = LoggerFactory.getLogger(OpenIdConnectAuthenticationFilter.class);
@Resource
private OAuth2RestOperations oAuth2RestOperations;
......@@ -43,8 +44,8 @@ public class OpenIdConnectAuthenticationFilter extends AbstractAuthenticationPro
private OpenIdTokenValidatorWrapper openIdTokenValidatorWrapper;
@Resource
private JweDecrypterService jweDecrypterService;
@Value("${id-token-encryption.enabled:false}")
private boolean isEncryptionEnabled;
@Resource
private ApplicationProperties applicationProperties;
protected OpenIdConnectAuthenticationFilter(final String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
......@@ -82,39 +83,41 @@ public class OpenIdConnectAuthenticationFilter extends AbstractAuthenticationPro
try {
accessToken = oAuth2RestOperations.getAccessToken();
} catch (final OAuth2Exception e) {
LOG.error("Could not get Access Token", e);
log.error("Could not get Access Token", e);
throw new AccessTokenRequiredException("Could not obtain access token", details, e);
}
return accessToken;
}
private TokenDetails getTokenDetails(final JWT jwt) {
JWTClaimsSet claimsSet;
try {
//If we support only signed JWT or encrypted JWT we can include only adequate part of code
if (jwt instanceof SignedJWT) {
openIdTokenValidatorWrapper.validateToken(jwt);
return new TokenDetails(jwt.getJWTClaimsSet());
claimsSet = jwt.getJWTClaimsSet();
} else if (jwt instanceof EncryptedJWT) {
final JWT encryptedJWT = jweDecrypterService.decrypt((EncryptedJWT) jwt);
openIdTokenValidatorWrapper.validateToken(encryptedJWT);
return new TokenDetails(encryptedJWT.getJWTClaimsSet());
claimsSet = encryptedJWT.getJWTClaimsSet();
} else {
LOG.warn("Plain JWT detected. JWT should be signed.");
return new TokenDetails(jwt.getJWTClaimsSet());
log.warn("Plain JWT detected. JWT should be signed.");
claimsSet = jwt.getJWTClaimsSet();
}
} catch (final ParseException e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
return new TokenDetails(claimsSet);
}
private UserInfo createUserInfo(final JWTClaimsSet jwtClaimsSet, final JWT jwt) {
Object name = jwtClaimsSet.getClaim("name");
String idToken;
String encryptedIdToken = null;
if(jwt instanceof EncryptedJWT) {
final EncryptedJWT encryptedJWT = (EncryptedJWT)jwt;
encryptedIdToken = jwt.getParsedString();
idToken = encryptedJWT.getPayload().toString();
if (jwt instanceof EncryptedJWT) {
final EncryptedJWT encryptedJWT = (EncryptedJWT) jwt;
encryptedIdToken = jwt.getParsedString();
idToken = encryptedJWT.getPayload().toString();
} else {
idToken = jwt.getParsedString();
}
......@@ -126,9 +129,9 @@ public class OpenIdConnectAuthenticationFilter extends AbstractAuthenticationPro
}
private void validateEncryptionConfigurationMatchesServer(final JWT jwt) {
if (isEncryptionEnabled && !(jwt instanceof EncryptedJWT)) {
if (applicationProperties.isIdTokenEncryptionEnabled() && !(jwt instanceof EncryptedJWT)) {
throw new IllegalStateException("Server did not return an EncryptedJWT but encryption was enabled. Check your server side configuration");
} else if (!isEncryptionEnabled && jwt instanceof EncryptedJWT) {
} else if (!applicationProperties.isIdTokenEncryptionEnabled() && jwt instanceof EncryptedJWT) {
throw new IllegalStateException("Server returned an EncryptedJWT but encryption was not enabled. Check your server side configuration.");
}
}
......
......@@ -5,7 +5,6 @@ import java.net.URL;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
......@@ -19,6 +18,7 @@ import com.nimbusds.jwt.JWT;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
import com.onegini.oidc.config.ApplicationProperties;
import com.onegini.oidc.model.OpenIdWellKnownConfiguration;
/**
......@@ -27,10 +27,8 @@ import com.onegini.oidc.model.OpenIdWellKnownConfiguration;
@Component
public class OpenIdTokenValidatorWrapper {
@Value("${onegini.oauth2.clientId}")
private String clientId;
@Value("${onegini.oauth2.issuer}")
private String issuer;
@Resource
private ApplicationProperties applicationProperties;
@Resource
private OpenIdWellKnownConfiguration openIdWellKnownConfiguration;
......@@ -42,7 +40,8 @@ public class OpenIdTokenValidatorWrapper {
try {
final JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(new URL(jwksUri));
final JWSKeySelector jwsKeySelector = new JWSVerificationKeySelector<>(algorithm, jwkSource);
final IDTokenValidator idTokenValidator = new IDTokenValidator(new Issuer(issuer), new ClientID(clientId), jwsKeySelector, null);
final IDTokenValidator idTokenValidator = new IDTokenValidator(new Issuer(applicationProperties.getIssuer()),
new ClientID(applicationProperties.getClientId()), jwsKeySelector, null);
idTokenValidator.validate(idToken, null);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Unable to convert '" + jwksUri + "' to URL.", e);
......
onegini.oauth2.clientId=openid-client
onegini.oauth2.clientSecret=secret
onegini.oauth2.issuer=http://localhost:7878/oauth
id-token-encryption.enabled=false
\ No newline at end of file
onegini.oidc.clientId=openid-client
onegini.oidc.clientSecret=secret
onegini.oidc.issuer=http://localhost:7878/oauth
onegini.oidc.idTokenEncryptionEnabled=false
\ No newline at end of file
......@@ -20,12 +20,12 @@
<dt>Name</dt>
<dd th:text="${userInfo.name}"></dd>
<th:block th:unless="${#strings.isEmpty(userInfo.encryptedIdToken)}">
<dt>Your encrypted id token</span></dt>
<dt>Your encrypted id token</dt>
<dd>
<pre th:text="${userInfo.encryptedIdToken}"></pre>
</dd>
</th:block>
<dt>Your id token</span></dt>
<dt>Your id token</dt>
<dd>
<pre th:text="${userInfo.idToken}"></pre>
</dd>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment