Merge pull request #795 from patriot1burke/master

force post binding switch and saml docs
This commit is contained in:
Bill Burke 2014-10-22 15:19:02 -04:00
commit 4417f28c5a
8 changed files with 152 additions and 11 deletions

View file

@ -13,6 +13,7 @@
<!ENTITY JavascriptAdapter SYSTEM "modules/javascript-adapter.xml">
<!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY SAML SYSTEM "modules/saml.xml">
<!ENTITY SocialConfig SYSTEM "modules/social-config.xml">
<!ENTITY SocialFacebook SYSTEM "modules/social-facebook.xml">
<!ENTITY SocialGitHub SYSTEM "modules/social-github.xml">
@ -121,6 +122,7 @@ This one is short
&UserFederation;
&ExportImport;
&ServerCache;
&SAML;
&SecurityVulnerabilities;
&Clustering;
&Migration;

View file

@ -73,6 +73,9 @@
<listitem>
OpenID Connect Support.
</listitem>
<listitem>
SAML Support.
</listitem>
<listitem>
CORS Support
</listitem>
@ -89,13 +92,13 @@
Account Management console that allows users to manage their own account, view their open sessions, reset passwords, etc.
</listitem>
<listitem>
Deployable as a WAR, appliance, or on Openshift.
Deployable as a WAR, appliance, or on Openshift. Completely clusterable.
</listitem>
<listitem>
Multitenancy support. You can host and manage multiple realms for multiple organizations.
</listitem>
<listitem>
Supports JBoss AS7, EAP 6.x, Wildfly and JavaScript applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java deployments
Supports JBoss AS7, EAP 6.x, Wildfly and Pure JavaScript applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java deployments
</listitem>
</itemizedlist>
</para>

View file

@ -0,0 +1,97 @@
<chapter id="saml">
<title>SAML SSO</title>
<para>
Keycloak supports SAML 2.0 for registered applications. Both POST and Redirect bindings are supported. You can choose
to require client signature validation and can have the server sign and/or encrypt responses as well. We do not
yet support logout via redirects. All logouts happen via a background POST binding request to the application
that will be logged out. We do not support SAML 1.1 either. If you want support for either of those, please
log a JIRA request and we'll schedule it.
</para>
<para>
When you create an application in the admin console, you can choose which protocol the application will log in with.
In the application create screen, choose <literal>saml</literal> from the protocol list. After that there
are a bunch of configuration options. Here is a description of each item:
</para>
<para>
<variablelist>
<varlistentry>
<term>Include AuthnStatement</term>
<listitem>
<para>
SAML login responses may specify the authenticaiton method used (password, etc.) as well as
a timestamp of the login. Setting this to on will include that statement in the response document.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Multi-valued Roles</term>
<listitem>
<para>
If this switch is off, any user role mapings will have a corresponding attribute created for it.
If this switch is turn on, only one role attribute will be created, but it will have
multiple values within in.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Sign Documents</term>
<listitem>
<para>
When turned on, Keycloak will sign the document using the realm's private key.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Sign Assertions</term>
<listitem>
<para>
With the <literal>Sign Documents</literal> switch signs the whole document. With this setting
you just assign the assertions of the document.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Signature Algorithm</term>
<listitem>
<para>
Choose between a variety of algorithms for signing SAML documents.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Encrypt Assertions</term>
<listitem>
<para>
Encrypt assertions in SAML documents with the realm's private key. The AES algorithm is used
with a key size of 128 bits.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Client Signature Required</term>
<listitem>
<para>
Expect that documents coming from a client are signed. Keycloak will validate this signature
using the client keys set up in the <literal>Application Keys</literal> submenu item.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Force POST Binding</term>
<listitem>
<para>
By default, Keycloak will respond using the initial SAML binding of the original request. By turning
on this switch, you will force Keycloak to always respond using the SAML POST Binding even if the
original request was a the Redirect binding.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added
to the role attributes in the SAML document using their basic name. So, if you have multiple applicaiton roles
you might have name collisions. You can use the Scope Mapping menu item to control which role mappings are set
in the response.
</para>
</chapter>

View file

@ -152,4 +152,13 @@
At this point in time, there is no knowledge of any SQL injection vulnerabilities in Keycloak
</para>
</section>
<section>
<title>Limiting Scope</title>
<para>
Using the <literal>Scope</literal> menu in the admin console for oauth clients or applications, you can control
exactly which role mappings will be included within the token sent back to the client or application. This
allows you to limit the scope of permissions given to the application or client which is great if the client isn't
very trusted and is known to not being very careful with its tokens.
</para>
</section>
</chapter>

View file

@ -363,6 +363,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
$scope.samlAssertionSignature = false;
$scope.samlClientSignature = false;
$scope.samlEncrypt = false;
$scope.samlForcePostBinding = false;
if (!$scope.create) {
if (!application.attributes) {
application.attributes = {};
@ -442,6 +443,13 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
$scope.samlMultiValuedRoles = false;
}
}
if ($scope.application.attributes["saml.force.post.binding"]) {
if ($scope.application.attributes["saml.force.post.binding"] == "true") {
$scope.samlForcePostBinding = true;
} else {
$scope.samlForcePostBinding = false;
}
}
$scope.switchChange = function() {
$scope.changed = true;
@ -534,6 +542,12 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
} else {
$scope.application.attributes["saml.multivalued.roles"] = "false";
}
if ($scope.samlForcePostBinding == true) {
$scope.application.attributes["saml.force.post.binding"] = "true";
} else {
$scope.application.attributes["saml.force.post.binding"] = "false";
}
$scope.application.protocol = $scope.protocol;

View file

@ -111,6 +111,13 @@
</div>
<span tooltip-placement="right" tooltip="Will the client sign their saml requests and responses? And should they be validated?" class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
<label class="col-sm-2 control-label" for="samlForcePostBinding">Force POST Binding</label>
<div class="col-sm-6">
<input ng-model="samlForcePostBinding" ng-click="switchChange()" name="samlForcePostBinding" id="samlForcePostBinding" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Always use POST binding for responses." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="!application.bearerOnly">
<label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI <span class="required" data-ng-show="create">*</span></label>

View file

@ -39,6 +39,14 @@ public class SamlProtocol implements LoginProtocol {
public static final String SAML_BINDING = "saml_binding";
public static final String SAML_POST_BINDING = "post";
public static final String SAML_GET_BINDING = "get";
public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
public static final String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
public static final String SAML_AUTHNSTATEMENT = "saml.authnstatement";
public static final String SAML_MULTIVALUED_ROLES = "saml.multivalued.roles";
public static final String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
public static final String SAML_ENCRYPT = "saml.encrypt";
public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
public static final String REQUEST_ID = "REQUEST_ID";
protected KeycloakSession session;
@ -98,14 +106,15 @@ public class SamlProtocol implements LoginProtocol {
}
protected boolean isPostBinding(ClientSessionModel clientSession) {
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING));
ClientModel client = clientSession.getClient();
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || "true".equals(client.getAttribute(SAML_FORCE_POST_BINDING));
}
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
ClientModel client = clientSession.getClient();
String requestID = clientSession.getNote("REQUEST_ID");
String requestID = clientSession.getNote(REQUEST_ID);
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
String redirectUri = clientSession.getRedirectUri();
String responseIssuer = getResponseIssuer(realm);
@ -166,23 +175,23 @@ public class SamlProtocol implements LoginProtocol {
}
private boolean requiresRealmSignature(ClientModel client) {
return "true".equals(client.getAttribute("saml.server.signature"));
return "true".equals(client.getAttribute(SAML_SERVER_SIGNATURE));
}
private boolean requiresAssertionSignature(ClientModel client) {
return "true".equals(client.getAttribute("saml.assertion.signature"));
return "true".equals(client.getAttribute(SAML_ASSERTION_SIGNATURE));
}
private boolean includeAuthnStatement(ClientModel client) {
return "true".equals(client.getAttribute("saml.authnstatement"));
return "true".equals(client.getAttribute(SAML_AUTHNSTATEMENT));
}
private boolean multivaluedRoles(ClientModel client) {
return "true".equals(client.getAttribute("saml.multivalued.roles"));
return "true".equals(client.getAttribute(SAML_MULTIVALUED_ROLES));
}
public static SignatureAlgorithm getSignatureAlgorithm(ClientModel client) {
String alg = client.getAttribute("saml.signature.algorithm");
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
if (alg != null) {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
if (algorithm != null) return algorithm;
@ -191,7 +200,7 @@ public class SamlProtocol implements LoginProtocol {
}
private boolean requiresEncryption(ClientModel client) {
return "true".equals(client.getAttribute("saml.encrypt"));
return "true".equals(client.getAttribute(SAML_ENCRYPT));
}
public void initClaims(SALM2LoginResponseBuilder builder, ClientModel model, UserModel user) {

View file

@ -198,7 +198,7 @@ public class SamlService {
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
clientSession.setNote(SamlProtocol.SAML_BINDING, getBindingType());
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
clientSession.setNote(SamlProtocol.REQUEST_ID, requestAbstractType.getID());
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
if (response != null) return response;