cookie login
This commit is contained in:
parent
5f10102c39
commit
fa63da7e06
8 changed files with 88 additions and 16 deletions
|
@ -11,8 +11,8 @@ import java.util.Set;
|
||||||
public class RealmRepresentation {
|
public class RealmRepresentation {
|
||||||
protected String self; // link
|
protected String self; // link
|
||||||
protected String realm;
|
protected String realm;
|
||||||
protected long tokenLifespan;
|
protected int tokenLifespan;
|
||||||
protected long accessCodeLifespan;
|
protected int accessCodeLifespan;
|
||||||
protected boolean enabled;
|
protected boolean enabled;
|
||||||
protected boolean sslNotRequired;
|
protected boolean sslNotRequired;
|
||||||
protected boolean cookieLoginAllowed;
|
protected boolean cookieLoginAllowed;
|
||||||
|
@ -98,11 +98,11 @@ public class RealmRepresentation {
|
||||||
this.cookieLoginAllowed = cookieLoginAllowed;
|
this.cookieLoginAllowed = cookieLoginAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTokenLifespan() {
|
public int getTokenLifespan() {
|
||||||
return tokenLifespan;
|
return tokenLifespan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTokenLifespan(long tokenLifespan) {
|
public void setTokenLifespan(int tokenLifespan) {
|
||||||
this.tokenLifespan = tokenLifespan;
|
this.tokenLifespan = tokenLifespan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,11 +138,11 @@ public class RealmRepresentation {
|
||||||
this.requiredCredentials = requiredCredentials;
|
this.requiredCredentials = requiredCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAccessCodeLifespan() {
|
public int getAccessCodeLifespan() {
|
||||||
return accessCodeLifespan;
|
return accessCodeLifespan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccessCodeLifespan(long accessCodeLifespan) {
|
public void setAccessCodeLifespan(int accessCodeLifespan) {
|
||||||
this.accessCodeLifespan = accessCodeLifespan;
|
this.accessCodeLifespan = accessCodeLifespan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"realm" : "demo",
|
"realm" : "demo",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
"auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/auth/request",
|
"auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login",
|
||||||
"code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes",
|
"code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes",
|
||||||
"ssl-not-required" : true,
|
"ssl-not-required" : true,
|
||||||
"client-id" : "product-portal",
|
"client-id" : "product-portal",
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"enabled" : true,
|
"enabled" : true,
|
||||||
"tokenLifespan" : 6000,
|
"tokenLifespan" : 6000,
|
||||||
"accessCodeLifespan" : 30,
|
"accessCodeLifespan" : 30,
|
||||||
|
"sslNotRequired" : true,
|
||||||
|
"cookieLoginAllowed" : true,
|
||||||
"privateKey" : "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
"privateKey" : "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||||
"publicKey" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
"publicKey" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
"requiredCredentials" : [
|
"requiredCredentials" : [
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
|
@ -14,8 +16,10 @@ import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
||||||
import org.picketlink.idm.model.User;
|
import org.picketlink.idm.model.User;
|
||||||
|
|
||||||
import javax.ws.rs.NotAuthorizedException;
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.NewCookie;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -41,6 +45,39 @@ public class AuthenticationManager {
|
||||||
return realm.isRealmAdmin(user);
|
return realm.isRealmAdmin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void expireIdentityCookie(Cookie cookie) {
|
||||||
|
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
||||||
|
if (response == null) return;
|
||||||
|
NewCookie expireIt = new NewCookie(cookie.getName(), "", cookie.getPath(), null, "Expiring cookie", 0, false);
|
||||||
|
response.addNewCookie(expireIt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User authenticateIdentityCookie(RealmModel realm, HttpHeaders headers) {
|
||||||
|
Cookie cookie = headers.getCookies().get(TokenManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||||
|
if (cookie == null) return null;
|
||||||
|
|
||||||
|
String tokenString = cookie.getValue();
|
||||||
|
try {
|
||||||
|
SkeletonKeyToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getId());
|
||||||
|
if (!token.isActive()) {
|
||||||
|
logger.info("identity cookie expired");
|
||||||
|
expireIdentityCookie(cookie);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
User user = realm.getIdm().getUser(token.getPrincipal());
|
||||||
|
if (user == null || !user.isEnabled()) {
|
||||||
|
logger.info("Unknown user in identity cookie");
|
||||||
|
expireIdentityCookie(cookie);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} catch (VerificationException e) {
|
||||||
|
logger.info("Failed to verify identity cookie", e);
|
||||||
|
expireIdentityCookie(cookie);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public User authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
|
public User authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
|
||||||
String tokenString = null;
|
String tokenString = null;
|
||||||
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
|
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
|
||||||
|
|
|
@ -7,12 +7,17 @@ import org.keycloak.representations.SkeletonKeyScope;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
import org.keycloak.services.models.RealmModel;
|
import org.keycloak.services.models.RealmModel;
|
||||||
import org.keycloak.services.models.ResourceModel;
|
import org.keycloak.services.models.ResourceModel;
|
||||||
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
import org.keycloak.services.resources.TokenService;
|
||||||
import org.picketlink.idm.model.User;
|
import org.picketlink.idm.model.User;
|
||||||
|
|
||||||
import javax.ws.rs.ForbiddenException;
|
import javax.ws.rs.ForbiddenException;
|
||||||
|
import javax.ws.rs.core.NewCookie;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -26,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
*/
|
*/
|
||||||
public class TokenManager {
|
public class TokenManager {
|
||||||
|
|
||||||
|
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||||
protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
|
protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
|
||||||
|
|
||||||
public void clearAccessCodes() {
|
public void clearAccessCodes() {
|
||||||
|
@ -36,6 +42,15 @@ public class TokenManager {
|
||||||
return accessCodeMap.remove(key);
|
return accessCodeMap.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NewCookie createLoginCookie(RealmModel realm, User user, UriInfo uriInfo) {
|
||||||
|
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
|
||||||
|
String encoded = encodeToken(realm, identityToken);
|
||||||
|
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
|
||||||
|
boolean secureOnly = !realm.isSslNotRequired();
|
||||||
|
NewCookie cookie = new NewCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, uri.getPath(), null, null, realm.getTokenLifespan(), secureOnly, true);
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
public String createAccessCode(String scopeParam, RealmModel realm, User client, User user)
|
public String createAccessCode(String scopeParam, RealmModel realm, User client, User user)
|
||||||
{
|
{
|
||||||
SkeletonKeyToken token = null;
|
SkeletonKeyToken token = null;
|
||||||
|
|
|
@ -114,20 +114,20 @@ public class RealmModel {
|
||||||
realmAgent.setAttribute(new Attribute<Boolean>(REALM_IS_REGISTRATION_ALLOWED, registrationAllowed));
|
realmAgent.setAttribute(new Attribute<Boolean>(REALM_IS_REGISTRATION_ALLOWED, registrationAllowed));
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTokenLifespan() {
|
public int getTokenLifespan() {
|
||||||
return (Long) realmAgent.getAttribute(REALM_TOKEN_LIFESPAN).getValue();
|
return (Integer) realmAgent.getAttribute(REALM_TOKEN_LIFESPAN).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTokenLifespan(long tokenLifespan) {
|
public void setTokenLifespan(int tokenLifespan) {
|
||||||
realmAgent.setAttribute(new Attribute<Long>(REALM_TOKEN_LIFESPAN, tokenLifespan));
|
realmAgent.setAttribute(new Attribute<Integer>(REALM_TOKEN_LIFESPAN, tokenLifespan));
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAccessCodeLifespan() {
|
public int getAccessCodeLifespan() {
|
||||||
return (Long) realmAgent.getAttribute(REALM_ACCESS_CODE_LIFESPAN).getValue();
|
return (Integer) realmAgent.getAttribute(REALM_ACCESS_CODE_LIFESPAN).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccessCodeLifespan(long accessCodeLifespan) {
|
public void setAccessCodeLifespan(int accessCodeLifespan) {
|
||||||
realmAgent.setAttribute(new Attribute<Long>(REALM_ACCESS_CODE_LIFESPAN, accessCodeLifespan));
|
realmAgent.setAttribute(new Attribute<Integer>(REALM_ACCESS_CODE_LIFESPAN, accessCodeLifespan));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPublicKeyPem() {
|
public String getPublicKeyPem() {
|
||||||
|
|
|
@ -51,6 +51,10 @@ public class RealmsResource {
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UriBuilder realmBaseUrl(UriInfo uriInfo) {
|
||||||
|
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getRealmResource");
|
||||||
|
}
|
||||||
|
|
||||||
@Path("{realm}/tokens")
|
@Path("{realm}/tokens")
|
||||||
public TokenService getTokenService(@PathParam("realm") String id) {
|
public TokenService getTokenService(@PathParam("realm") String id) {
|
||||||
RealmManager realmManager = new RealmManager(identitySession);
|
RealmManager realmManager = new RealmManager(identitySession);
|
||||||
|
|
|
@ -31,6 +31,7 @@ import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.NewCookie;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.SecurityContext;
|
import javax.ws.rs.core.SecurityContext;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -208,10 +209,18 @@ public class TokenService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return redirectAccessCode(scopeParam, state, redirect, client, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response redirectAccessCode(String scopeParam, String state, String redirect, User client, User user) {
|
||||||
String accessCode = tokenManager.createAccessCode(scopeParam, realm, client, user);
|
String accessCode = tokenManager.createAccessCode(scopeParam, realm, client, user);
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", accessCode);
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", accessCode);
|
||||||
if (state != null) redirectUri.queryParam("state", state);
|
if (state != null) redirectUri.queryParam("state", state);
|
||||||
return Response.status(302).location(redirectUri.build()).build();
|
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||||
|
if (realm.isCookieLoginAllowed()) {
|
||||||
|
location.cookie(tokenManager.createLoginCookie(realm, user, uriInfo));
|
||||||
|
}
|
||||||
|
return location.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("access/codes")
|
@Path("access/codes")
|
||||||
|
@ -380,6 +389,11 @@ public class TokenService {
|
||||||
identitySession.close();
|
identitySession.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User user = authManager.authenticateIdentityCookie(realm, headers);
|
||||||
|
if (user != null) {
|
||||||
|
return redirectAccessCode(scopeParam, state, redirect, client, user);
|
||||||
|
}
|
||||||
// todo make sure client is allowed to request a login
|
// todo make sure client is allowed to request a login
|
||||||
|
|
||||||
forwardToLoginForm(redirect, clientId, scopeParam, state);
|
forwardToLoginForm(redirect, clientId, scopeParam, state);
|
||||||
|
|
Loading…
Reference in a new issue