Merge pull request #831 from patriot1burke/master

principal token attribute mapping
This commit is contained in:
Bill Burke 2014-10-31 16:40:30 -04:00
commit 380f5dfb4e
13 changed files with 188 additions and 8 deletions

View file

@ -18,7 +18,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password", "client-keystore", "client-keystore-password", "client-key-password",
"auth-server-url-for-backend-requests", "always-refresh-token", "auth-server-url-for-backend-requests", "always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store" "register-node-at-startup", "register-node-period", "token-store", "principal-attribute"
}) })
public class AdapterConfig extends BaseAdapterConfig { public class AdapterConfig extends BaseAdapterConfig {
@ -48,6 +48,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected int registerNodePeriod = -1; protected int registerNodePeriod = -1;
@JsonProperty("token-store") @JsonProperty("token-store")
protected String tokenStore; protected String tokenStore;
@JsonProperty("principal-attribute")
protected String principalAttribute;
public boolean isAllowAnyHostname() { public boolean isAllowAnyHostname() {
return allowAnyHostname; return allowAnyHostname;
@ -152,4 +154,12 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setTokenStore(String tokenStore) { public void setTokenStore(String tokenStore) {
this.tokenStore = tokenStore; this.tokenStore = tokenStore;
} }
public String getPrincipalAttribute() {
return principalAttribute;
}
public void setPrincipalAttribute(String principalAttribute) {
this.principalAttribute = principalAttribute;
}
} }

View file

@ -336,6 +336,15 @@
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>principal-attribute</term>
<listitem>
<para>
OpenID Connection ID Token attribute to populate the UserPrincipal name with. If token attribute is null, defaults to <literal>sub</literal>
Possible values are <literal>sub</literal>, <literal>preferred_username</literal>, <literal>email</literal>, <literal>name</literal>, <literal>nickname</literal>, <literal>given_name</literal>, <literal>family_name</literal>.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</para> </para>
</section> </section>

View file

@ -59,4 +59,27 @@ public class AdapterUtils {
} }
return roles; return roles;
} }
public static String getPrincipalName(KeycloakDeployment deployment, AccessToken token) {
String attr = "sub";
if (deployment.getPrincipalAttribute() != null) attr = deployment.getPrincipalAttribute();
String name = null;
if ("sub".equals(attr)) {
name = token.getSubject();
} else if ("email".equals(attr)) {
name = token.getEmail();
} else if ("preferred_username".equals(attr)) {
name = token.getPreferredUsername();
} else if ("name".equals(attr)) {
name = token.getName();
} else if ("given_name".equals(attr)) {
name = token.getGivenName();
} else if ("family_name".equals(attr)) {
name = token.getFamilyName();
} else if ("nickname".equals(attr)) {
name = token.getNickName();
}
if (name == null) name = token.getSubject();
return name;
}
} }

View file

@ -68,7 +68,7 @@ public class CookieTokenStore {
log.debug("Token Verification succeeded!"); log.debug("Token Verification succeeded!");
RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString); RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(accessToken.getSubject(), secContext); return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, accessToken), secContext);
} catch (VerificationException ve) { } catch (VerificationException ve) {
log.warn("Failed verify token", ve); log.warn("Failed verify token", ve);
return null; return null;

View file

@ -34,6 +34,7 @@ public class KeycloakDeployment {
protected String accountUrl; protected String accountUrl;
protected String registerNodeUrl; protected String registerNodeUrl;
protected String unregisterNodeUrl; protected String unregisterNodeUrl;
protected String principalAttribute = "sub";
protected String resourceName; protected String resourceName;
protected boolean bearerOnly; protected boolean bearerOnly;
@ -333,4 +334,12 @@ public class KeycloakDeployment {
public void setRegisterNodePeriod(int registerNodePeriod) { public void setRegisterNodePeriod(int registerNodePeriod) {
this.registerNodePeriod = registerNodePeriod; this.registerNodePeriod = registerNodePeriod;
} }
public String getPrincipalAttribute() {
return principalAttribute;
}
public void setPrincipalAttribute(String principalAttribute) {
this.principalAttribute = principalAttribute;
}
} }

View file

@ -54,6 +54,7 @@ public class KeycloakDeploymentBuilder {
} else { } else {
deployment.setTokenStore(TokenStore.SESSION); deployment.setTokenStore(TokenStore.SESSION);
} }
if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
deployment.setResourceCredentials(adapterConfig.getCredentials()); deployment.setResourceCredentials(adapterConfig.getCredentials());
deployment.setPublicClient(adapterConfig.isPublicClient()); deployment.setPublicClient(adapterConfig.isPublicClient());
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings()); deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());

View file

@ -106,7 +106,7 @@ public abstract class RequestAuthenticator {
protected void completeAuthentication(OAuthRequestAuthenticator oauth) { protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken()); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(oauth.getToken().getSubject(), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
completeOAuthAuthentication(principal); completeOAuthAuthentication(principal);
} }
@ -116,7 +116,7 @@ public abstract class RequestAuthenticator {
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) { protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(bearer.getToken().getSubject(), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, bearer.getToken()), session);
completeBearerAuthentication(principal); completeBearerAuthentication(principal);
} }

View file

@ -130,6 +130,42 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true) .setAllowExpression(true)
.setDefaultValue(new ModelNode(false)) .setDefaultValue(new ModelNode(false))
.build(); .build();
protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
.setXmlName("auth-server-url-for-backend-requests")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
.setXmlName("always-refresh-token")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
.setXmlName("register-node-at-startup")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
.setXmlName("register-node-period")
.setAllowExpression(true)
.setValidator(new IntRangeValidator(-1, true))
.build();
protected static final SimpleAttributeDefinition TOKEN_STORE =
new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
.setXmlName("token-store")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
.setXmlName("principal-attribute")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>(); protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
@ -150,6 +186,12 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_ALLOWED_HEADERS); ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS); ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(EXPOSE_TOKEN); ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
} }
/** /**

View file

@ -23,6 +23,12 @@ keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.expose-token=Enable secure URL that exposes access token keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
keycloak.realm.register-node-at-startup=Cluster setting
keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment=A deployment secured by Keycloak keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@ -49,6 +55,12 @@ keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
keycloak.secure-deployment.register-node-at-startup=Cluster setting
keycloak.secure-deployment.register-node-period=how often to re-register node
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment.credential=Credential value keycloak.secure-deployment.credential=Credential value

View file

@ -83,6 +83,12 @@
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:complexType> </xs:complexType>
</xs:schema> </xs:schema>

View file

@ -130,6 +130,43 @@ public class SharedAttributeDefinitons {
.setAllowExpression(true) .setAllowExpression(true)
.setDefaultValue(new ModelNode(false)) .setDefaultValue(new ModelNode(false))
.build(); .build();
protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
.setXmlName("auth-server-url-for-backend-requests")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
.setXmlName("always-refresh-token")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
.setXmlName("register-node-at-startup")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
.setXmlName("register-node-period")
.setAllowExpression(true)
.setValidator(new IntRangeValidator(-1, true))
.build();
protected static final SimpleAttributeDefinition TOKEN_STORE =
new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
.setXmlName("token-store")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
.setXmlName("principal-attribute")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>(); protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
@ -150,6 +187,12 @@ public class SharedAttributeDefinitons {
ATTRIBUTES.add(CORS_ALLOWED_HEADERS); ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS); ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(EXPOSE_TOKEN); ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
} }
/** /**

View file

@ -23,6 +23,13 @@ keycloak.realm.cors-max-age=CORS max-age header
keycloak.realm.cors-allowed-headers=CORS allowed headers keycloak.realm.cors-allowed-headers=CORS allowed headers
keycloak.realm.cors-allowed-methods=CORS allowed methods keycloak.realm.cors-allowed-methods=CORS allowed methods
keycloak.realm.expose-token=Enable secure URL that exposes access token keycloak.realm.expose-token=Enable secure URL that exposes access token
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.realm.always-refresh-token=Refresh token on every single web request
keycloak.realm.register-node-at-startup=Cluster setting
keycloak.realm.register-node-period=how often to re-register node
keycloak.realm.token-store=cookie or session storage for auth session data
keycloak.realm.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment=A deployment secured by Keycloak keycloak.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
@ -49,6 +56,12 @@ keycloak.secure-deployment.cors-max-age=CORS max-age header
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
keycloak.secure-deployment.register-node-at-startup=Cluster setting
keycloak.secure-deployment.register-node-period=how often to re-register node
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment.credential=Credential value keycloak.secure-deployment.credential=Credential value

View file

@ -42,6 +42,12 @@
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:all> </xs:all>
<xs:attribute name="name" type="xs:string" use="required"> <xs:attribute name="name" type="xs:string" use="required">
<xs:annotation> <xs:annotation>
@ -74,6 +80,12 @@
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/> <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:all> </xs:all>
<xs:attribute name="name" type="xs:string" use="required"> <xs:attribute name="name" type="xs:string" use="required">
<xs:annotation> <xs:annotation>