IDToken setup

This commit is contained in:
Bill Burke 2014-02-26 19:04:42 -05:00
parent b2719ff40a
commit 0f67feb9dd
12 changed files with 191 additions and 14 deletions

View file

@ -2,6 +2,7 @@ package org.keycloak;
import org.keycloak.adapters.ResourceMetadata;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.io.Serializable;
@ -12,14 +13,18 @@ import java.io.Serializable;
public class KeycloakAuthenticatedSession implements Serializable {
protected String tokenString;
protected AccessToken token;
protected IDToken idToken;
protected String idTokenString;
protected transient ResourceMetadata metadata;
public KeycloakAuthenticatedSession() {
}
public KeycloakAuthenticatedSession(String tokenString, AccessToken token, ResourceMetadata metadata) {
public KeycloakAuthenticatedSession(String tokenString, AccessToken token, String idTokenString, IDToken idToken, ResourceMetadata metadata) {
this.tokenString = tokenString;
this.token = token;
this.idToken = idToken;
this.idTokenString = idTokenString;
this.metadata = metadata;
}
@ -38,4 +43,12 @@ public class KeycloakAuthenticatedSession implements Serializable {
public void setMetadata(ResourceMetadata metadata) {
this.metadata = metadata;
}
public IDToken getIdToken() {
return idToken;
}
public String getIdTokenString() {
return idTokenString;
}
}

View file

@ -6,6 +6,7 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.keycloak.KeycloakAuthenticatedSession;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization;
import javax.servlet.http.HttpServletRequest;
@ -35,6 +36,12 @@ public class CustomerDatabaseClient {
}
}
public static IDToken getIDToken(HttpServletRequest req) {
KeycloakAuthenticatedSession session = (KeycloakAuthenticatedSession) req.getAttribute(KeycloakAuthenticatedSession.class.getName());
return session.getIdToken();
}
public static List<String> getCustomers(HttpServletRequest req) throws Failure {
KeycloakAuthenticatedSession session = (KeycloakAuthenticatedSession) req.getAttribute(KeycloakAuthenticatedSession.class.getName());

View file

@ -2,6 +2,7 @@
pageEncoding="ISO-8859-1" %>
<%@ page import="org.keycloak.example.CustomerDatabaseClient" %>
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
<%@ page import="org.keycloak.representations.IDToken" %>
<html>
<head>
<title>Customer View Page</title>
@ -11,11 +12,18 @@
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth/rest/realms/demo/tokens/logout")
.queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
String acctUri = "http://localhost:8080/auth/rest/realms/demo/account";
IDToken idToken = CustomerDatabaseClient.getIDToken(request);
%>
<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a> | <a
href="<%=acctUri%>">manage acct</a></p>
User <b><%=request.getUserPrincipal().getName()%>
Servlet User Principal <b><%=request.getUserPrincipal().getName()%>
</b> made this request.
<p><b>Caller IDToken values</b> (<i>You can specify what is returned in IDToken in the customer-portal claims page in the admin console</i>:</p>
<p>Username: <%=idToken.getPreferredUsername()%></p>
<p>Email: <%=idToken.getEmail()%></p>
<p>Full Name: <%=idToken.getName()%></p>
<p>First: <%=idToken.getGivenName()%></p>
<p>Last: <%=idToken.getFamilyName()%></p>
<h2>Customer Listing</h2>
<%
java.util.List<String> list = null;

View file

@ -7,6 +7,7 @@ import org.keycloak.adapters.config.RealmConfiguration;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.jboss.logging.Logger;
import org.keycloak.representations.IDToken;
import java.io.IOException;
@ -24,8 +25,8 @@ public class RefreshableKeycloakSession extends KeycloakAuthenticatedSession {
public RefreshableKeycloakSession() {
}
public RefreshableKeycloakSession(String tokenString, AccessToken token, ResourceMetadata metadata, RealmConfiguration realmConfiguration, String refreshToken) {
super(tokenString, token, metadata);
public RefreshableKeycloakSession(String tokenString, AccessToken token, String idTokenString, IDToken idToken, ResourceMetadata metadata, RealmConfiguration realmConfiguration, String refreshToken) {
super(tokenString, token, idTokenString, idToken, metadata);
this.realmConfiguration = realmConfiguration;
this.refreshToken = refreshToken;
}

View file

@ -106,7 +106,7 @@ public class CatalinaBearerTokenAuthenticator {
principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skeletonKeyPrincipal, roles);
request.setUserPrincipal(principal);
request.setAuthType("OAUTH_BEARER");
KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(tokenString, token, resourceMetadata);
KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(tokenString, token, null, null, resourceMetadata);
request.setAttribute(KeycloakAuthenticatedSession.class.getName(), skSession);
return true;

View file

@ -262,7 +262,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
Session session = request.getSessionInternal(true);
session.setPrincipal(principal);
session.setAuthType("OAUTH");
KeycloakAuthenticatedSession skSession = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfiguration, oauth.getRefreshToken());
KeycloakAuthenticatedSession skSession = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), resourceMetadata, realmConfiguration, oauth.getRefreshToken());
session.setNote(KeycloakAuthenticatedSession.class.getName(), skSession);
String username = token.getSubject();

View file

@ -5,8 +5,10 @@ import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.adapters.config.RealmConfiguration;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.util.KeycloakUriBuilder;
import javax.servlet.http.Cookie;
@ -28,6 +30,8 @@ public class ServletOAuthLogin {
protected RealmConfiguration realmInfo;
protected int redirectPort;
protected String tokenString;
protected String idTokenString;
protected IDToken idToken;
protected AccessToken token;
protected String refreshToken;
@ -50,6 +54,14 @@ public class ServletOAuthLogin {
return refreshToken;
}
public String getIdTokenString() {
return idTokenString;
}
public IDToken getIdToken() {
return idToken;
}
public RealmConfiguration getRealmInfo() {
return realmInfo;
}
@ -246,8 +258,17 @@ public class ServletOAuthLogin {
}
tokenString = tokenResponse.getToken();
idTokenString = tokenResponse.getIdToken();
try {
token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata().getRealmKey(), realmInfo.getMetadata().getRealm());
if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try {
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
throw new VerificationException();
}
}
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");

View file

@ -67,7 +67,7 @@ public class JaxrsBearerTokenFilter implements ContainerRequestFilter {
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, resourceMetadata.getRealmKey(), resourceMetadata.getRealm());
KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(tokenString, token, resourceMetadata);
KeycloakAuthenticatedSession skSession = new KeycloakAuthenticatedSession(tokenString, token, null, null, resourceMetadata);
ResteasyProviderFactory.pushContext(KeycloakAuthenticatedSession.class, skSession);
String callerPrincipal = securityContext.getUserPrincipal() != null ? securityContext.getUserPrincipal().getName() : null;

View file

@ -94,7 +94,7 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism
protected void completeAuthentication(HttpServerExchange exchange, SecurityContext securityContext, OAuthAuthenticator oauth) {
final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), null);
RefreshableKeycloakSession session = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), resourceMetadata, realmConfig, oauth.getRefreshToken());
RefreshableKeycloakSession session = new RefreshableKeycloakSession(oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), resourceMetadata, realmConfig, oauth.getRefreshToken());
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
securityContext.authenticationComplete(account, "KEYCLOAK", true);
login(exchange, account);
@ -107,7 +107,7 @@ public class KeycloakAuthenticationMechanism implements AuthenticationMechanism
protected void completeAuthentication(SecurityContext securityContext, BearerTokenAuthenticator bearer) {
final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), bearer.getSurrogate());
RefreshableKeycloakSession session = new RefreshableKeycloakSession(bearer.getTokenString(), bearer.getToken(), resourceMetadata, realmConfig, null);
RefreshableKeycloakSession session = new RefreshableKeycloakSession(bearer.getTokenString(), bearer.getToken(), null, null, resourceMetadata, realmConfig, null);
KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, adapterConfig, resourceMetadata);
securityContext.authenticationComplete(account, "KEYCLOAK", false);
}

View file

@ -12,8 +12,10 @@ import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.config.RealmConfiguration;
import org.keycloak.VerificationException;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.util.KeycloakUriBuilder;
import java.io.IOException;
@ -31,6 +33,8 @@ public class OAuthAuthenticator {
protected RealmConfiguration realmInfo;
protected int sslRedirectPort;
protected String tokenString;
protected String idTokenString;
protected IDToken idToken;
protected AccessToken token;
protected HttpServerExchange exchange;
protected KeycloakChallenge challenge;
@ -58,6 +62,22 @@ public class OAuthAuthenticator {
return refreshToken;
}
public String getIdTokenString() {
return idTokenString;
}
public void setIdTokenString(String idTokenString) {
this.idTokenString = idTokenString;
}
public IDToken getIdToken() {
return idToken;
}
public void setIdToken(IDToken idToken) {
this.idToken = idToken;
}
protected String getRequestUrl() {
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
.replaceQuery(exchange.getQueryString());
@ -255,8 +275,17 @@ public class OAuthAuthenticator {
tokenString = tokenResponse.getToken();
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
try {
token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata().getRealmKey(), realmInfo.getMetadata().getRealm());
if (idTokenString != null) {
JWSInput input = new JWSInput(idTokenString);
try {
idToken = input.readJsonContent(IDToken.class);
} catch (IOException e) {
throw new VerificationException();
}
}
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");

View file

@ -6,6 +6,8 @@ import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClaimRequesterModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
@ -13,7 +15,9 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessScope;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClaimRepresentation;
import org.keycloak.util.Base64Url;
import org.keycloak.util.JsonSerialization;
@ -178,7 +182,9 @@ public class TokenManager {
}
}
AccessToken accessToken = initToken(realm, client, user);
ClaimRequesterModel claimRequesterModel = getClaimRequester(realm, client);
AccessToken accessToken = initToken(realm, claimRequesterModel, client, user);
accessToken.setRealmAccess(refreshToken.getRealmAccess());
accessToken.setResourceAccess(refreshToken.getResourceAccess());
return accessToken;
@ -188,6 +194,12 @@ public class TokenManager {
return createClientAccessToken(scopeParam, realm, client, user, new LinkedList<RoleModel>(), new MultivaluedHashMap<String, RoleModel>());
}
protected ClaimRequesterModel getClaimRequester(RealmModel realm, UserModel client) {
ClaimRequesterModel model = realm.getApplicationByName(client.getLoginName());
if (model != null) return model;
return realm.getOAuthClient(client.getLoginName());
}
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, UserModel client, UserModel user, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
AccessScope scopeMap = null;
@ -196,6 +208,7 @@ public class TokenManager {
Set<RoleModel> roleMappings = realm.getRoleMappings(user);
Set<RoleModel> scopeMappings = realm.getScopeMappings(client);
ClaimRequesterModel claimRequesterModel = getClaimRequester(realm, client);
ApplicationModel clientApp = realm.getApplicationByName(client.getLoginName());
Set<RoleModel> clientAppRoles = clientApp == null ? null : clientApp.getRoles();
if (clientAppRoles != null) scopeMappings.addAll(clientAppRoles);
@ -222,7 +235,7 @@ public class TokenManager {
}
}
AccessToken token = initToken(realm, client, user);
AccessToken token = initToken(realm, claimRequesterModel, client, user);
if (realmRolesRequested.size() > 0) {
for (RoleModel role : realmRolesRequested) {
@ -240,7 +253,42 @@ public class TokenManager {
return token;
}
protected AccessToken initToken(RealmModel realm, UserModel client, UserModel user) {
public void initClaims(IDToken token, ClaimRequesterModel model, UserModel user) {
if (ClaimMask.hasUsername(model.getAllowedClaimsMask())) {
token.setPreferredUsername(user.getLoginName());
}
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
token.setEmail(user.getEmail());
token.setEmailVerified(user.isEmailVerified());
}
if (ClaimMask.hasName(model.getAllowedClaimsMask())) {
token.setFamilyName(user.getLastName());
token.setGivenName(user.getFirstName());
StringBuilder fullName = new StringBuilder();
if (user.getFirstName() != null) fullName.append(user.getFirstName()).append(" ");
if (user.getLastName() != null) fullName.append(user.getLastName());
token.setName(fullName.toString());
}
}
protected IDToken initIDToken(RealmModel realm, ClaimRequesterModel claimer, UserModel client, UserModel user) {
IDToken token = new IDToken();
token.id(KeycloakModelUtils.generateId());
token.subject(user.getId());
token.audience(realm.getName());
token.issuedNow();
token.issuedFor(client.getLoginName());
token.issuer(realm.getName());
if (realm.getAccessTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
}
initClaims(token, claimer, user);
return token;
}
protected AccessToken initToken(RealmModel realm, ClaimRequesterModel claimer, UserModel client, UserModel user) {
AccessToken token = new AccessToken();
token.id(KeycloakModelUtils.generateId());
token.subject(user.getId());
@ -250,12 +298,12 @@ public class TokenManager {
token.issuer(realm.getName());
if (realm.getAccessTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
logger.info("Access Token expiration: " + token.getExpiration());
}
Set<String> allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null) {
token.setAllowedOrigins(allowedOrigins);
}
initClaims(token, claimer, user);
return token;
}
@ -324,6 +372,7 @@ public class TokenManager {
RealmModel realm;
AccessToken accessToken;
RefreshToken refreshToken;
IDToken idToken;
public AccessTokenResponseBuilder(RealmModel realm) {
this.realm = realm;
@ -354,8 +403,53 @@ public class TokenManager {
return this;
}
public AccessTokenResponseBuilder generateIDToken() {
if (accessToken == null) {
throw new IllegalStateException("accessToken not set");
}
idToken = new IDToken();
idToken.id(KeycloakModelUtils.generateId());
idToken.subject(accessToken.getSubject());
idToken.audience(realm.getName());
idToken.issuedNow();
idToken.issuedFor(accessToken.getIssuedFor());
idToken.issuer(accessToken.getIssuer());
if (realm.getAccessTokenLifespan() > 0) {
idToken.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
}
idToken.setPreferredUsername(accessToken.getPreferredUsername());
idToken.setGivenName(accessToken.getGivenName());
idToken.setMiddleName(accessToken.getMiddleName());
idToken.setFamilyName(accessToken.getFamilyName());
idToken.setName(accessToken.getName());
idToken.setNickName(accessToken.getNickName());
idToken.setGender(accessToken.getGender());
idToken.setPicture(accessToken.getPicture());
idToken.setProfile(accessToken.getProfile());
idToken.setWebsite(accessToken.getWebsite());
idToken.setBirthdate(accessToken.getBirthdate());
idToken.setEmail(accessToken.getEmail());
idToken.setEmailVerified(accessToken.getEmailVerified());
idToken.setLocale(accessToken.getLocale());
idToken.setFormattedAddress(accessToken.getFormattedAddress());
idToken.setAddress(accessToken.getAddress());
idToken.setStreetAddress(accessToken.getStreetAddress());
idToken.setLocality(accessToken.getLocality());
idToken.setRegion(accessToken.getRegion());
idToken.setPostalCode(accessToken.getPostalCode());
idToken.setCountry(accessToken.getCountry());
idToken.setPhoneNumber(accessToken.getPhoneNumber());
idToken.setPhoneNumberVerified(accessToken.getPhoneNumberVerified());
idToken.setZoneinfo(accessToken.getZoneinfo());
return this;
}
public AccessTokenResponse build() {
AccessTokenResponse res = new AccessTokenResponse();
if (idToken != null) {
String encodedToken = new JWSBuilder().jsonContent(idToken).rsa256(realm.getPrivateKey());
res.setIdToken(encodedToken);
}
if (accessToken != null) {
String encodedToken = new JWSBuilder().jsonContent(accessToken).rsa256(realm.getPrivateKey());
res.setToken(encodedToken);

View file

@ -159,7 +159,9 @@ public class TokenService {
}
String scope = form.getFirst("scope");
AccessTokenResponse res = tokenManager.responseBuilder(realm)
.generateAccessToken(scope, client, user).build();
.generateAccessToken(scope, client, user)
.generateIDToken()
.build();
return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
}
@ -188,6 +190,7 @@ public class TokenService {
AccessTokenResponse res = tokenManager.responseBuilder(realm)
.accessToken(accessToken)
.generateIDToken()
.generateRefreshToken().build();
return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
}
@ -410,6 +413,7 @@ public class TokenService {
logger.debug("accessRequest SUCCESS");
AccessTokenResponse res = tokenManager.responseBuilder(realm)
.accessToken(accessCode.getToken())
.generateIDToken()
.generateRefreshToken().build();
return Cors.add(request, Response.ok(res)).allowedOrigins(client).allowedMethods("POST").build();