Commit 6b5e922b authored by William Loosman's avatar William Loosman Committed by 陈健

Fixed pom dependencies.

Added ID token support.

Added ID token validation.
parent f6a773f6
......@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
<version>2.0.4.RELEASE</version>
</parent>
<dependencies>
......@@ -20,17 +20,30 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
......@@ -47,6 +60,7 @@
<version>2.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
......
......@@ -3,7 +3,7 @@ package com.github.fromi.openidconnect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ScopedProxyMode;
......
......@@ -3,7 +3,6 @@ package com.github.fromi.openidconnect.security;
import static org.springframework.security.oauth2.common.AuthenticationScheme.header;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -31,14 +30,13 @@ public class OAuth2Client {
@Value("${onegini.oauth2.issuer}")
private String issuer;
public static ProviderConfiguration providerConfiguration = null;
@Bean
public OAuth2ProtectedResourceDetails googleOAuth2Details() {
public ProviderConfiguration getProviderConfiguration(){
return new ProviderDiscoveryClient(issuer).discover();
}
//Discover endpoints
ProviderDiscoveryClient dc = new ProviderDiscoveryClient(issuer);
providerConfiguration = dc.discover();
@Bean
public OAuth2ProtectedResourceDetails googleOAuth2Details(final ProviderConfiguration providerConfiguration) {
//setup OAuth
AuthorizationCodeResourceDetails conf = new AuthorizationCodeResourceDetails();
......@@ -59,7 +57,7 @@ public class OAuth2Client {
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations googleOAuth2RestTemplate() {
return new OAuth2RestTemplate(googleOAuth2Details(), oAuth2ClientContext);
public OAuth2RestOperations googleOAuth2RestTemplate(final ProviderConfiguration providerConfiguration) {
return new OAuth2RestTemplate(googleOAuth2Details(providerConfiguration), oAuth2ClientContext);
}
}
package com.github.fromi.openidconnect.security;
import static com.github.fromi.openidconnect.security.OAuth2Client.providerConfiguration;
import static java.util.Optional.empty;
import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES;
import java.io.IOException;
import java.net.MalformedURLException;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.discovery.ProviderConfiguration;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
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.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import com.auth0.jwk.InvalidPublicKeyException;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jndi.toolkit.url.Uri;
public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Value("${onegini.oauth2.clientId}")
private String clientId;
@Value("${onegini.oauth2.clientSecret}")
private String clientSecret;
@Value("${onegini.oauth2.issuer}")
private String issuer;
@Resource
private OAuth2RestOperations restTemplate;
@Resource
private ProviderConfiguration providerConfiguration;
@Resource
private OAuth2ProtectedResourceDetails details;
protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authentication -> authentication); // AbstractAuthenticationProcessingFilter requires an authentication manager.
......@@ -31,8 +67,57 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
//todo: use id token for user info instead of endpoint
final ResponseEntity<UserInfo> userInfoResponseEntity = restTemplate.getForEntity(providerConfiguration.getUserInfoEndpoint().toString(), UserInfo.class);
return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES);
//Use ID token to retrieve user info -> when we do this we also verify the ID token
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
throw new BadCredentialsException("Could not obtain access token", e);
}
try {
String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
String kid = JwtHelper.headers(idToken).get("kid");
Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid));
Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
verifyClaims(authInfo);
UserInfo user = new UserInfo().setId(authInfo.get("sub")).setName(authInfo.get("sub"));
return new PreAuthenticatedAuthenticationToken(user, empty(), NO_AUTHORITIES);
}
catch (InvalidTokenException e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
catch (InvalidPublicKeyException e){
throw new AccessTokenRequiredException(e.getMessage(), details);
}
catch (JwkException e){
throw new AccessTokenRequiredException(e.getMessage(), details);
}
//Use UserInfo endpoint to retrieve user info
//final ResponseEntity<UserInfo> userInfoResponseEntity = restTemplate.getForEntity(providerConfiguration.getUserInfoEndpoint().toString(), UserInfo.class);
//return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES);
}
private RsaVerifier verifier(final String kid) throws InvalidPublicKeyException, JwkException {
JwkProvider provider = new UrlJwkProvider(providerConfiguration.getJwkSetUri());
Jwk jwk = provider.get(kid);
return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}
public void verifyClaims(Map claims) throws MalformedURLException {
int exp = (int) claims.get("exp");
Date expireDate = new Date(exp * 1000L);
Date now = new Date();
if (expireDate.before(now) || !new Uri(issuer).getHost().equals(new Uri((String) claims.get("iss")).getHost()) ||
!claims.get("aud").equals(clientId)) {
throw new RuntimeException("Invalid claims");
}
}
}
package com.github.fromi.openidconnect.security;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserInfo {
private final String id;
private final String name;
// private final String givenName;
// private final String familyName;
// private final String locale;
// private final String preferred_username;
@JsonCreator
public UserInfo(@JsonProperty("sub") String id//,
//@JsonProperty("name") String name,
//@JsonProperty("given_name") String givenName,
//@JsonProperty("family_name") String familyName,
//@JsonProperty("locale") String locale,
//@JsonProperty("preferred_username") String preferred_username
){
private String id;
private String name;
private String givenName;
private String familyName;
private String locale;
private String preferred_username;
public UserInfo(){
}
public UserInfo setId(final String id) {
this.id = id;
this.name = id;
//this.name = name;
//this.givenName = givenName;
//this.familyName = familyName;
//this.locale = locale;
//this.preferred_username = preferred_username;
return this;
}
public UserInfo setName(final String name) {
this.name = name;
return this;
}
public UserInfo setGivenName(final String givenName) {
this.givenName = givenName;
return this;
}
public UserInfo setFamilyName(final String familyName) {
this.familyName = familyName;
return this;
}
public UserInfo setLocale(final String locale) {
this.locale = locale;
return this;
}
public UserInfo setPreferred_username(final String preferred_username) {
this.preferred_username = preferred_username;
return this;
}
public String getId() {
......@@ -38,19 +50,19 @@ public class UserInfo {
return name;
}
// public String getGivenName() {
// return givenName;
// }
//
// public String getFamilyName() {
// return familyName;
// }
//
// public String getLocale() {
// return locale;
// }
//
// public String getPreferred_username() {
// return preferred_username;
// }
public String getGivenName() {
return givenName;
}
public String getFamilyName() {
return familyName;
}
public String getLocale() {
return locale;
}
public String getPreferred_username() {
return preferred_username;
}
}
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