expose ID Token and UserInfo to the AuthoritiesProvider and AuthoritiesMapper, both extensible
closes #699 closes #761pull/834/head
parent
f7b5228109
commit
d1e8529a7b
|
@ -19,14 +19,19 @@
|
|||
*/
|
||||
package org.mitre.openid.connect.client;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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 com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -37,31 +42,36 @@ import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper
|
|||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class NamedAdminAuthoritiesMapper implements GrantedAuthoritiesMapper {
|
||||
public class NamedAdminAuthoritiesMapper implements OIDCAuthoritiesMapper {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(NamedAdminAuthoritiesMapper.class);
|
||||
|
||||
private static final SimpleGrantedAuthority ROLE_ADMIN = new SimpleGrantedAuthority("ROLE_ADMIN");
|
||||
private static final SimpleGrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER");
|
||||
|
||||
private Set<SubjectIssuerGrantedAuthority> admins = new HashSet<SubjectIssuerGrantedAuthority>();
|
||||
|
||||
private GrantedAuthoritiesMapper chain = new NullAuthoritiesMapper();
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
|
||||
public Collection<? extends GrantedAuthority> mapAuthorities(JWT idToken, UserInfo userInfo) {
|
||||
|
||||
Set<GrantedAuthority> out = new HashSet<GrantedAuthority>();
|
||||
out.addAll(authorities);
|
||||
try {
|
||||
ReadOnlyJWTClaimsSet claims = idToken.getJWTClaimsSet();
|
||||
|
||||
SubjectIssuerGrantedAuthority authority = new SubjectIssuerGrantedAuthority(claims.getSubject(), claims.getIssuer());
|
||||
out.add(authority);
|
||||
|
||||
for (GrantedAuthority authority : authorities) {
|
||||
if (admins.contains(authority)) {
|
||||
out.add(ROLE_ADMIN);
|
||||
}
|
||||
|
||||
// everybody's a user by default
|
||||
out.add(ROLE_USER);
|
||||
|
||||
} catch (ParseException e) {
|
||||
logger.error("Unable to parse ID Token inside of authorities mapper (huh?)");
|
||||
}
|
||||
|
||||
// everybody's a user by default
|
||||
out.add(ROLE_USER);
|
||||
|
||||
return chain.mapAuthorities(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,18 +88,4 @@ public class NamedAdminAuthoritiesMapper implements GrantedAuthoritiesMapper {
|
|||
this.admins = admins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chain
|
||||
*/
|
||||
public GrantedAuthoritiesMapper getChain() {
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chain the chain to set
|
||||
*/
|
||||
public void setChain(GrantedAuthoritiesMapper chain) {
|
||||
this.chain = chain;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,18 +16,22 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.openid.connect.client;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
|
||||
/**
|
||||
* @author nemonik
|
||||
|
@ -35,9 +39,11 @@ import com.google.common.collect.Lists;
|
|||
*/
|
||||
public class OIDCAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class);
|
||||
|
||||
private UserInfoFetcher userInfoFetcher = new UserInfoFetcher();
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper();
|
||||
private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
@ -46,8 +52,7 @@ public class OIDCAuthenticationProvider implements AuthenticationProvider {
|
|||
* authenticate(org.springframework.security.core.Authentication)
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(final Authentication authentication)
|
||||
throws AuthenticationException {
|
||||
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
|
||||
|
||||
if (!supports(authentication.getClass())) {
|
||||
return null;
|
||||
|
@ -56,33 +61,56 @@ public class OIDCAuthenticationProvider implements AuthenticationProvider {
|
|||
if (authentication instanceof OIDCAuthenticationToken) {
|
||||
|
||||
OIDCAuthenticationToken token = (OIDCAuthenticationToken) authentication;
|
||||
|
||||
Collection<SubjectIssuerGrantedAuthority> authorities = Lists.newArrayList(new SubjectIssuerGrantedAuthority(token.getSub(), token.getIssuer()));
|
||||
|
||||
UserInfo userInfo = userInfoFetcher.loadUserInfo(token);
|
||||
|
||||
if (userInfo == null) {
|
||||
// TODO: user Info not found -- error?
|
||||
} else {
|
||||
if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) {
|
||||
// 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: " + token.getSub() + " / " + userInfo.getSub());
|
||||
|
||||
try {
|
||||
|
||||
// get the ID Token value out
|
||||
String idTokenString = token.getIdTokenValue();
|
||||
JWT idToken = JWTParser.parse(idTokenString);
|
||||
|
||||
// load the user info if we can
|
||||
UserInfo userInfo = userInfoFetcher.loadUserInfo(token);
|
||||
|
||||
if (userInfo == null) {
|
||||
// user info not found -- could be an error, could be fine
|
||||
} else {
|
||||
// if we found userinfo, double check it
|
||||
if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) {
|
||||
// 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: " + token.getSub() + " / " + userInfo.getSub());
|
||||
}
|
||||
}
|
||||
|
||||
return createAuthenticationToken(token, authoritiesMapper.mapAuthorities(idToken, userInfo), userInfo);
|
||||
} catch (ParseException e) {
|
||||
logger.error("Unable to parse ID token in the token");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OIDCAuthenticationToken(token.getSub(),
|
||||
token.getIssuer(),
|
||||
userInfo, authoritiesMapper.mapAuthorities(authorities),
|
||||
token.getIdTokenValue(), token.getAccessTokenValue(), token.getRefreshTokenValue());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function to return a different kind of Authentication, processes the authorities differently,
|
||||
* or do post-processing based on the UserInfo object.
|
||||
*
|
||||
* @param token
|
||||
* @param authorities
|
||||
* @param userInfo
|
||||
* @return
|
||||
*/
|
||||
protected Authentication createAuthenticationToken(OIDCAuthenticationToken token, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
|
||||
return new OIDCAuthenticationToken(token.getSub(),
|
||||
token.getIssuer(),
|
||||
userInfo, authorities,
|
||||
token.getIdTokenValue(), token.getAccessTokenValue(), token.getRefreshTokenValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authoritiesMapper
|
||||
*/
|
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) {
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2015 The MITRE Corporation
|
||||
* and the MIT Kerberos and Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.openid.connect.client;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.mitre.openid.connect.model.UserInfo;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public interface OIDCAuthoritiesMapper {
|
||||
|
||||
/**
|
||||
* @param idToken the ID Token (parsed as a JWT, cannot be @null)
|
||||
* @param userInfo userInfo of the current user (could be @null)
|
||||
* @return the set of authorities to map to this user
|
||||
*/
|
||||
Collection<? extends GrantedAuthority> mapAuthorities(JWT idToken, UserInfo userInfo);
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.mitre.openid.connect.model;
|
|||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Convert;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
|
@ -28,6 +29,8 @@ import javax.persistence.NamedQuery;
|
|||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.mitre.openid.connect.model.convert.JsonObjectStringConverter;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@Entity
|
||||
|
@ -510,6 +513,9 @@ public class DefaultUserInfo implements UserInfo {
|
|||
* @return the jsonString
|
||||
*/
|
||||
@Override
|
||||
@Basic
|
||||
@Column(name = "src")
|
||||
@Convert(converter = JsonObjectStringConverter.class)
|
||||
public JsonObject getSource() {
|
||||
return src;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2015 The MITRE Corporation
|
||||
* and the MIT Kerberos and Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.openid.connect.model.convert;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Converter
|
||||
public class JsonObjectStringConverter implements AttributeConverter<JsonObject, String> {
|
||||
|
||||
private JsonParser parser = new JsonParser();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(JsonObject attribute) {
|
||||
if (attribute != null) {
|
||||
return attribute.toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public JsonObject convertToEntityAttribute(String dbData) {
|
||||
if (!Strings.isNullOrEmpty(dbData)) {
|
||||
return parser.parse(dbData).getAsJsonObject();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -253,7 +253,8 @@ CREATE TABLE IF NOT EXISTS user_info (
|
|||
phone_number_verified BOOLEAN,
|
||||
address_id VARCHAR(256),
|
||||
updated_time VARCHAR(256),
|
||||
birthdate VARCHAR(256)
|
||||
birthdate VARCHAR(256),
|
||||
src VARCHAR(4096)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS whitelisted_site (
|
||||
|
|
|
@ -110,7 +110,9 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
|
|||
ClientDetailsEntity client = null;
|
||||
|
||||
authRequest = authRequestFactory.createAuthorizationRequest(createRequestMap(request.getParameterMap()));
|
||||
client = clientService.loadClientByClientId(authRequest.getClientId());
|
||||
if (!Strings.isNullOrEmpty(authRequest.getClientId())) {
|
||||
client = clientService.loadClientByClientId(authRequest.getClientId());
|
||||
}
|
||||
|
||||
// save the login hint to the session
|
||||
if (authRequest.getExtensions().get(LOGIN_HINT) != null) {
|
||||
|
|
Loading…
Reference in New Issue