Refactored auth provider to call the userinfo endpoint and provide info inside the auth token.

pull/210/head
Justin Richer 2012-08-17 11:49:42 -04:00
parent 8520fcbf72
commit fbd6e67af8
5 changed files with 159 additions and 38 deletions

View File

@ -432,7 +432,7 @@ public class AbstractOIDCAuthenticationFilter extends
// construct an OpenIdConnectAuthenticationToken and return // construct an OpenIdConnectAuthenticationToken and return
// a Authentication object w/the userId and the idToken // 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); Authentication authentication = this.getAuthenticationManager().authenticate(token);

View File

@ -15,16 +15,25 @@
******************************************************************************/ ******************************************************************************/
package org.mitre.openid.connect.client; 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.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
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.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
/** /**
* @author nemonik * @author nemonik
* *
@ -32,7 +41,8 @@ import org.springframework.util.Assert;
public class OpenIdConnectAuthenticationProvider implements public class OpenIdConnectAuthenticationProvider implements
AuthenticationProvider, InitializingBean { AuthenticationProvider, InitializingBean {
private AuthenticationUserDetailsService<OpenIdConnectAuthenticationToken> userDetailsService; private UserInfoFetcher userInfoFetcher = new UserInfoFetcher();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
/* /*
@ -43,8 +53,6 @@ public class OpenIdConnectAuthenticationProvider implements
*/ */
@Override @Override
public void afterPropertiesSet() throws Exception { 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) { if (authentication instanceof OpenIdConnectAuthenticationToken) {
// Default authorities set
// TODO: let this be configured
Collection<SimpleGrantedAuthority> authorities = Sets.newHashSet(new SimpleGrantedAuthority("ROLE_USER"));
OpenIdConnectAuthenticationToken token = (OpenIdConnectAuthenticationToken) authentication; OpenIdConnectAuthenticationToken token = (OpenIdConnectAuthenticationToken) authentication;
UserDetails userDetails = userDetailsService.loadUserDetails(token); UserInfo userInfo = userInfoFetcher.loadUserInfo(token);
return new OpenIdConnectAuthenticationToken(userDetails, if (userInfo == null) {
authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), token.getUserId(), // 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()); token.getIdTokenValue(), token.getAccessTokenValue(), token.getRefreshTokenValue());
} }
@ -82,11 +104,6 @@ public class OpenIdConnectAuthenticationProvider implements
this.authoritiesMapper = authoritiesMapper; this.authoritiesMapper = authoritiesMapper;
} }
public void setUserDetailsService(
AuthenticationUserDetailsService<OpenIdConnectAuthenticationToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
@ -96,7 +113,6 @@ public class OpenIdConnectAuthenticationProvider implements
*/ */
@Override @Override
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return OpenIdConnectAuthenticationToken.class return OpenIdConnectAuthenticationToken.class.isAssignableFrom(authentication);
.isAssignableFrom(authentication);
} }
} }

View File

@ -18,11 +18,16 @@ package org.mitre.openid.connect.client;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import org.mitre.openid.connect.config.OIDCServerConfiguration;
import org.mitre.openid.connect.model.IdToken; import org.mitre.openid.connect.model.IdToken;
import org.mitre.openid.connect.model.UserInfo;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.SpringSecurityCoreVersion;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
/** /**
* *
* @author Michael Walsh, Justin Richer * @author Michael Walsh, Justin Richer
@ -32,51 +37,73 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke
private static final long serialVersionUID = 22100073066377804L; 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 idTokenValue; // string representation of the id token
private final String accessTokenValue; // string representation of the access token private final String accessTokenValue; // string representation of the access token
private final String refreshTokenValue; // string representation of the refresh 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 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 * Set to authenticated.
* @param authorities *
* Constructs a Principal out of the user_id and issuer.
* @param userId * @param userId
* @param authorities
* @param principal
* @param idToken * @param idToken
*/ */
public OpenIdConnectAuthenticationToken(Object principle, public OpenIdConnectAuthenticationToken(String userId, String issuer,
Collection<? extends GrantedAuthority> authorities, String userId, UserInfo userInfo, Collection<? extends GrantedAuthority> authorities,
String idTokenValue, String accessTokenValue, String refreshTokenValue) { String idTokenValue, String accessTokenValue, String refreshTokenValue) {
super(authorities); super(authorities);
this.principle = principle; this.principal = ImmutableMap.of("user_id", userId, "issuer", issuer);
this.userInfo = userInfo;
this.userId = userId; this.userId = userId;
this.issuer = issuer;
this.idTokenValue = idTokenValue; this.idTokenValue = idTokenValue;
this.accessTokenValue = accessTokenValue; this.accessTokenValue = accessTokenValue;
this.refreshTokenValue = refreshTokenValue; this.refreshTokenValue = refreshTokenValue;
this.serverConfiguration = null; // we don't need a server config anymore
setAuthenticated(true); 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 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<GrantedAuthority>(0)); super(new ArrayList<GrantedAuthority>(0));
this.principle = userId; this.principal = ImmutableMap.of("user_id", userId, "issuer", issuer);
this.userId = userId; this.userId = userId;
this.issuer = issuer;
this.idTokenValue = idTokenValue; this.idTokenValue = idTokenValue;
this.accessTokenValue = accessTokenValue; this.accessTokenValue = accessTokenValue;
this.refreshTokenValue = refreshTokenValue; this.refreshTokenValue = refreshTokenValue;
this.userInfo = null; // we don't have a UserInfo yet
this.serverConfiguration = serverConfiguration;
setAuthenticated(false); setAuthenticated(false);
} }
@ -98,7 +125,7 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return principle; return principal;
} }
public String getUserId() { public String getUserId() {
@ -125,6 +152,27 @@ public class OpenIdConnectAuthenticationToken extends AbstractAuthenticationToke
public String getRefreshTokenValue() { public String getRefreshTokenValue() {
return refreshTokenValue; 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;
}
} }

View File

@ -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<String, String> form = new LinkedMultiValueMap<String, String>();
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();
}
}

View File

@ -15,6 +15,8 @@
******************************************************************************/ ******************************************************************************/
package org.mitre.openid.connect.config; package org.mitre.openid.connect.config;
import java.net.URI;
/** /**
* @author nemonik * @author nemonik
* *
@ -38,6 +40,8 @@ public class OIDCServerConfiguration {
private String jwkEncryptUrl; private String jwkEncryptUrl;
private String jwkSigningUrl; private String jwkSigningUrl;
private String userInfoUrl;
public String getAuthorizationEndpointURI() { public String getAuthorizationEndpointURI() {
return authorizationEndpointURI; return authorizationEndpointURI;
@ -111,17 +115,27 @@ public class OIDCServerConfiguration {
this.jwkSigningUrl = jwkSigningUrl; this.jwkSigningUrl = jwkSigningUrl;
} }
@Override /**
public String toString() { * @return the userInfoUrl
return "OIDCServerConfiguration [authorizationEndpointURI=" */
+ authorizationEndpointURI + ", tokenEndpointURI=" public String getUserInfoUrl() {
+ tokenEndpointURI + ", clientSecret=" + clientSecret return userInfoUrl;
+ ", clientId=" + clientId + ", issuer=" + issuer }
+", x509EncryptedUrl="
+ x509EncryptUrl + ", jwkEncryptedUrl=" /**
+ jwkEncryptUrl + ", x509SigningUrl=" * @param userInfoUrl the userInfoUrl to set
+ x509SigningUrl + ", jwkSigningUrl=" */
+ jwkSigningUrl + "]"; 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 + "]";
}
} }