Merge pull request #1961 from pedroigor/KEYCLOAK-2202
[KEYCLOAK-2202] - Removing LoginProtocol in order to reuse SAML settings.
This commit is contained in:
commit
35804aa0df
7 changed files with 106 additions and 144 deletions
|
@ -35,7 +35,6 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService;
|
||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||
|
@ -224,7 +223,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(getLoginProtocol());
|
||||
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||
|
@ -373,7 +372,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
}
|
||||
}
|
||||
|
||||
public class PostBindingProtocol extends BindingProtocol {
|
||||
protected class PostBindingProtocol extends BindingProtocol {
|
||||
|
||||
@Override
|
||||
protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
|
||||
|
@ -446,12 +445,12 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) {
|
||||
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||
protocol.setRealm(realm)
|
||||
.setHttpHeaders(request.getHttpHeaders())
|
||||
.setUriInfo(uriInfo)
|
||||
.setEventBuilder(event);
|
||||
return handleBrowserAuthenticationRequest(clientSession, protocol, isPassive);
|
||||
SamlProtocol samlProtocol = new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo);
|
||||
return newBrowserAuthentication(clientSession, isPassive, samlProtocol);
|
||||
}
|
||||
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) {
|
||||
return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -471,16 +470,6 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes("application/soap+xml")
|
||||
public Response soapBinding(InputStream inputStream) {
|
||||
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager);
|
||||
|
||||
ResteasyProviderFactory.getInstance().injectProperties(bindingService);
|
||||
|
||||
return bindingService.authenticate(inputStream);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("descriptor")
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
|
@ -537,7 +526,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(getLoginProtocol());
|
||||
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||
clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
|
||||
clientSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
|
||||
|
@ -555,8 +544,14 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
|
||||
}
|
||||
|
||||
protected String getLoginProtocol() {
|
||||
return SamlProtocol.LOGIN_PROTOCOL;
|
||||
@POST
|
||||
@Consumes("application/soap+xml")
|
||||
public Response soapBinding(InputStream inputStream) {
|
||||
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager);
|
||||
|
||||
ResteasyProviderFactory.getInstance().injectProperties(bindingService);
|
||||
|
||||
return bindingService.authenticate(inputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
package org.keycloak.protocol.saml.profile.ecp;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocolFactory;
|
||||
import org.keycloak.protocol.saml.profile.ecp.util.Soap;
|
||||
import org.keycloak.protocol.saml.profile.ecp.util.Soap.SoapMessageBuilder;
|
||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPHeaderElement;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class SamlEcpProfileProtocolFactory extends SamlProtocolFactory {
|
||||
|
||||
static final String ID = "saml-ecp-profile";
|
||||
|
||||
private static final String NS_PREFIX_PROFILE_ECP = "ecp";
|
||||
private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
|
||||
private static final String NS_PREFIX_SAML_ASSERTION = "saml";
|
||||
|
||||
@Override
|
||||
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||
return new SamlEcpProfileService(realm, event, authManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginProtocol create(KeycloakSession session) {
|
||||
return new SamlProtocol() {
|
||||
// method created to send a SOAP Binding response instead of a HTTP POST response
|
||||
@Override
|
||||
protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException {
|
||||
Document document = bindingBuilder.postBinding(samlDocument).getDocument();
|
||||
|
||||
try {
|
||||
SoapMessageBuilder messageBuilder = Soap.createMessage()
|
||||
.addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get())
|
||||
.addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get())
|
||||
.addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
|
||||
|
||||
createEcpResponseHeader(redirectUri, messageBuilder);
|
||||
createRequestAuthenticatedHeader(clientSession, messageBuilder);
|
||||
|
||||
messageBuilder.addToBody(document);
|
||||
|
||||
return messageBuilder.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while creating SAML response.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, SoapMessageBuilder messageBuilder) {
|
||||
ClientModel client = clientSession.getClient();
|
||||
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
|
||||
SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP);
|
||||
|
||||
ecpRequestAuthenticated.setMustUnderstand(true);
|
||||
ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next");
|
||||
}
|
||||
}
|
||||
|
||||
private void createEcpResponseHeader(String redirectUri, SoapMessageBuilder messageBuilder) throws SOAPException {
|
||||
SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP);
|
||||
|
||||
ecpResponseHeader.setMustUnderstand(true);
|
||||
ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
|
||||
ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
|
||||
return Soap.createMessage().addToBody(document).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException {
|
||||
return Soap.createFault().reason("Logout not supported.").build();
|
||||
}
|
||||
}.setSession(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,24 @@ import org.keycloak.models.AuthenticationFlowModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.protocol.saml.SamlService;
|
||||
import org.keycloak.protocol.saml.profile.ecp.util.Soap;
|
||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPHeaderElement;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
|
@ -20,6 +31,10 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class SamlEcpProfileService extends SamlService {
|
||||
|
||||
private static final String NS_PREFIX_PROFILE_ECP = "ecp";
|
||||
private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
|
||||
private static final String NS_PREFIX_SAML_ASSERTION = "saml";
|
||||
|
||||
public SamlEcpProfileService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||
super(realm, event, authManager);
|
||||
}
|
||||
|
@ -53,8 +68,63 @@ public class SamlEcpProfileService extends SamlService {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String getLoginProtocol() {
|
||||
return SamlEcpProfileProtocolFactory.ID;
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) {
|
||||
return super.newBrowserAuthentication(clientSession, isPassive, createEcpSamlProtocol());
|
||||
}
|
||||
|
||||
private SamlProtocol createEcpSamlProtocol() {
|
||||
return new SamlProtocol() {
|
||||
// method created to send a SOAP Binding response instead of a HTTP POST response
|
||||
@Override
|
||||
protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException {
|
||||
Document document = bindingBuilder.postBinding(samlDocument).getDocument();
|
||||
|
||||
try {
|
||||
Soap.SoapMessageBuilder messageBuilder = Soap.createMessage()
|
||||
.addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get())
|
||||
.addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get())
|
||||
.addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
|
||||
|
||||
createEcpResponseHeader(redirectUri, messageBuilder);
|
||||
createRequestAuthenticatedHeader(clientSession, messageBuilder);
|
||||
|
||||
messageBuilder.addToBody(document);
|
||||
|
||||
return messageBuilder.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error while creating SAML response.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, Soap.SoapMessageBuilder messageBuilder) {
|
||||
ClientModel client = clientSession.getClient();
|
||||
|
||||
if ("true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
|
||||
SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP);
|
||||
|
||||
ecpRequestAuthenticated.setMustUnderstand(true);
|
||||
ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next");
|
||||
}
|
||||
}
|
||||
|
||||
private void createEcpResponseHeader(String redirectUri, Soap.SoapMessageBuilder messageBuilder) throws SOAPException {
|
||||
SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP);
|
||||
|
||||
ecpResponseHeader.setMustUnderstand(true);
|
||||
ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
|
||||
ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
|
||||
return Soap.createMessage().addToBody(document).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException {
|
||||
return Soap.createFault().reason("Logout not supported.").build();
|
||||
}
|
||||
}.setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
org.keycloak.protocol.saml.SamlProtocolFactory
|
||||
org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileProtocolFactory
|
||||
org.keycloak.protocol.saml.SamlProtocolFactory
|
|
@ -810,10 +810,10 @@ public class AuthenticationProcessor {
|
|||
AuthenticationManager.evaluateRequiredActionTriggers(session, userSession, clientSession, connection, request, uriInfo, event, realm, clientSession.getAuthenticatedUser());
|
||||
}
|
||||
|
||||
public Response finishAuthentication() {
|
||||
public Response finishAuthentication(LoginProtocol protocol) {
|
||||
event.success();
|
||||
RealmModel realm = clientSession.getRealm();
|
||||
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event);
|
||||
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event, protocol);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ public abstract class AuthorizationEndpointBase {
|
|||
else
|
||||
return protocol.sendError(clientSession, Error.PASSIVE_INTERACTION_REQUIRED);
|
||||
} else {
|
||||
return processor.finishAuthentication();
|
||||
return processor.finishAuthentication(protocol);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
|
|
|
@ -382,6 +382,19 @@ public class AuthenticationManager {
|
|||
ClientSessionModel clientSession,
|
||||
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
|
||||
EventBuilder event) {
|
||||
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||
protocol.setRealm(realm)
|
||||
.setHttpHeaders(request.getHttpHeaders())
|
||||
.setUriInfo(uriInfo)
|
||||
.setEventBuilder(event);
|
||||
return redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event, protocol);
|
||||
|
||||
}
|
||||
|
||||
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
|
||||
ClientSessionModel clientSession,
|
||||
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
|
||||
EventBuilder event, LoginProtocol protocol) {
|
||||
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||
if (sessionCookie != null) {
|
||||
|
||||
|
@ -405,12 +418,6 @@ public class AuthenticationManager {
|
|||
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
|
||||
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
||||
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||
protocol.setRealm(realm)
|
||||
.setHttpHeaders(request.getHttpHeaders())
|
||||
.setUriInfo(uriInfo)
|
||||
.setEventBuilder(event);
|
||||
RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
|
||||
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue