Remove access token from access code, and create token when code is exchanged for token
This commit is contained in:
parent
3514d5cd78
commit
b196d0dded
9 changed files with 150 additions and 129 deletions
|
@ -1,6 +1,5 @@
|
|||
package org.keycloak.representations;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -10,15 +9,18 @@ import java.util.Set;
|
|||
*/
|
||||
public class AccessCode {
|
||||
protected String id;
|
||||
protected String clientId;
|
||||
protected String userId;
|
||||
protected String usernameUsed;
|
||||
protected String state;
|
||||
protected String sessionState;
|
||||
protected String redirectUri;
|
||||
protected boolean rememberMe;
|
||||
protected String authMethod;
|
||||
protected int timestamp;
|
||||
protected int expiration;
|
||||
protected AccessToken accessToken;
|
||||
protected Set<String> requiredActions;
|
||||
protected Set<String> requestedRoles;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -28,6 +30,22 @@ public class AccessCode {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
@ -36,6 +54,14 @@ public class AccessCode {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public String getSessionState() {
|
||||
return sessionState;
|
||||
}
|
||||
|
||||
public void setSessionState(String sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
@ -68,14 +94,6 @@ public class AccessCode {
|
|||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public AccessToken getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(AccessToken accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
@ -99,4 +117,12 @@ public class AccessCode {
|
|||
public void setUsernameUsed(String usernameUsed) {
|
||||
this.usernameUsed = usernameUsed;
|
||||
}
|
||||
|
||||
public Set<String> getRequestedRoles() {
|
||||
return requestedRoles;
|
||||
}
|
||||
|
||||
public void setRequestedRoles(Set<String> requestedRoles) {
|
||||
this.requestedRoles = requestedRoles;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.representations.AccessCode;
|
||||
|
@ -33,29 +35,45 @@ public class AccessCodeEntry {
|
|||
}
|
||||
|
||||
public UserModel getUser() {
|
||||
return keycloakSession.users().getUserById(accessCode.getAccessToken().getSubject(), realm);
|
||||
return keycloakSession.users().getUserById(accessCode.getUserId(), realm);
|
||||
}
|
||||
|
||||
public String getSessionState() {
|
||||
return accessCode.getAccessToken().getSessionState();
|
||||
return accessCode.getSessionState();
|
||||
}
|
||||
|
||||
public void setSessionState(String state) {
|
||||
accessCode.setSessionState(state);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return accessCode.getExpiration() != 0 && Time.currentTime() > accessCode.getExpiration();
|
||||
}
|
||||
|
||||
public AccessToken getToken() {
|
||||
return accessCode.getAccessToken();
|
||||
public Set<RoleModel> getRequestedRoles() {
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
for (String roleId : accessCode.getRequestedRoles()) {
|
||||
RoleModel role = realm.getRoleById(roleId);
|
||||
if (role == null) {
|
||||
new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid role " + roleId);
|
||||
}
|
||||
requestedRoles.add(realm.getRoleById(roleId));
|
||||
}
|
||||
return requestedRoles;
|
||||
}
|
||||
|
||||
public ClientModel getClient() {
|
||||
return realm.findClient(accessCode.getAccessToken().getIssuedFor());
|
||||
return realm.findClient(accessCode.getClientId());
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return accessCode.getState();
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
accessCode.setState(state);
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return accessCode.getRedirectUri();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
|
@ -24,12 +23,9 @@ import org.keycloak.representations.IDToken;
|
|||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
@ -80,21 +76,24 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, KeycloakSession keycloakSession, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
List<RoleModel> realmRolesRequested = new LinkedList<RoleModel>();
|
||||
MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedMapImpl<String, RoleModel>();
|
||||
|
||||
AccessToken token = createClientAccessToken(scopeParam, realm, client, user, session, realmRolesRequested, resourceRolesRequested);
|
||||
if (session != null) token.setSessionState(session.getId());
|
||||
AccessCode code = new AccessCode();
|
||||
code.setId(UUID.randomUUID().toString() + System.currentTimeMillis());
|
||||
code.setAccessToken(token);
|
||||
code.setClientId(client.getClientId());
|
||||
code.setUserId(user.getId());
|
||||
code.setTimestamp(Time.currentTime());
|
||||
code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
code.setState(state);
|
||||
code.setSessionState(session != null ? session.getId() : null);
|
||||
code.setRedirectUri(redirect);
|
||||
code.setState(state);
|
||||
|
||||
Set<String> requestedRoles = new HashSet<String>();
|
||||
for (RoleModel r : getAccess(scopeParam, client, user)) {
|
||||
requestedRoles.add(r.getId());
|
||||
}
|
||||
code.setRequestedRoles(requestedRoles);
|
||||
|
||||
AccessCodeEntry entry = new AccessCodeEntry(keycloakSession, realm, code);
|
||||
return entry;
|
||||
|
||||
}
|
||||
|
||||
public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
|
||||
|
@ -142,11 +141,54 @@ public class TokenManager {
|
|||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
|
||||
}
|
||||
|
||||
verifyAccess(refreshToken, realm, client, user);
|
||||
|
||||
AccessToken accessToken = initToken(realm, client, user, userSession);
|
||||
accessToken.setRealmAccess(refreshToken.getRealmAccess());
|
||||
accessToken.setResourceAccess(refreshToken.getResourceAccess());
|
||||
|
||||
// only refresh session if next token refresh will be after idle timeout
|
||||
if (currentTime + realm.getAccessTokenLifespan() > userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout()) {
|
||||
userSession.setLastSessionRefresh(currentTime);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
AccessToken token = initToken(realm, client, user, session);
|
||||
for (RoleModel role : requestedRoles) {
|
||||
addComposites(token, role);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
|
||||
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
|
||||
Set<RoleModel> roleMappings = user.getRoleMappings();
|
||||
Set<RoleModel> scopeMappings = client.getScopeMappings();
|
||||
if (client instanceof ApplicationModel) {
|
||||
scopeMappings.addAll(((ApplicationModel) client).getRoles());
|
||||
}
|
||||
|
||||
for (RoleModel role : roleMappings) {
|
||||
for (RoleModel desiredRole : scopeMappings) {
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
applyScope(role, desiredRole, visited, requestedRoles);
|
||||
}
|
||||
}
|
||||
|
||||
return requestedRoles;
|
||||
}
|
||||
|
||||
public void verifyAccess(AccessToken token, RealmModel realm, ClientModel client, UserModel user) throws OAuthErrorException {
|
||||
ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
|
||||
|
||||
|
||||
if (refreshToken.getRealmAccess() != null) {
|
||||
for (String roleName : refreshToken.getRealmAccess().getRoles()) {
|
||||
if (token.getRealmAccess() != null) {
|
||||
for (String roleName : token.getRealmAccess().getRoles()) {
|
||||
RoleModel role = realm.getRole(roleName);
|
||||
if (role == null) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
|
||||
|
@ -159,8 +201,8 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (refreshToken.getResourceAccess() != null) {
|
||||
for (Map.Entry<String, AccessToken.Access> entry : refreshToken.getResourceAccess().entrySet()) {
|
||||
if (token.getResourceAccess() != null) {
|
||||
for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) {
|
||||
ApplicationModel app = realm.getApplicationByName(entry.getKey());
|
||||
if (app == null) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName());
|
||||
|
@ -180,67 +222,6 @@ public class TokenManager {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken accessToken = initToken(realm, client, user, userSession);
|
||||
accessToken.setRealmAccess(refreshToken.getRealmAccess());
|
||||
accessToken.setResourceAccess(refreshToken.getResourceAccess());
|
||||
|
||||
// only refresh session if next token refresh will be after idle timeout
|
||||
if (currentTime + realm.getAccessTokenLifespan() > userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout()) {
|
||||
userSession.setLastSessionRefresh(currentTime);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
return createClientAccessToken(scopeParam, realm, client, user, session, new LinkedList<RoleModel>(), new MultivaluedMapImpl<String, RoleModel>());
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
|
||||
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
|
||||
|
||||
Set<RoleModel> roleMappings = user.getRoleMappings();
|
||||
Set<RoleModel> scopeMappings = client.getScopeMappings();
|
||||
ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
|
||||
Set<RoleModel> clientAppRoles = clientApp == null ? null : clientApp.getRoles();
|
||||
if (clientAppRoles != null) scopeMappings.addAll(clientAppRoles);
|
||||
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
|
||||
for (RoleModel role : roleMappings) {
|
||||
if (clientApp != null && role.getContainer().equals(clientApp)) requestedRoles.add(role);
|
||||
for (RoleModel desiredRole : scopeMappings) {
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
applyScope(role, desiredRole, visited, requestedRoles);
|
||||
}
|
||||
}
|
||||
|
||||
for (RoleModel role : requestedRoles) {
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
realmRolesRequested.add(role);
|
||||
} else if (role.getContainer() instanceof ApplicationModel) {
|
||||
ApplicationModel app = (ApplicationModel)role.getContainer();
|
||||
resourceRolesRequested.add(app.getName(), role);
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken token = initToken(realm, client, user, session);
|
||||
|
||||
if (realmRolesRequested.size() > 0) {
|
||||
for (RoleModel role : realmRolesRequested) {
|
||||
addComposites(token, role);
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceRolesRequested.size() > 0) {
|
||||
for (List<RoleModel> roles : resourceRolesRequested.values()) {
|
||||
for (RoleModel role : roles) {
|
||||
addComposites(token, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public void initClaims(IDToken token, ClientModel model, UserModel user) {
|
||||
|
@ -363,7 +344,8 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
accessToken = createClientAccessToken(scopeParam, realm, client, user, session);
|
||||
Set<RoleModel> requestedRoles = getAccess(scopeParam, client, user);
|
||||
accessToken = createClientAccessToken(requestedRoles, realm, client, user, session);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,7 @@ public class RequiredActionsService {
|
|||
// Password reset through email won't have an associated session
|
||||
if (accessCode.getSessionState() == null) {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserById(accessCode.getUser().getId(), realm), clientConnection.getRemoteAddr());
|
||||
accessCode.getToken().setSessionState(userSession.getId());
|
||||
accessCode.setSessionState(userSession.getId());
|
||||
audit.session(userSession);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
@ -641,14 +642,6 @@ public class TokenService {
|
|||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
if (!accessCode.getToken().isActive()) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
audit.user(accessCode.getUser());
|
||||
audit.session(accessCode.getSessionState());
|
||||
|
@ -698,8 +691,20 @@ public class TokenService {
|
|||
|
||||
userSession.associateClient(client);
|
||||
|
||||
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
|
||||
|
||||
try {
|
||||
tokenManager.verifyAccess(token, realm, client, user);
|
||||
} catch (OAuthErrorException e) {
|
||||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, e.getError());
|
||||
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
|
||||
}
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.accessToken(accessCode.getToken())
|
||||
.accessToken(token)
|
||||
.generateIDToken()
|
||||
.generateRefreshToken().build();
|
||||
|
||||
|
|
|
@ -157,32 +157,22 @@ public class OAuthFlows {
|
|||
|
||||
if (!isResource) {
|
||||
accessCode.resetExpiration();
|
||||
List<RoleModel> realmRolesRequested = new LinkedList<RoleModel>();
|
||||
MultivaluedMap<String, RoleModel> appRolesRequested = new MultivaluedMapImpl<String, RoleModel>();
|
||||
if (accessCode.getToken().getRealmAccess() != null) {
|
||||
if (accessCode.getToken().getRealmAccess().getRoles() != null) {
|
||||
for (String role : accessCode.getToken().getRealmAccess().getRoles()) {
|
||||
RoleModel roleModel = realm.getRole(role);
|
||||
if (roleModel != null) realmRolesRequested.add(roleModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accessCode.getToken().getResourceAccess().size() > 0) {
|
||||
for (Map.Entry<String, AccessToken.Access> entry : accessCode.getToken().getResourceAccess().entrySet()) {
|
||||
ApplicationModel app = realm.getApplicationByName(entry.getKey());
|
||||
if (app == null) continue;
|
||||
if (entry.getValue().getRoles() != null) {
|
||||
for (String role : entry.getValue().getRoles()) {
|
||||
RoleModel roleModel = app.getRole(role);
|
||||
if (roleModel != null) appRolesRequested.add(entry.getKey(), roleModel);
|
||||
}
|
||||
|
||||
}
|
||||
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
|
||||
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
|
||||
for (RoleModel r : accessCode.getRequestedRoles()) {
|
||||
if (r.getContainer() instanceof RealmModel) {
|
||||
realmRoles.add(r);
|
||||
} else {
|
||||
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
|
||||
}
|
||||
}
|
||||
return Flows.forms(this.session, realm, uriInfo).setAccessCode(accessCode.getCode()).
|
||||
setAccessRequest(realmRolesRequested, appRolesRequested).
|
||||
setClient(client).createOAuthGrant();
|
||||
|
||||
return Flows.forms(this.session, realm, uriInfo)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.setAccessRequest(realmRoles, resourceRoles)
|
||||
.setClient(client)
|
||||
.createOAuthGrant();
|
||||
}
|
||||
|
||||
if (redirect != null) {
|
||||
|
|
|
@ -101,7 +101,7 @@ public class AdapterTest {
|
|||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession);
|
||||
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
|
||||
return tm.encodeToken(adminRealm, token);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
|
|
@ -88,7 +88,7 @@ public class RelativeUriAdapterTest {
|
|||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession);
|
||||
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
|
||||
adminToken = tm.encodeToken(adminRealm, token);
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ public class AdminAPITest {
|
|||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession);
|
||||
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
|
||||
return tm.encodeToken(adminRealm, token);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
|
Loading…
Reference in a new issue