SAML IDP Initiated login
This commit is contained in:
parent
d74d93a522
commit
57cfbb3770
12 changed files with 1987 additions and 1876 deletions
|
@ -204,6 +204,13 @@
|
|||
</div>
|
||||
<kc-tooltip>If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overiden for each binding and service in the Fine Grain SAML Endpoint Configuration.</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
|
||||
<label class="col-md-2 control-label" for="urlReferenceName">IDP Initiated SSO URL Name</label>
|
||||
<div class="col-sm-6">
|
||||
<input ng-model="client.attributes.saml_idp_initiated_sso_url_name" class="form-control" type="text" name="urlReferenceName" id="urlReferenceName" />
|
||||
</div>
|
||||
<kc-tooltip>URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly && !create && protocol == 'openid-connect'">
|
||||
<label class="col-md-2 control-label" for="newWebOrigin">Web Origins</label>
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ bypassKerberos=Your browser is not set up for Kerberos login. Please click cont
|
|||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
recaptchaFailed=Invalid Recaptcha
|
||||
recaptchaNotConfigured=Recaptcha is required, but not configured
|
||||
consentDenied=Consent denied.
|
||||
|
||||
registerWithTitle=Registrierung bei {0}
|
||||
registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
|
||||
|
|
|
@ -37,6 +37,7 @@ termsTitle=Terms and Conditions
|
|||
termsTitleHtml=Terms and Conditions
|
||||
recaptchaFailed=Invalid Recaptcha
|
||||
recaptchaNotConfigured=Recaptcha is required, but not configured
|
||||
consentDenied=Consent denied.
|
||||
|
||||
noAccount=New user?
|
||||
username=Username
|
||||
|
|
|
@ -16,6 +16,7 @@ kerberosNotConfigured=Kerberos Not Configured
|
|||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
recaptchaFailed=Invalid Recaptcha
|
||||
recaptchaNotConfigured=Recaptcha is required, but not configured
|
||||
consentDenied=Consent denied.
|
||||
|
||||
registerWithTitle=Registrati come {0}
|
||||
registerWithTitleHtml=Registrati come <strong>{0}</strong>
|
||||
|
|
|
@ -16,6 +16,7 @@ kerberosNotConfigured=Kerberos Not Configured
|
|||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
recaptchaFailed=Invalid Recaptcha
|
||||
recaptchaNotConfigured=Recaptcha is required, but not configured
|
||||
consentDenied=Consent denied.
|
||||
|
||||
registerWithTitle=Registre-se com {0}
|
||||
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
|
||||
|
|
0
model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
Normal file → Executable file
0
model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
Normal file → Executable file
File diff suppressed because it is too large
Load diff
|
@ -40,13 +40,17 @@ import org.w3c.dom.Document;
|
|||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -55,6 +59,7 @@ import java.util.UUID;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlProtocol implements LoginProtocol {
|
||||
public static final String SAML_IDP_INITIATED_SSO_URL_NAME = "saml_idp_initiated_sso_url_name";
|
||||
protected static final Logger logger = Logger.getLogger(SamlProtocol.class);
|
||||
|
||||
|
||||
|
@ -71,6 +76,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
public static final String SAML_NAME_ID_FORMAT_ATTRIBUTE = "saml_name_id_format";
|
||||
public static final String LOGIN_PROTOCOL = "saml";
|
||||
public static final String SAML_BINDING = "saml_binding";
|
||||
public static final String SAML_IDP_INITIATED_LOGIN = "saml_idp_initiated_login";
|
||||
public static final String SAML_POST_BINDING = "post";
|
||||
public static final String SAML_REDIRECT_BINDING = "get";
|
||||
public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
|
||||
|
@ -134,9 +140,19 @@ public class SamlProtocol implements LoginProtocol {
|
|||
|
||||
@Override
|
||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||
Response error = getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
session.sessions().removeClientSession(realm, clientSession);
|
||||
return error;
|
||||
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
|
||||
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("realm", realm.getName());
|
||||
params.put("protocol", LOGIN_PROTOCOL);
|
||||
params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
|
||||
session.sessions().removeClientSession(realm, clientSession);
|
||||
URI redirect = builder.buildFromMap(params);
|
||||
return Response.status(302).location(redirect).build();
|
||||
} else {
|
||||
session.sessions().removeClientSession(realm, clientSession);
|
||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
}
|
||||
}
|
||||
|
||||
protected String getResponseIssuer(RealmModel realm) {
|
||||
|
@ -426,7 +442,11 @@ public class SamlProtocol implements LoginProtocol {
|
|||
|
||||
@Override
|
||||
public Response consentDenied(ClientSessionModel clientSession) {
|
||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
|
||||
return ErrorPage.error(session, Messages.CONSENT_DENIED);
|
||||
} else {
|
||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLogoutServiceUrl(UriInfo uriInfo, ClientModel client, String bindingType) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.protocol.saml;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
|
@ -42,6 +43,7 @@ import javax.ws.rs.FormParam;
|
|||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
@ -245,8 +247,8 @@ public class SamlService {
|
|||
} else {
|
||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
}
|
||||
if (redirect == null && client instanceof ClientModel) {
|
||||
redirect = ((ClientModel) client).getManagementUrl();
|
||||
if (redirect == null) {
|
||||
redirect = client.getManagementUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -283,40 +285,8 @@ public class SamlService {
|
|||
return newBrowserAuthentication(clientSession);
|
||||
}
|
||||
|
||||
private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
||||
logger.debug("Automatically redirect to identity provider: " + providerId);
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isAuthenticateByDefault()) {
|
||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
|
||||
}
|
||||
}
|
||||
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setClientSession(clientSession)
|
||||
.setFlowId(flowId)
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setProtector(authManager.getProtector())
|
||||
.setRealm(realm)
|
||||
.setSession(session)
|
||||
.setUriInfo(uriInfo)
|
||||
.setRequest(request);
|
||||
|
||||
try {
|
||||
return processor.authenticate();
|
||||
} catch (Exception e) {
|
||||
return processor.handleBrowserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getBindingType(AuthnRequestType requestAbstractType) {
|
||||
|
@ -515,6 +485,42 @@ public class SamlService {
|
|||
}
|
||||
|
||||
|
||||
private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
||||
logger.debug("Automatically redirect to identity provider: " + providerId);
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isAuthenticateByDefault()) {
|
||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
|
||||
}
|
||||
}
|
||||
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setClientSession(clientSession)
|
||||
.setFlowId(flowId)
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setProtector(authManager.getProtector())
|
||||
.setRealm(realm)
|
||||
.setSession(session)
|
||||
.setUriInfo(uriInfo)
|
||||
.setRequest(request);
|
||||
|
||||
try {
|
||||
return processor.authenticate();
|
||||
} catch (Exception e) {
|
||||
return processor.handleBrowserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@GET
|
||||
|
@ -552,4 +558,60 @@ public class SamlService {
|
|||
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("clients/{client}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName) {
|
||||
event.event(EventType.LOGIN);
|
||||
ClientModel client = null;
|
||||
for (ClientModel c : realm.getClients()) {
|
||||
String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
|
||||
if (urlName == null) continue;
|
||||
if (urlName.equals(clientUrlName)) {
|
||||
client = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
|
||||
}
|
||||
if (client.getManagementUrl() == null
|
||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
|
||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
|
||||
logger.error("SAML assertion consumer url not set up");
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
||||
}
|
||||
|
||||
String bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
if (client.getManagementUrl() == null
|
||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
|
||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
|
||||
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
|
||||
}
|
||||
|
||||
String redirect = null;
|
||||
if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
|
||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
} else {
|
||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
}
|
||||
if (redirect == null) {
|
||||
redirect = client.getManagementUrl();
|
||||
}
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
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);
|
||||
clientSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
|
||||
clientSession.setRedirectUri(redirect);
|
||||
|
||||
|
||||
return newBrowserAuthentication(clientSession);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -179,4 +179,6 @@ public class Messages {
|
|||
public static final String IDENTITY_PROVIDER_LOGIN_FAILURE = "identityProviderLoginFailure";
|
||||
|
||||
public static final String FAILED_LOGOUT = "failedLogout";
|
||||
|
||||
public static final String CONSENT_DENIED="consentDenied";
|
||||
}
|
||||
|
|
|
@ -143,6 +143,10 @@ public class SamlBindingTest {
|
|||
Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void ideTesting() throws Exception {
|
||||
Thread.sleep(100000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostSimpleLoginLogout() {
|
||||
|
@ -155,6 +159,17 @@ public class SamlBindingTest {
|
|||
driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
|
||||
checkLoggedOut("http://localhost:8081/sales-post/");
|
||||
}
|
||||
@Test
|
||||
public void testPostSimpleLoginLogoutIdpInitiated() {
|
||||
driver.navigate().to("http://localhost:8081/auth/realms/demo/protocol/saml/clients/sales-post");
|
||||
loginPage.login("bburke", "password");
|
||||
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post/");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("bburke"));
|
||||
driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
|
||||
checkLoggedOut("http://localhost:8081/sales-post/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostSignedLoginLogout() {
|
||||
driver.navigate().to("http://localhost:8081/sales-post-sig/");
|
||||
|
|
|
@ -47,7 +47,8 @@
|
|||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post/",
|
||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post/",
|
||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post/",
|
||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post/"
|
||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post/",
|
||||
"saml_idp_initiated_sso_url_name": "sales-post"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue