From fbd6e67af84f986c5abceba6374e9161abaf0640 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Fri, 17 Aug 2012 11:49:42 -0400 Subject: [PATCH] Refactored auth provider to call the userinfo endpoint and provide info inside the auth token. --- .../AbstractOIDCAuthenticationFilter.java | 2 +- .../OpenIdConnectAuthenticationProvider.java | 42 +++++++---- .../OpenIdConnectAuthenticationToken.java | 72 +++++++++++++++---- .../connect/client/UserInfoFetcher.java | 43 +++++++++++ .../config/OIDCServerConfiguration.java | 38 ++++++---- 5 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java index 4521922b2..d1a2d99ac 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AbstractOIDCAuthenticationFilter.java @@ -432,7 +432,7 @@ public class AbstractOIDCAuthenticationFilter extends // construct an OpenIdConnectAuthenticationToken and return // a Authentication object w/the userId and the idToken - OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idTokenValue, accessTokenValue, refreshTokenValue); + OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idToken.getClaims().getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue); Authentication authentication = this.getAuthenticationManager().authenticate(token); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationProvider.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationProvider.java index ecbcc3cf3..7b2c18a02 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationProvider.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationProvider.java @@ -15,16 +15,25 @@ ******************************************************************************/ package org.mitre.openid.connect.client; +import java.util.Collection; + +import org.mitre.openid.connect.model.UserInfo; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.util.Assert; +import com.google.common.base.Strings; +import com.google.common.collect.Sets; + /** * @author nemonik * @@ -32,7 +41,8 @@ import org.springframework.util.Assert; public class OpenIdConnectAuthenticationProvider implements AuthenticationProvider, InitializingBean { - private AuthenticationUserDetailsService userDetailsService; + private UserInfoFetcher userInfoFetcher = new UserInfoFetcher(); + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); /* @@ -43,8 +53,6 @@ public class OpenIdConnectAuthenticationProvider implements */ @Override public void afterPropertiesSet() throws Exception { - Assert.notNull(this.userDetailsService, - "The userDetailsService must be set"); } /* @@ -63,12 +71,26 @@ public class OpenIdConnectAuthenticationProvider implements if (authentication instanceof OpenIdConnectAuthenticationToken) { + // Default authorities set + // TODO: let this be configured + Collection authorities = Sets.newHashSet(new SimpleGrantedAuthority("ROLE_USER")); + OpenIdConnectAuthenticationToken token = (OpenIdConnectAuthenticationToken) authentication; - UserDetails userDetails = userDetailsService.loadUserDetails(token); + UserInfo userInfo = userInfoFetcher.loadUserInfo(token); - return new OpenIdConnectAuthenticationToken(userDetails, - authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), token.getUserId(), + if (userInfo == null) { + // TODO: user Info not found -- error? + } else { + if (!Strings.isNullOrEmpty(userInfo.getUserId()) && userInfo.getUserId().equals(token.getUserId())) { + // the userinfo came back and the user_id fields don't match what was in the id_token + throw new UsernameNotFoundException("user_id mismatch between id_token and user_info call: " + userInfo.getUserId() + " / " + token.getUserId()); + } + } + + return new OpenIdConnectAuthenticationToken(token.getUserId(), + token.getIssuer(), + userInfo, authoritiesMapper.mapAuthorities(authorities), token.getIdTokenValue(), token.getAccessTokenValue(), token.getRefreshTokenValue()); } @@ -82,11 +104,6 @@ public class OpenIdConnectAuthenticationProvider implements this.authoritiesMapper = authoritiesMapper; } - public void setUserDetailsService( - AuthenticationUserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - /* * (non-Javadoc) * @@ -96,7 +113,6 @@ public class OpenIdConnectAuthenticationProvider implements */ @Override public boolean supports(Class authentication) { - return OpenIdConnectAuthenticationToken.class - .isAssignableFrom(authentication); + return OpenIdConnectAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationToken.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationToken.java index d43f0d444..0a9f18203 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationToken.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OpenIdConnectAuthenticationToken.java @@ -18,11 +18,16 @@ package org.mitre.openid.connect.client; import java.util.ArrayList; import java.util.Collection; +import org.mitre.openid.connect.config.OIDCServerConfiguration; import org.mitre.openid.connect.model.IdToken; +import org.mitre.openid.connect.model.UserInfo; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; + /** * * @author Michael Walsh, Justin Richer @@ -32,51 +37,73 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke private static final long serialVersionUID = 22100073066377804L; - private final Object principle; + private final Object principal; private final String idTokenValue; // string representation of the id token private final String accessTokenValue; // string representation of the access token private final String refreshTokenValue; // string representation of the refresh token + private final String issuer; // issuer URL (parsed from the id token) private final String userId; // user id (parsed from the id token) + private final transient OIDCServerConfiguration serverConfiguration; // server configuration used to fulfill this token, don't serialize it + private final transient UserInfo userInfo; // user info container, don't serialize it b/c it might be huge and can be re-fetched + /** - * Constructs OpenIdConnectAuthenticationToken provided + * Constructs OpenIdConnectAuthenticationToken with a full set of authorities, marking this as authenticated. * - * @param principle - * @param authorities + * Set to authenticated. + * + * Constructs a Principal out of the user_id and issuer. * @param userId + * @param authorities + * @param principal * @param idToken */ - public OpenIdConnectAuthenticationToken(Object principle, - Collection authorities, String userId, + public OpenIdConnectAuthenticationToken(String userId, String issuer, + UserInfo userInfo, Collection authorities, String idTokenValue, String accessTokenValue, String refreshTokenValue) { super(authorities); - this.principle = principle; + this.principal = ImmutableMap.of("user_id", userId, "issuer", issuer); + this.userInfo = userInfo; this.userId = userId; + this.issuer = issuer; this.idTokenValue = idTokenValue; this.accessTokenValue = accessTokenValue; this.refreshTokenValue = refreshTokenValue; + this.serverConfiguration = null; // we don't need a server config anymore + setAuthenticated(true); } /** - * Constructs OpenIdConnectAuthenticationToken provided + * Constructs OpenIdConnectAuthenticationToken for use as a data shuttle from the filter to the auth provider. * - * @param idToken + * Set to not-authenticated. + * + * Constructs a Principal out of the user_id and issuer. * @param userId + * @param idToken */ - public OpenIdConnectAuthenticationToken(String userId, String idTokenValue, String accessTokenValue, String refreshTokenValue) { + public OpenIdConnectAuthenticationToken(String userId, String issuer, + OIDCServerConfiguration serverConfiguration, + String idTokenValue, String accessTokenValue, String refreshTokenValue) { super(new ArrayList(0)); - this.principle = userId; + this.principal = ImmutableMap.of("user_id", userId, "issuer", issuer); this.userId = userId; + this.issuer = issuer; this.idTokenValue = idTokenValue; this.accessTokenValue = accessTokenValue; this.refreshTokenValue = refreshTokenValue; + this.userInfo = null; // we don't have a UserInfo yet + + this.serverConfiguration = serverConfiguration; + + setAuthenticated(false); } @@ -98,7 +125,7 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke @Override public Object getPrincipal() { // TODO Auto-generated method stub - return principle; + return principal; } public String getUserId() { @@ -125,6 +152,27 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke public String getRefreshTokenValue() { return refreshTokenValue; } + + /** + * @return the serverConfiguration + */ + public OIDCServerConfiguration getServerConfiguration() { + return serverConfiguration; + } + + /** + * @return the issuer + */ + public String getIssuer() { + return issuer; + } + + /** + * @return the userInfo + */ + public UserInfo getUserInfo() { + return userInfo; + } } \ No newline at end of file diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java new file mode 100644 index 000000000..c96accbbd --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java @@ -0,0 +1,43 @@ +package org.mitre.openid.connect.client; + +import java.net.URI; + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.DefaultHttpClient; +import org.mitre.openid.connect.model.DefaultUserInfo; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class UserInfoFetcher { + + public UserInfo loadUserInfo(OpenIdConnectAuthenticationToken token) { + + HttpClient httpClient = new DefaultHttpClient(); + + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + + RestTemplate restTemplate = new RestTemplate(factory); + + MultiValueMap form = new LinkedMultiValueMap(); + form.add("access_token", token.getAccessTokenValue()); + + String userInfoString = restTemplate.postForObject(token.getServerConfiguration().getUserInfoUrl(), form, String.class); + + JsonObject userInfoJson = new JsonParser().parse(userInfoString).getAsJsonObject(); + + Gson gson = new Gson(); + DefaultUserInfo userInfo = gson.fromJson(userInfoJson, DefaultUserInfo.class); + + return new DefaultUserInfo(); + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/OIDCServerConfiguration.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/OIDCServerConfiguration.java index f0589daa9..9c0322721 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/OIDCServerConfiguration.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/OIDCServerConfiguration.java @@ -15,6 +15,8 @@ ******************************************************************************/ package org.mitre.openid.connect.config; +import java.net.URI; + /** * @author nemonik * @@ -38,6 +40,8 @@ public class OIDCServerConfiguration { private String jwkEncryptUrl; private String jwkSigningUrl; + + private String userInfoUrl; public String getAuthorizationEndpointURI() { return authorizationEndpointURI; @@ -111,17 +115,27 @@ public class OIDCServerConfiguration { this.jwkSigningUrl = jwkSigningUrl; } - @Override - public String toString() { - return "OIDCServerConfiguration [authorizationEndpointURI=" - + authorizationEndpointURI + ", tokenEndpointURI=" - + tokenEndpointURI + ", clientSecret=" + clientSecret - + ", clientId=" + clientId + ", issuer=" + issuer - +", x509EncryptedUrl=" - + x509EncryptUrl + ", jwkEncryptedUrl=" - + jwkEncryptUrl + ", x509SigningUrl=" - + x509SigningUrl + ", jwkSigningUrl=" - + jwkSigningUrl + "]"; - } + /** + * @return the userInfoUrl + */ + public String getUserInfoUrl() { + return userInfoUrl; + } + + /** + * @param userInfoUrl the userInfoUrl to set + */ + public void setUserInfoUrl(String userInfoUrl) { + this.userInfoUrl = userInfoUrl; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "OIDCServerConfiguration [authorizationEndpointURI=" + authorizationEndpointURI + ", tokenEndpointURI=" + tokenEndpointURI + ", clientSecret=" + clientSecret + ", clientId=" + clientId + ", issuer=" + issuer + ", x509EncryptUrl=" + x509EncryptUrl + ", x509SigningUrl=" + + x509SigningUrl + ", jwkEncryptUrl=" + jwkEncryptUrl + ", jwkSigningUrl=" + jwkSigningUrl + ", userInfoUrl=" + userInfoUrl + "]"; + } } \ No newline at end of file