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 @@ ...@@ -12,7 +12,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version> <version>2.0.4.RELEASE</version>
</parent> </parent>
<dependencies> <dependencies>
...@@ -20,17 +20,30 @@ ...@@ -20,17 +20,30 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security.oauth</groupId> <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId> <artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version> <version>2.3.3.RELEASE</version>
</dependency> </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> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>18.0</version> <version>18.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
...@@ -47,6 +60,7 @@ ...@@ -47,6 +60,7 @@
<version>2.4.0</version> <version>2.4.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -3,7 +3,7 @@ package com.github.fromi.openidconnect; ...@@ -3,7 +3,7 @@ package com.github.fromi.openidconnect;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder; 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.ComponentScan;
import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.ScopedProxyMode;
......
...@@ -3,7 +3,6 @@ package com.github.fromi.openidconnect.security; ...@@ -3,7 +3,6 @@ package com.github.fromi.openidconnect.security;
import static org.springframework.security.oauth2.common.AuthenticationScheme.header; import static org.springframework.security.oauth2.common.AuthenticationScheme.header;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -31,14 +30,13 @@ public class OAuth2Client { ...@@ -31,14 +30,13 @@ public class OAuth2Client {
@Value("${onegini.oauth2.issuer}") @Value("${onegini.oauth2.issuer}")
private String issuer; private String issuer;
public static ProviderConfiguration providerConfiguration = null;
@Bean @Bean
public OAuth2ProtectedResourceDetails googleOAuth2Details() { public ProviderConfiguration getProviderConfiguration(){
return new ProviderDiscoveryClient(issuer).discover();
}
//Discover endpoints @Bean
ProviderDiscoveryClient dc = new ProviderDiscoveryClient(issuer); public OAuth2ProtectedResourceDetails googleOAuth2Details(final ProviderConfiguration providerConfiguration) {
providerConfiguration = dc.discover();
//setup OAuth //setup OAuth
AuthorizationCodeResourceDetails conf = new AuthorizationCodeResourceDetails(); AuthorizationCodeResourceDetails conf = new AuthorizationCodeResourceDetails();
...@@ -59,7 +57,7 @@ public class OAuth2Client { ...@@ -59,7 +57,7 @@ public class OAuth2Client {
@Bean @Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations googleOAuth2RestTemplate() { public OAuth2RestOperations googleOAuth2RestTemplate(final ProviderConfiguration providerConfiguration) {
return new OAuth2RestTemplate(googleOAuth2Details(), oAuth2ClientContext); return new OAuth2RestTemplate(googleOAuth2Details(providerConfiguration), oAuth2ClientContext);
} }
} }
package com.github.fromi.openidconnect.security; package com.github.fromi.openidconnect.security;
import static com.github.fromi.openidconnect.security.OAuth2Client.providerConfiguration;
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.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.annotation.Resource;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.Authentication;
import org.springframework.security.core.AuthenticationException; 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.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.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; 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 { 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 @Resource
private OAuth2RestOperations restTemplate; private OAuth2RestOperations restTemplate;
@Resource
private ProviderConfiguration providerConfiguration;
@Resource
private OAuth2ProtectedResourceDetails details;
protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) { protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl); super(defaultFilterProcessesUrl);
setAuthenticationManager(authentication -> authentication); // AbstractAuthenticationProcessingFilter requires an authentication manager. setAuthenticationManager(authentication -> authentication); // AbstractAuthenticationProcessingFilter requires an authentication manager.
...@@ -31,8 +67,57 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro ...@@ -31,8 +67,57 @@ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationPro
@Override @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException { 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); //Use ID token to retrieve user info -> when we do this we also verify the ID token
return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES); 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; 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 { public class UserInfo {
private final String id; private String id;
private final String name; private String name;
// private final String givenName; private String givenName;
// private final String familyName; private String familyName;
// private final String locale; private String locale;
// private final String preferred_username; private String preferred_username;
@JsonCreator public UserInfo(){
public UserInfo(@JsonProperty("sub") String id//,
//@JsonProperty("name") String name, }
//@JsonProperty("given_name") String givenName,
//@JsonProperty("family_name") String familyName, public UserInfo setId(final String id) {
//@JsonProperty("locale") String locale,
//@JsonProperty("preferred_username") String preferred_username
){
this.id = id; this.id = id;
this.name = id; return this;
//this.name = name; }
//this.givenName = givenName;
//this.familyName = familyName; public UserInfo setName(final String name) {
//this.locale = locale; this.name = name;
//this.preferred_username = preferred_username; 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() { public String getId() {
...@@ -38,19 +50,19 @@ public class UserInfo { ...@@ -38,19 +50,19 @@ public class UserInfo {
return name; return name;
} }
// public String getGivenName() { public String getGivenName() {
// return givenName; return givenName;
// } }
//
// public String getFamilyName() { public String getFamilyName() {
// return familyName; return familyName;
// } }
//
// public String getLocale() { public String getLocale() {
// return locale; return locale;
// } }
//
// public String getPreferred_username() { public String getPreferred_username() {
// return preferred_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