Commit a4b8e548 authored by Jasha Joachimsthal's avatar Jasha Joachimsthal Committed by 陈健

OAUTH-3116 Use nimbus library for the JWT

parent f310ee31
...@@ -38,7 +38,7 @@ ___Example configuration___ ...@@ -38,7 +38,7 @@ ___Example configuration___
oauth2: oauth2:
clientId: openid-client clientId: openid-client
clientSecret: secret clientSecret: secret
issuer: http://localhost:7878/oauth/ issuer: http://localhost:7878/oauth
## Run and test ## Run and test
Run the example via the Run configuration in IntelliJ or via the command line: `mvn spring-boot:run`. The Token Server needs to be accessible to start this Run the example via the Run configuration in IntelliJ or via the command line: `mvn spring-boot:run`. The Token Server needs to be accessible to start this
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.onegini.oidc</groupId> <groupId>com.onegini.oidc</groupId>
<artifactId>spring-google-openidconnect</artifactId> <artifactId>java-spring-oidc-example</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.nimbusds</groupId>
<artifactId>jwks-rsa</artifactId> <artifactId>oauth2-oidc-sdk</artifactId>
<version>0.3.0</version> <version>5.63</version>
</dependency> </dependency>
<dependency> <dependency>
......
...@@ -11,13 +11,13 @@ import org.springframework.context.annotation.ScopedProxyMode; ...@@ -11,13 +11,13 @@ import org.springframework.context.annotation.ScopedProxyMode;
@EnableAutoConfiguration @EnableAutoConfiguration
public class Application extends SpringBootServletInitializer { public class Application extends SpringBootServletInitializer {
public static void main(String[] args) { public static void main(final String[] args) {
SpringApplication.run(Application.class); SpringApplication.run(Application.class);
} }
@Override @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class); return application.sources(Application.class);
} }
} }
\ No newline at end of file
...@@ -3,9 +3,7 @@ package com.github.fromi.openidconnect.security; ...@@ -3,9 +3,7 @@ package com.github.fromi.openidconnect.security;
import static java.util.Optional.empty; import static java.util.Optional.empty;
import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES; import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES;
import java.io.IOException; import java.text.ParseException;
import java.net.URI;
import java.security.interfaces.RSAPublicKey;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
...@@ -16,25 +14,19 @@ import javax.servlet.http.HttpServletResponse; ...@@ -16,25 +14,19 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.discovery.ProviderConfiguration; import org.springframework.security.oauth2.client.discovery.ProviderConfiguration;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import com.auth0.jwk.Jwk; import com.nimbusds.jwt.JWT;
import com.auth0.jwk.JwkException; import com.nimbusds.jwt.JWTClaimsSet;
import com.auth0.jwk.JwkProvider; import com.nimbusds.jwt.JWTParser;
import com.auth0.jwk.UrlJwkProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
...@@ -59,7 +51,7 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro ...@@ -59,7 +51,7 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro
} }
@Override @Override
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws IOException { public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
//Use ID token to retrieve user info -> when we do this we also verify the ID token //Use ID token to retrieve user info -> when we do this we also verify the ID token
final OAuth2AccessToken accessToken; final OAuth2AccessToken accessToken;
...@@ -71,20 +63,22 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro ...@@ -71,20 +63,22 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro
} }
// Option 1: Use claim to create UserInfo // Option 1: Use claim to create UserInfo
final String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
try { try {
final String idToken = accessToken.getAdditionalInformation().get("id_token").toString(); final JWT jwt = JWTParser.parse(idToken);
final String kid = JwtHelper.headers(idToken).get("kid"); // TODO OAUTH-3116: validate the JWT
final Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid)); final JWTClaimsSet jwtClaimsSet = jwt.getJWTClaimsSet();
final Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
verifyClaims(authInfo);
final UserInfo user = new UserInfo().setId(authInfo.get("sub")).setName(authInfo.get("sub")); final Map<String, Object> authInfo = jwtClaimsSet.getClaims();
// TODO OAUTH-3116: find a way to make them available in the modelMap
verifyClaims(jwtClaimsSet);
final UserInfo user = new UserInfo().setId(jwtClaimsSet.getSubject()).setName(jwtClaimsSet.getSubject());
return new PreAuthenticatedAuthenticationToken(user, empty(), NO_AUTHORITIES); return new PreAuthenticatedAuthenticationToken(user, empty(), NO_AUTHORITIES);
} catch (final InvalidTokenException e) { } catch (final ParseException e) {
throw new BadCredentialsException("Could not obtain user details from token", e); throw new BadCredentialsException("Could not obtain user details from token", e);
} catch (final JwkException e) {
throw new AccessTokenRequiredException(e.getMessage(), details);
} }
// Option 2: Use UserInfo endpoint to retrieve user info // Option 2: Use UserInfo endpoint to retrieve user info
...@@ -92,21 +86,18 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro ...@@ -92,21 +86,18 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro
// return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES); // return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES);
} }
private RsaVerifier verifier(final String kid) throws JwkException { private void verifyClaims(final JWTClaimsSet claims) {
final JwkProvider provider = new UrlJwkProvider(providerConfiguration.getJwkSetUri()); final Date expireDate = claims.getExpirationTime();
final Jwk jwk = provider.get(kid);
return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}
private void verifyClaims(final Map claims) {
final int exp = (int) claims.get("exp");
final Date expireDate = new Date(exp * 1000L);
final Date now = new Date(); final Date now = new Date();
if (expireDate.before(now)
|| !Objects.equals(URI.create(issuer).getHost(), URI.create((String) claims.get("iss")).getHost()) if (expireDate.before(now)) {
|| !Objects.equals(clientId, claims.get("aud"))) { throw new CredentialsExpiredException("Claim has expired");
throw new RuntimeException("Invalid claims"); }
if (!(Objects.equals(issuer, claims.getIssuer())
&& claims.getAudience().contains(clientId))) {
throw new BadCredentialsException("Invalid claims");
} }
} }
} }
\ No newline at end of file
...@@ -2,4 +2,4 @@ onegini: ...@@ -2,4 +2,4 @@ onegini:
oauth2: oauth2:
clientId: openid-client clientId: openid-client
clientSecret: secret clientSecret: secret
issuer: http://localhost:7878/oauth/ issuer: http://localhost:7878/oauth
\ No newline at end of file \ No newline at end of file
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