From d7bafd54fa9aacb5e7dbaeb20982c256eee6966c Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 18 Apr 2016 11:40:41 -0400 Subject: [PATCH 001/194] first commit --- .gitattributes | 20 ++++++++++++++++++++ .gitignore | 47 +++++++++++++++++++++++++++++++++++++++++++++++ README.adoc | 10 ++++++++++ 3 files changed, 77 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100755 README.adoc diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..2b70adf8d8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +* text=auto + +*.html text eol=lf +*.java text eol=lf +*.js text eol=lf +*.json text eol=lf +*.jsp text eol=lf +*.md text eol=lf +*.properties text eol=lf +*.svg text auto +*.xml text eol=lf +*.xsl text eol=lf + +*.png binary +*.jpg binary +*.gif binary +*.ttf binary +*.eot binary +*.otf binary +*.woff binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..44612eb8b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Intellij +################### +.idea +*.iml + +# Eclipse # +########### +.project +.settings +.classpath + +# NetBeans # +############ +nbactions.xml +nb-configuration.xml +catalog.xml + +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log + +# Maven # +######### +target + diff --git a/README.adoc b/README.adoc new file mode 100755 index 0000000000..aa7e76f843 --- /dev/null +++ b/README.adoc @@ -0,0 +1,10 @@ + +Keycloak Client Applications Guide +====================== + +image:images/keycloak_logo.png[alt="Keycloak"] + +*Keycloak* _Documentation_ for {{book.versions.swarm}} + +http://www.keycloak.org + From 8a00945dc2bd11859a64baa9013342165fd95fb8 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 18 Apr 2016 15:10:32 -0400 Subject: [PATCH 002/194] initial import --- README.adoc | 2 +- SUMMARY.adoc | 25 ++ images/keycloak_logo.png | Bin 0 -> 18350 bytes topics/adapter-context.adoc | 18 ++ topics/adapter_error_handling.adoc | 60 +++++ topics/fuse-adapter.adoc | 17 ++ topics/installed-applications.adoc | 23 ++ topics/jaas.adoc | 27 +++ topics/javascript-adapter.adoc | 363 ++++++++++++++++++++++++++++ topics/jboss-adapter.adoc | 277 +++++++++++++++++++++ topics/jetty8-adapter.adoc | 47 ++++ topics/jetty9-adapter.adoc | 148 ++++++++++++ topics/logout.adoc | 6 + topics/multi-tenancy.adoc | 28 +++ topics/oidc.adoc | 220 +++++++++++++++++ topics/preface.adoc | 20 ++ topics/spring-boot-adapter.adoc | 67 +++++ topics/spring-security-adapter.adoc | 264 ++++++++++++++++++++ topics/tomcat-adapter.adoc | 87 +++++++ 19 files changed, 1698 insertions(+), 1 deletion(-) create mode 100755 SUMMARY.adoc create mode 100755 images/keycloak_logo.png create mode 100755 topics/adapter-context.adoc create mode 100755 topics/adapter_error_handling.adoc create mode 100755 topics/fuse-adapter.adoc create mode 100755 topics/installed-applications.adoc create mode 100755 topics/jaas.adoc create mode 100755 topics/javascript-adapter.adoc create mode 100755 topics/jboss-adapter.adoc create mode 100755 topics/jetty8-adapter.adoc create mode 100755 topics/jetty9-adapter.adoc create mode 100755 topics/logout.adoc create mode 100755 topics/multi-tenancy.adoc create mode 100755 topics/oidc.adoc create mode 100755 topics/preface.adoc create mode 100755 topics/spring-boot-adapter.adoc create mode 100755 topics/spring-security-adapter.adoc create mode 100755 topics/tomcat-adapter.adoc diff --git a/README.adoc b/README.adoc index aa7e76f843..b1550581b0 100755 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,5 @@ -Keycloak Client Applications Guide +Securing Client Applications Guide ====================== image:images/keycloak_logo.png[alt="Keycloak"] diff --git a/SUMMARY.adoc b/SUMMARY.adoc new file mode 100755 index 0000000000..20e507cbb0 --- /dev/null +++ b/SUMMARY.adoc @@ -0,0 +1,25 @@ +== Securing Client Applications Guide + +//. link:topics/templates/document-attributes.adoc[] +:imagesdir: images + + . link:topics/preface.adoc[Preface] + . link:topics/Overview.adoc[Overview] + . link:topics/oidc.adoc[OpenID Connect Client Adapters] + .. link:topics/jboss-adapter.adoc[JBoss/Wildfly Adapter] + .. link:topics/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] + .. link:topics/jetty9-adapter.adoc[Jetty 9.x Adapters] + .. link:topics/jetty8-adapter.adoc[Jetty 8.1.x Adapter] + .. link:topics/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + .. link:topics/fuse-adapter.adoc[JBoss Fuse and Apache Karaf Adapter] + .. link:topics/javascript-adapter.adoc[Javascript Adapter] + .. link:topics/spring-boot-adapter.adoc[Spring Boot Adapter] + .. link:topics/spring-security-adapter.adoc[Spring Security Adapter] + .. link:topics/installed-applications.adoc[Installed Applications] + .. link:topics/logout.adoc[Logout] + .. link:topics/adapter_error_handling.adoc[Error Handling] + .. link:topics/multi-tenancy.adoc[ Multi Tenancy] + .. link:topics/jaas.adoc[JAAS plugin] + . link:topics/saml.adoc[SAML Client Adapters] + + diff --git a/images/keycloak_logo.png b/images/keycloak_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..4883f523023502c83147b99ad2693b840a018c53 GIT binary patch literal 18350 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 Yc_&{j_2)SNJXwdq)78&qol`;+023O}B>(^b literal 0 HcmV?d00001 diff --git a/topics/adapter-context.adoc b/topics/adapter-context.adoc new file mode 100755 index 0000000000..ba99e8f2bf --- /dev/null +++ b/topics/adapter-context.adoc @@ -0,0 +1,18 @@ += KeycloakSecurityContext +:doctype: book +:sectnums: +:toc: left +:icons: font +:experimental: +:sourcedir: . + +The `KeycloakSecurityContext` interface is available if you need to look at the access token directly. +This context is also useful if you need to get the encoded access token so you can make additional REST invocations. +In servlet environments it is available in secured invocations as an attribute in HttpServletRequest. +Or, it is available in secure and insecure requests in the HttpSession for browser apps. + +[source] +---- +httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName()); +httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName()); +---- diff --git a/topics/adapter_error_handling.adoc b/topics/adapter_error_handling.adoc new file mode 100755 index 0000000000..82c246b5f5 --- /dev/null +++ b/topics/adapter_error_handling.adoc @@ -0,0 +1,60 @@ + +[[_adapter_error_handling]] += Error Handling + +Keycloak has some error handling facilities for servlet based client adapters. +When an error is encountered in authentication, keycloak will call `HttpServletResponse.sendError()`. +You can set up an error-page within your `web.xml` file to handle the error however you want. +Keycloak may throw 400, 401, 403, and 500 errors. + + +[source] +---- + + + + 404 + /ErrorHandler + +---- + +Keycloak also sets an `HttpServletRequest` attribute that you can retrieve. +The attribute name is `org.keycloak.adapters.spi.AuthenticationError`. +Typecast this object to: `org.keycloak.adapters.OIDCAuthenticationError`. +This class can tell you exactly what happened. +If this attribute is not set, then the adapter was not responsible for the error code. + + +[source] +---- + +public class OIDCAuthenticationError implements AuthenticationError { + public static enum Reason { + NO_BEARER_TOKEN, + NO_REDIRECT_URI, + INVALID_STATE_COOKIE, + OAUTH_ERROR, + SSL_REQUIRED, + CODE_TO_TOKEN_FAILURE, + INVALID_TOKEN, + STALE_TOKEN, + NO_AUTHORIZATION_HEADER + } + + private Reason reason; + private String description; + + public OIDCAuthenticationError(Reason reason, String description) { + this.reason = reason; + this.description = description; + } + + public Reason getReason() { + return reason; + } + + public String getDescription() { + return description; + } +} +---- \ No newline at end of file diff --git a/topics/fuse-adapter.adoc b/topics/fuse-adapter.adoc new file mode 100755 index 0000000000..948cf69d4b --- /dev/null +++ b/topics/fuse-adapter.adoc @@ -0,0 +1,17 @@ + +[[_fuse_adapter]] += JBoss Fuse and Apache Karaf Adapter + +Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.1 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. + +What is supported for Fuse/Karaf is: + +* Security for classic WAR applications deployed on Fuse/Karaf with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender]. +* Security for servlets deployed on Fuse/Karaf as OSGI services with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender]. +* Security for http://camel.apache.org/[Apache Camel] Jetty endpoints running with http://camel.apache.org/jetty.html[Camel Jetty] component. +* Security for http://cxf.apache.org/[Apache CXF] endpoints running on their own separate http://cxf.apache.org/docs/jetty-configuration.html[Jetty engine]. +* Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet. +* Security for SSH and JMX admin access. +* Security for http://hawt.io/[Hawt.io admin console] . + +The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory `examples/fuse` . \ No newline at end of file diff --git a/topics/installed-applications.adoc b/topics/installed-applications.adoc new file mode 100755 index 0000000000..de5bbded5f --- /dev/null +++ b/topics/installed-applications.adoc @@ -0,0 +1,23 @@ + += Installed Applications + +Keycloak provides two special redirect uris for installed applications. + +[[_installed_applications_url]] +== Installed Applications url + +http://localhost + +This returns the code to a web server on the client as a query parameter. +Any port number is allowed. +This makes it possible to start a web server for the installed application on any free port number without requiring changes in the `Admin Console`. + +[[_installed_applications_urn]] +== Installed Applications urn + +`urn:ietf:wg:oauth:2.0:oob` + +If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. +When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. +The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. +With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. diff --git a/topics/jaas.adoc b/topics/jaas.adoc new file mode 100755 index 0000000000..5307d39e05 --- /dev/null +++ b/topics/jaas.adoc @@ -0,0 +1,27 @@ + +[[_jaas_adapter]] += JAAS plugin + +It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, but directly choose one of our adapters. +However some applications and systems may still rely on pure legacy JAAS solution. +Keycloak provides couple of login modules to help with such use cases. +Some login modules provided by Keycloak are: + + + +org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule:: + This login module allows to authenticate with username/password from Keycloak database. + It's using <<_direct_access_grants,Direct Access Grants>> Keycloak endpoint to validate on Keycloak side if provided username/password is valid. + It's useful especially for non-web based systems, which need to rely on JAAS and want to use Keycloak credentials, but can't use classic browser based authentication flow due to their non-web nature. + Example of such application could be messaging application or SSH system. + +org.keycloak.adapters.jaas.BearerTokenLoginModule:: + This login module allows to authenticate with Keycloak access token passed to it through CallbackHandler as password. + It may be useful for example in case, when you have Keycloak access token from classic web based authentication flow and your web application then needs to talk to external non-web based system, which rely on JAAS. + For example to JMS/messaging system. + +Both login modules have configuration property `keycloak-config-file` where you need to provide location of keycloak.json configuration file. +It could be either provided from filesystem or from classpath (in that case you may need value like `classpath:/folder-on-classpath/keycloak.json` ). + +Second property `role-principal-class` allows to specify alternative class for Role principals attached to JAAS Subject. +Default value for Role principal is `org.keycloak.adapters.jaas.RolePrincipal` . Note that class should have constructor with single String argument. \ No newline at end of file diff --git a/topics/javascript-adapter.adoc b/topics/javascript-adapter.adoc new file mode 100755 index 0000000000..e2bb8a7b5c --- /dev/null +++ b/topics/javascript-adapter.adoc @@ -0,0 +1,363 @@ + += Javascript Adapter + +The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications. +This library is referenceable directly from the keycloak server. +You can also download the adapter from Keycloak's download site if you want a static copy. +It works in the same way as other application adapters except that your browser is driving the OAuth redirect protocol rather than the server. + +The disadvantage of using this approach is that you have a non-confidential, public client. +This makes it more important that you register valid redirect URLs and make sure your domain name is secured. + +To use this adapter, you must first configure an application (or client) through the `Keycloak Admin Console`. +You should select `public` for the `Access Type` field. +As public clients can't be verified with a client secret, you are required to configure one or more valid redirect uris. +Once you've configured the application, click on the `Installation` tab and download the `keycloak.json` file. +This file should be hosted on your web-server at the same root as your HTML pages. +Alternatively, you can manually configure the adapter and specify the URL for this file. + +Next, you have to initialize the adapter in your application. +An example is shown below. + +[source,html] +---- + + + + +---- +To specify the location of the keycloak.json file: + +[source] +---- +var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json')); +---- +Or finally to manually configure the adapter: + +[source] +---- + +var keycloak = Keycloak({ + url: 'http://keycloak-server/auth', + realm: 'myrealm', + clientId: 'myapp' +}); +---- +You can also pass `login-required` or `check-sso` to the init function. +Login required will cause a redirect to the login form on the server, while check-sso will simply redirect to the auth server to check if the user is already logged in to the realm. +For example: + +[source] +---- +keycloak.init({ onLoad: 'login-required' }) +---- + +After you login, your application will be able to make REST calls using bearer token authentication. +Here's an example pulled from the `customer-portal-js` example that comes with the distribution. + +[source] +---- + + + +---- + +The `loadData()` method builds an HTTP request setting the `Authorization` header to a bearer token. +The `keycloak.token` points to the access token the browser obtained when it logged you in. +The `loadFailure()` method is invoked on a failure. +The `reloadData()` function calls `keycloak.updateToken()` passing in the `loadData()` and `loadFailure()` callbacks. +The `keycloak.updateToken()` method checks to see if the access token hasn't expired. +If it hasn't, and your oauth login returned a refresh token, this method will refresh the access token. +Finally, if successful, it will invoke the success callback, which in this case is the `loadData()` method. + +To refresh the token when it is expired, call the `updateToken` method. +This method returns a promise object, which can be used to invoke a function on success or failure. +This method can be used to wrap functions that should only be called with a valid token. +For example, the following method will refresh the token if it expires within 30 seconds, and then invoke the specified function. +If the token is valid for more than 30 seconds it will just call the specified function. + +[source] +---- +keycloak.updateToken(30).success(function() { + // send request with valid token +}).error(function() { + alert('failed to refresh token'); +); +---- + +== Session status iframe + +By default, the JavaScript adapter creates a non-visible iframe that is used to detect if a single-sign out has occurred. +This does not require any network traffic, instead the status is retrieved from a special status cookie. +This feature can be disabled by setting `checkLoginIframe: false` in the options passed to the `init` method. + +[[_javascript_implicit_flow]] +== Implicit and Hybrid Flow + +By default, the JavaScript adapter uses http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[OpenID Connect standard (Authorization code) flow], which means that after authentication, the Keycloak server redirects the user back to your application, where the JavaScript adapter will exchange the `code` for an access token and a refresh token. + +However, Keycloak also supports http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[OpenID Connect Implicit flow] where an access token is sent immediately after successful authentication with Keycloak (there is no additional request for exchange code). This could have better performance than standard flow, as there is no additional request to exchange the code for tokens. +However, sending the access token in the URL fragment could pose a security issue in some environments (access logs might expose tokens located in the URL). + +To enable implicit flow, you need to enable the `Implicit Flow Enabled` flag for the client in the Keycloak admin console. +You also need to pass the parameter `flow` with value `implicit` to `init` method. +An example is below: + +[source] +---- + +keycloak.init({ flow: 'implicit' }) +---- +Note that with implicit flow, you are not given a refresh token after authentication. + +This makes it harder for your application to periodically update the access token in background (without browser redirection). It's recommended that you implement an `onTokenExpired` callback method on the keycloak object, so you are notified after the token is expired (For example you can call keycloak.login, which will redirect browser to Keycloak login screen and it will immediately redirect you back if the SSO session is still valid and the user is still logged. +However, make sure to save the application state before performing a redirect.) + +Keycloak also has support for http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth[OpenID Connect Hybrid flow]. + +This requires the client to have both the `Standard Flow Enabled` and `Implicit Flow Enabled` flags enabled in the admin console. +The Keycloak server will then send both the code and tokens to your application. +The access token can be used immediately while the code can be exchanged for access and refresh tokens. +Similar to the implicit flow, the hybrid flow is good for performance because the access token is available immediately. +But, the token is still sent in the URL, and security risks might still apply. +However, one advantage over the implicit flow is that a refresh token is made available to the application (after the code-to-token request is finished). + +For hybrid flow, you need to pass the parameter `flow` with value `hybrid` to `init` method. + +== Older browsers + +The JavaScript adapter depends on Base64 (window.btoa and window.atob) and HTML5 History API. +If you need to support browsers that don't provide those (for example IE9) you'll need to add polyfillers. +Example polyfill libraries: + +* https://github.com/davidchambers/Base64.js +* https://github.com/devote/HTML5-History-API + +== JavaScript Adapter reference + +=== Constructor + +[source] +---- + +new Keycloak(); +new Keycloak('http://localhost/keycloak.json'); +new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' }); +---- + +=== Properties + +* authenticated - true if the user is authenticated +* Authorization +* tokenParsed - the parsed token +* subject - the user id +* idToken - the id token if claims is enabled for the application, null otherwise +* idTokenParsed - the parsed id token +* realmAccess - the realm roles associated with the token +* resourceAccess - the resource roles assocaited with the token +* refreshToken - the base64 encoded token that can be used to retrieve a new token +* refreshTokenParsed - the parsed refresh token +* timeSkew - estimated skew between local time and Keycloak server in seconds +* fragment +* Implicit flow +* flow + +=== Methods + +==== init(options) + +Called to initialize the adapter. + +Options is an Object, where: + +* onLoad - specifies an action to do on load, can be either 'login-required' or 'check-sso' +* token - set an initial value for the token +* refreshToken - set an initial value for the refresh token +* idToken - set an initial value for the id token (only together with token or refreshToken) +* timeSkew - set an initial value for skew between local time and Keycloak server in seconds (only together with token or refreshToken) +* checkLoginIframe - set to enable/disable monitoring login state (default is true) +* checkLoginIframeInterval - set the interval to check login state (default is 5 seconds) +* query ++`fragment` ++`fragment` ++`query` +* standard ++`implicit` ++`hybrid`<<_javascript_implicit_flow,+Implicit flow>> + + +Returns promise to set functions to be invoked on success or error. + +==== login(options) + +Redirects to login form on (options is an optional object with redirectUri and/or prompt fields) + +Options is an Object, where: + +* redirectUri - specifies the uri to redirect to after login +* prompt - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed) +* loginHint - used to pre-fill the username/email field on the login form +* action - if value is 'register' then user is redirected to registration page, otherwise to login page +* locale - specifies the desired locale for the UI + +==== createLoginUrl(options) + +Returns the url to login form on (options is an optional object with redirectUri and/or prompt fields) + +Options is an Object, where: + +* redirectUri - specifies the uri to redirect to after login +* prompt - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed) + +==== logout(options) + +Redirects to logout + +Options is an Object, where: + +* redirectUri - specifies the uri to redirect to after logout + +==== createLogoutUrl(options) + +Returns logout out + +Options is an Object, where: + +* redirectUri - specifies the uri to redirect to after logout + +==== register(options) + +Redirects to registration form. +It's a shortcut for doing login with option action = 'register' + +Options are same as login method but 'action' is overwritten to 'register' + +==== createRegisterUrl(options) + +Returns the url to registration page. +It's a shortcut for doing createRegisterUrl with option action = 'register' + +Options are same as createLoginUrl method but 'action' is overwritten to 'register' + +==== accountManagement() + +Redirects to account management + +==== createAccountUrl() + +Returns the url to account management + +==== hasRealmRole(role) + +Returns true if the token has the given realm role + +==== hasResourceRole(role, resource) + +Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used) + +==== loadUserProfile() + +Loads the users profile + +Returns promise to set functions to be invoked on success or error. + +==== isTokenExpired(minValidity) + +Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used) + +==== updateToken(minValidity) + +If the token expires within minValidity seconds (minValidity is optional, if not specified 0 is used) the token is refreshed. +If the session status iframe is enabled, the session status is also checked. + +Returns promise to set functions that can be invoked if the token is still valid, or if the token is no longer valid. +For example: + +[source] +---- + +keycloak.updateToken(5).success(function(refreshed) { + if (refreshed) { + alert('token was successfully refreshed'); + } else { + alert('token is still valid'); + } + }).error(function() { + alert('failed to refresh the token, or the session has expired'); + }); +---- + +==== clearToken() + +Clear authentication state, including tokens. +This can be useful if application has detected the session has expired, for example if updating token fails. +Invoking this results in onAuthLogout callback listener being invoked. + +[source] +---- + +keycloak.updateToken(5).error(function() { + keycloak.clearToken(); +}); +---- + +=== Callback Events + +The adapter supports setting callback listeners for certain events. +For example: +[source] +---- + +keycloak.onAuthSuccess = function() { alert('authenticated'); } +---- + +* onReady(authenticated) - called when the adapter is initialized +* onAuthSuccess - called when a user is successfully authenticated +* onAuthError - called if there was an error during authentication +* onAuthRefreshSuccess - called when the token is refreshed +* onAuthRefreshError - called if there was an error while trying to refresh the token +* onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode) +* onTokenExpired - called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen diff --git a/topics/jboss-adapter.adoc b/topics/jboss-adapter.adoc new file mode 100755 index 0000000000..351eba5206 --- /dev/null +++ b/topics/jboss-adapter.adoc @@ -0,0 +1,277 @@ + +[[_jboss_adapter]] += JBoss/Wildfly Adapter + +To be able to secure WAR apps deployed on JBoss AS 7.1.1, JBoss EAP 6.x, or Wildfly, you must install and configure the Keycloak Subsystem. +You then have two options to secure your WARs. +You can provide a keycloak config file in your WAR and change the auth-method to KEYCLOAK within web.xml. +Alternatively, you don't have to crack open your WARs at all and can apply Keycloak via the Keycloak Subsystem configuration in standalone.xml. +Both methods are described in this section. + +[[_jboss_adapter_installation]] +== Adapter Installation + +Adapters are no longer included with the appliance or war distribution. +Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +Install on Wildfly 9 or 10: + +[source] +---- + +$ cd $WILDFLY_HOME +$ unzip keycloak-wildfly-adapter-dist.zip +---- +Install on Wildfly 8: + +[source] +---- + +$ cd $WILDFLY_HOME +$ unzip keycloak-wf8-adapter-dist.zip +---- +Install on JBoss EAP 6.x: + +[source] +---- + +$ cd $JBOSS_HOME +$ unzip keycloak-eap6-adapter-dist.zip +---- +Install on JBoss AS 7.1.1: + +[source] +---- + +$ cd $JBOSS_HOME +$ unzip keycloak-as7-adapter-dist.zip +---- + +This zip file creates new JBoss Modules specific to the Wildfly Keycloak Adapter within your Wildfly distro. + +After adding the Keycloak modules, you must then enable the Keycloak Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. + +There is a CLI script that will help you modify your server configuration. +Start the server and run the script from the server's bin directory: + +[source] +---- + +$ cd $JBOSS_HOME/bin +$ jboss-cli.sh -c --file=adapter-install.cli +---- +The script will add the extension, subsystem, and optional security-domain as described below. + +For more recent versions of WildFly there's also a offline CLI script that can be used to install the adapter while the server is not running: + +[source] +---- + +$ cd $JBOSS_HOME/bin +$ jboss-cli.sh -c --file=adapter-install-offline.cli +---- + +[source] +---- + + + + + + ... + + + + + ... + +---- + +The keycloak security domain should be used with EJBs and other components when you need the security context created in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. +Otherwise this configuration is optional. + +[source] +---- + + + + +... + + + + + + +---- + +For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: + +[source] +---- + +import org.jboss.ejb3.annotation.SecurityDomain; +import org.jboss.resteasy.annotations.cache.NoCache; + +import javax.annotation.security.RolesAllowed; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import java.util.ArrayList; +import java.util.List; + +@Path("customers") +@Stateless +@SecurityDomain("keycloak") +public class CustomerService { + + @EJB + CustomerDB db; + + @GET + @Produces("application/json") + @NoCache + @RolesAllowed("db_user") + public List getCustomers() { + return db.getCustomers(); + } +} +---- + +We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain annotation when you want to propagate a keycloak security context to the EJB tier. + +== Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. + +Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. +You also have to use standard servlet security to specify role-base constraints on your URLs. +Here's an example pulled from one of the examples that comes distributed with Keycloak. + + +[source] +---- + + + + + customer-portal + + + + Admins + /admin/* + + + admin + + + CONFIDENTIAL + + + + + Customers + /customers/* + + + user + + + CONFIDENTIAL + + + + + KEYCLOAK + this is ignored currently + + + + admin + + + user + + +---- + +== Securing WARs via Keycloak Subsystem + +You do not have to crack open a WAR to secure it with Keycloak. +Alternatively, you can externally secure it via the Keycloak Adapter Subsystem. +While you don't have to specify KEYCLOAK as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. +You do not, however, have to create a `WEB-INF/keycloak.json` file. +This metadata is instead defined within XML in your server's `domain.xml` or `standalone.xml` subsystem configuration section. + + +[source] +---- + + + + + + + + + demo + MIGfMA0GCSqGSIb3DQEBAQUAA + http://localhost:8081/auth + external + customer-portal + password + + + +---- + + +The `secure-deployment` `name` attribute identifies the WAR you want to secure. +Its value is the `module-name` defined in `web.xml` with `.war` appended. +The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_adapter_config,general adapter configuration>>. +The exception is the `credential` element. + +To make it easier for you, you can go to the Keycloak Adminstration Console and go to the Application/Installation tab of the application this WAR is aligned with. +It provides an example XML file you can cut and paste. + +There is an additional convenience format for this XML if you have multiple WARs you are deployment that are secured by the same domain. +This format allows you to define common configuration items in one place under the `realm` element. + + +[source] +---- + + + + MIGfMA0GCSqGSIb3DQEBA + http://localhost:8080/auth + external + + + demo + customer-portal + password + + + demo + product-portal + password + + + demo + database-service + true + + +---- diff --git a/topics/jetty8-adapter.adoc b/topics/jetty8-adapter.adoc new file mode 100755 index 0000000000..39159755ad --- /dev/null +++ b/topics/jetty8-adapter.adoc @@ -0,0 +1,47 @@ + +[[_jetty8_adapter]] += Jetty 8.1.x Adapter + +Keycloak has a separate adapter for Jetty 8.1.x that you will have to install into your Jetty installation. +You then have to provide some extra configuration in each WAR you deploy to Jetty. +Let's go over these steps. + +[[_jetty8_adapter_installation]] +== Adapter Installation + +Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the Jetty 8.1.x distro into Jetty 8.1.x's root directory. +Including adapter's jars within your WEB-INF/lib directory will not work! + + +[source] +---- + +$ cd $JETTY_HOME +$ unzip keycloak-jetty81-adapter-dist.zip +---- + +Next, you will have to enable the keycloak option. +Edit start.ini and add keycloak to the options + + +[source] +---- + + +#=========================================================== +# Start classpath OPTIONS. +# These control what classes are on the classpath +# for a full listing do +# java -jar start.jar --list-options +#----------------------------------------------------------- +OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations,keycloak +---- + +== Required Per WAR Configuration + +Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. +Our 8.1.x adapter supports both keycloak.json and the jboss-web.xml advanced configuration. +See <<_jetty9_per_war,Required Per WAR Configuration>> \ No newline at end of file diff --git a/topics/jetty9-adapter.adoc b/topics/jetty9-adapter.adoc new file mode 100755 index 0000000000..ed0a8c6daa --- /dev/null +++ b/topics/jetty9-adapter.adoc @@ -0,0 +1,148 @@ + +[[_jetty9_adapter]] += Jetty 9.x Adapters + +Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will have to install into your Jetty installation. +You then have to provide some extra configuration in each WAR you deploy to Jetty. +Let's go over these steps. + +[[_jetty9_adapter_installation]] +== Adapter Installation + +Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the Jetty 9.x distro into Jetty 9.x's root directory. +Including adapter's jars within your WEB-INF/lib directory will not work! + + +[source] +---- + +$ cd $JETTY_HOME +$ unzip keycloak-jetty92-adapter-dist.zip +---- + +Next, you will have to enable the keycloak module for your jetty.base. + + +[source] +---- + +$ cd your-base +$ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak +---- + +[[_jetty9_per_war]] +== Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `WEB-INF/jetty-web.xml` file in your WAR package. +This is a Jetty specific config file and you must define a Keycloak specific authenticator within it. + +[source] +---- + + + + + + + + + + + + +---- + +Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. + +WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. +You will have to define all adapter settings within the `jetty-web.xml` file as described below. + +Instead of using keycloak.json, you can define everything within the `jetty-web.xml`. +You'll just have to figure out how the json settings match to the `org.keycloak.representations.adapters.config.AdapterConfig` class. + + +[source] +---- + + + + + + + + + + + tomcat + customer-portal + http://localhost:8081/auth + external + + + + secret + password + + + + MIGfMA0GCSqGSIb3DQEBAQUAA4 + + + + + + +---- + +You do not have to crack open your WAR to secure it with keycloak. +Instead create the jetty-web.xml file in your webapps directory with the name of yourwar.xml. +Jetty should pick it up. +In this mode, you'll have to declare keycloak.json configuration directly within the xml file. + +Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. +Here's an example: + + +[source] +---- + + + + + customer-portal + + + + Customers + /* + + + user + + + CONFIDENTIAL + + + + + BASIC + this is ignored currently + + + + admin + + + user + + +---- \ No newline at end of file diff --git a/topics/logout.adoc b/topics/logout.adoc new file mode 100755 index 0000000000..da2a11e6b2 --- /dev/null +++ b/topics/logout.adoc @@ -0,0 +1,6 @@ + += Logout + +There are multiple ways you can logout from a web application. +For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point the browser at the url `http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri`. +This will log you out if you have an SSO session with your browser. \ No newline at end of file diff --git a/topics/multi-tenancy.adoc b/topics/multi-tenancy.adoc new file mode 100755 index 0000000000..b792d0bdcc --- /dev/null +++ b/topics/multi-tenancy.adoc @@ -0,0 +1,28 @@ + += Multi Tenancy + +Multi Tenancy, in our context, means that one single target application (WAR) can be secured by a single (or clustered) Keycloak server, authenticating its users against different realms. +In practice, this means that one application needs to use different `keycloak.json` files. +For this case, there are two possible solutions: + +* The same WAR file deployed under two different names, each with its own Keycloak configuration (probably via the Keycloak Subsystem). + This scenario is suitable when the number of realms is known in advance or when there's a dynamic provision of application instances. + One example would be a service provider that dynamically creates servers/deployments for their clients, like a PaaS. +* client1.acme.com ++`client2.acme.com` ++`/app/client1/` ++`/app/client2/` This chapter of the reference guide focus on this second scenario. + +Keycloak provides an extension point for applications that need to evaluate the realm on a request basis. +During the authentication and authorization phase of the incoming request, Keycloak queries the application via this extension point and expects the application to return a complete representation of the realm. +With this, Keycloak then proceeds the authentication and authorization process, accepting or refusing the request based on the incoming credentials and on the returned realm. +For this scenario, an application needs to: + +* web.xml ++`keycloak.config.resolver` ++`org.keycloak.adapters.KeycloakConfigResolver` +* org.keycloak.adapters.KeycloakConfigResolver ++`resolve(org.keycloak.adapters.spi.HttpFacade.Request)` ++`org.keycloak.adapters.KeycloakDeployment` + +An implementation of this feature can be found in the examples. \ No newline at end of file diff --git a/topics/oidc.adoc b/topics/oidc.adoc new file mode 100755 index 0000000000..7c598b1d4e --- /dev/null +++ b/topics/oidc.adoc @@ -0,0 +1,220 @@ + +[[_adapter_config]] + += Adapters + +Keycloak can secure a wide variety of application types. +This section defines which application types are supported and how to configure and install them so that you can use Keycloak to secure your applications. + +These client adapters use an extension of the OpenID Connect protocol (a derivate of OAuth 2.0). This extension provides support for clustering, backchannel logout, and other non-standard adminstrative functions. +The Keycloak project also provides a separate, standalone, generic, SAML client adapter. +But that is describe in a separate document and has a different download. + + +== General Adapter Config + +Each adapter supported by Keycloak can be configured by a simple JSON text file. +This is what one might look like: + + +[source] +---- +{ + "realm" : "demo", + "resource" : "customer-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3D...31LwIDAQAB", + "auth-server-url" : "https://localhost:8443/auth", + "ssl-required" : "external", + "use-resource-role-mappings" : false, + "enable-cors" : true, + "cors-max-age" : 1000, + "cors-allowed-methods" : "POST, PUT, DELETE, GET", + "bearer-only" : false, + "enable-basic-auth" : false, + "expose-token" : true, + "credentials" : { + "secret" : "234234-234234-234234" + }, + + "connection-pool-size" : 20, + "disable-trust-manager": false, + "allow-any-hostname" : false, + "truststore" : "path/to/truststore.jks", + "truststore-password" : "geheim", + "client-keystore" : "path/to/client-keystore.jks", + "client-keystore-password" : "geheim", + "client-key-password" : "geheim" +} +---- + +Some of these configuration switches may be adapter specific and some are common across all adapters. +For Java adapters you can use `${...}` enclosure as System property replacement. +For example `${jboss.server.config.dir}`. +Also, you can obtain a template for this config file from the admin console. +Go to the realm and select the application you want a template for. +Go to the `Installation` tab and this will provide you with a template that includes the public key of the realm. + +Here is a description of each item: + + + +realm:: + Name of the realm representing the users of your distributed applications and services. + This is _REQUIRED._ + +resource:: + Username of the application. + Each application has a username that is used when the application connects with the Keycloak server to turn an access code into an access token (part of the OAuth 2.0 protocol). This is _REQUIRED._ + +realm-public-key:: + PEM format of public key. + You can obtain this from the administration console. + This is _REQUIRED._ + +auth-server-url:: + The base URL of the Keycloak Server. + All other Keycloak pages and REST services are derived from this. + It is usually of the form `https://host:port/auth` This is _REQUIRED._ + +ssl-required:: + Ensures that all communication to and from the Keycloak server from the adapter is over HTTPS. + This is _OPTIONAL_. + The default value is _external_ meaning that HTTPS is required by default for external requests. + Valid values are 'all', 'external' and 'none'. + +use-resource-role-mappings:: + If set to true, the adapter will look inside the token for application level role mappings for the user. + If false, it will look at the realm level for user role mappings. + This is _OPTIONAL_. + The default value is _false_. + +public-client:: + If set to true, the adapter will not send credentials for the client to Keycloak. + The default value is _false_. + +enable-cors:: + This enables CORS support. + It will handle CORS preflight requests. + It will also look into the access token to determine valid origins. + This is _OPTIONAL_. + The default value is _false_. + +cors-max-age:: + If CORS is enabled, this sets the value of the `Access-Control-Max-Age` header. + This is _OPTIONAL_. + If not set, this header is not returned in CORS responses. + +cors-allowed-methods:: + If CORS is enabled, this sets the value of the `Access-Control-Allow-Methods` header. + This should be a comma-separated string. + This is _OPTIONAL_. + If not set, this header is not returned in CORS responses. + +cors-allowed-headers:: + If CORS is enabled, this sets the value of the `Access-Control-Allow-Headers` header. + This should be a comma-separated string. + This is _OPTIONAL_. + If not set, this header is not returned in CORS responses. + +bearer-only:: + This tells the adapter to only do bearer token authentication. + That is, it will not do OAuth 2.0 redirects, but only accept bearer tokens through the `Authorization` header. + This is _OPTIONAL_. + The default value is _false_. + +enable-basic-auth:: + This tells the adapter to also support basic authentication. + If this option is enabled, then _secret_ must also be provided. + This is _OPTIONAL_. + The default value is _false_. + +expose-token:: + If `true`, an authenticated browser client (via a Javascript HTTP invocation) can obtain the signed access token via the URL `root/k_query_bearer_token`. + This is _OPTIONAL_. + The default value is _false_. + +credentials:: + Specify the credentials of the application. + This is an object notation where the key is the credential type and the value is the value of the credential type. + Currently only `password` is supported. + This is _REQUIRED_. + +connection-pool-size:: + Adapters will make separate HTTP invocations to the Keycloak Server to turn an access code into an access token. + This config option defines how many connections to the Keycloak Server should be pooled. + This is _OPTIONAL_. + The default value is `20`. + +disable-trust-manager:: + If the Keycloak Server requires HTTPS and this config option is set to `true` you do not have to specify a truststore. + While convenient, this setting is not recommended as you will not be verifying the host name of the Keycloak Server. + This is _OPTIONAL_. + The default value is `false`. + +allow-any-hostname:: + If the Keycloak Server requires HTTPS and this config option is set to `true` the Keycloak Server's certificate is validated via the truststore, but host name validation is not done. + This is not a recommended. + This seting may be useful in test environments This is _OPTIONAL_. + The default value is `false`. + +truststore:: + This setting is for Java adapters. + The value is the file path to a Java keystore file. + If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead. + Used for outgoing HTTPS communications to the Keycloak server. + Client making HTTPS requests need a way to verify the host of the server they are talking to. + This is what the trustore does. + The keystore contains one or more trusted host certificates or certificate authorities. + You can create this truststore by extracting the public certificate of the Keycloak server's SSL keystore. + This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. + +truststore-password:: + Password for the truststore keystore. + This is _REQUIRED_ if `truststore` is set. + +client-keystore:: + _Not supported yet, but we will support in future versions._ This setting is for Java adapters. + This is the file path to a Java keystore file. + This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the Keycloak server. + This is _OPTIONAL_. + +client-keystore-password:: + _Not supported yet, but we will support in future versions._ Password for the client keystore. + This is _REQUIRED_ if `client-keystore` is set. + +client-key-password:: + _Not supported yet, but we will support in future versions._ Password for the client's key. + This is _REQUIRED_ if `client-keystore` is set. + +auth-server-url-for-backend-requests:: + Alternative location of auth-server-url used just for backend requests. + It must be absolute URI. + Useful especially in cluster (see <<_relative_uri_optimization,Relative URI Optimization>>) or if you would like to use _https_ for browser requests but stick with _http_ for backend requests etc. + +always-refresh-token:: + If _true_, Keycloak will refresh token in every request. + More info in <<_refresh_token_each_req,Refresh token in each request>> . + +register-node-at-startup:: + If _true_, then adapter will send registration request to Keycloak. + It's _false_ by default and useful just in cluster (See <<_registration_app_nodes,Registration of application nodes to Keycloak>>) + +register-node-period:: + Period for re-registration adapter to Keycloak. + Useful in cluster. + See <<_registration_app_nodes,Registration of application nodes to Keycloak>> for details. + +token-store:: + Possible values are _session_ and _cookie_. + Default is _session_, which means that adapter stores account info in HTTP Session. + Alternative _cookie_ means storage of info in cookie. + See <<_stateless_token_store,Stateless token store>> for details. + +principal-attribute:: + OpenID Connection ID Token attribute to populate the UserPrincipal name with. + If token attribute is null, defaults to `sub`. + Possible values are `sub`, `preferred_username`, `email`, `name`, `nickname`, `given_name`, `family_name`. + +turn-off-change-session-id-on-login:: + The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off This is _OPTIONAL_. + The default value is _false_. diff --git a/topics/preface.adoc b/topics/preface.adoc new file mode 100755 index 0000000000..fb0c9657df --- /dev/null +++ b/topics/preface.adoc @@ -0,0 +1,20 @@ += Preface + +In some of the example listings, what is meant to be displayed on one line does not fit inside the available page width.These lines have been broken up. A '\' at the end of a line means that a break has been introduced to fit in the page, with the following lines indented. +So: + +[source] +---- +Let's pretend to have an extremely \ +long line that \ +does not fit +This one is short +---- +Is really: + +[source] +---- +Let's pretend to have an extremely long line that does not fit +This one is short +---- + diff --git a/topics/spring-boot-adapter.adoc b/topics/spring-boot-adapter.adoc new file mode 100755 index 0000000000..e3ae217af2 --- /dev/null +++ b/topics/spring-boot-adapter.adoc @@ -0,0 +1,67 @@ + += Spring Boot Adapter + +To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app. +You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`). Let's go over these steps. + +[[_spring_boot_adapter_installation]] +== Adapter Installation + +The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all you need to do is add the Keycloak Spring Boot adapter JAR to your project. +Depending on what container you are using with Spring Boot, you also need to add the appropriate Keycloak container adapter. +If you are using Maven, add the following to your pom.xml (using Tomcat as an example): + + +[source] +---- + + + + org.keycloak + keycloak-spring-boot-adapter + &project.version; + + + org.keycloak + keycloak-tomcat8-adapter + &project.version; + +---- + +[[_spring_boot_adapter_configuration]] +== Required Spring Boot Adapter Configuration + +This section describes how to configure your Spring Boot app to use Keycloak. + +Instead of a `keycloak.json` file, you configure the realm for the Spring Boot Keycloak adapter via the normal Spring Boot configuration. +For example: + +[source] +---- + + +keycloak.realm = demorealm +keycloak.realmKey = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCWYuxXmsmfV+Xc9Ik8QET8lD4wuHrJAXbbutS2O/eMjQQLNK7QDX/k/XhOkhxP0YBEypqeXeGaeQJjCxDhFjJXQuewUEMlmSja3IpoJ9/hFn4Cns4m7NGO+rtvnfnwgVfsEOS5EmZhRddp+40KBPPJfTH6Vgu6KjQwuFPj6DTwIDAQAB +keycloak.auth-server-url = http://127.0.0.1:8080/auth +keycloak.ssl-required = external +keycloak.resource = demoapp +keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111 +keycloak.use-resource-role-mappings = true +---- + +You also need to specify the J2EE security config that would normally go in the `web.xml`. +Here's an example configuration: + +[source] +---- + + +keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff +keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin +keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = user +keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure + +keycloak.securityConstraints[0].securityCollections[1].name = admin stuff +keycloak.securityConstraints[0].securityCollections[1].authRoles[0] = admin +keycloak.securityConstraints[0].securityCollections[1].patterns[0] = /admin +---- \ No newline at end of file diff --git a/topics/spring-security-adapter.adoc b/topics/spring-security-adapter.adoc new file mode 100755 index 0000000000..cce17a5f46 --- /dev/null +++ b/topics/spring-security-adapter.adoc @@ -0,0 +1,264 @@ + += Spring Security Adapter + +To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project. +You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline. + +Unlike the other Keycloak Adapters, you should not configure your security in web.xml. +However, keycloak.json is still required. + +== Adapter Installation + +Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. + + +[source] +---- + + + + org.keycloak + keycloak-spring-security-adapter + &project.version; + +---- + +== Spring Security Configuration + +The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax. + +=== Java Configuration + +Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a http://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/web/WebSecurityConfigurer.html[WebSecurityConfigurer] instance. +The implementation allows customization by overriding methods. +While its use is not required, it greatly simplifies your security context configuration. + + +[source] +---- + + +@Configuration +@EnableWebSecurity +@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) +public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter +{ + /** + * Registers the KeycloakAuthenticationProvider with the authentication manager. + */ + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(keycloakAuthenticationProvider()); + } + + /** + * Defines the session authentication strategy. + */ + @Bean + @Override + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception + { + super.configure(http); + http + .authorizeRequests() + .antMatchers("/customers*").hasRole("USER") + .antMatchers("/admin*").hasRole("ADMIN") + .anyRequest().permitAll(); + } +} +---- + +You must provide a session authentication strategy bean which should be of type `RegisterSessionAuthenticationStrategy` for public or confidential applications and `NullAuthenticatedSessionStrategy` for bearer-only applications. + +Spring Security's `SessionFixationProtectionStrategy` is currently not supported because it changes the session identifier after login via Keycloak. +If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier. + +=== XML Configuration + +While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose. + + +[source] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +== Multi Tenancy + +The Keycloak Spring Security adapter also supports multi tenancy. +Instead of injecting `AdapterDeploymentContextFactoryBean` with the path to `keycloak.json` you can inject an implementation of the `KeycloakConfigResolver` interface. +More details on how to implement the `KeycloakConfigResolver` can be found in <<_multi_tenancy>>. + +== Naming Security Roles + +Spring Security, when using role-based authentication, requires that role names start with `ROLE_`. +For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`. + +The class `org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider` supports an optional `org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper` which can be used to map roles coming from Keycloak to roles recognized by Spring Security. +Use, for example, `org.springframework.security.core.authority.mapping.SimpleAuthorityMapper` to insert the `ROLE_` prefix and convert the role name to upper case. +The class is part of Spring Security Core module. + +== Client to Client Support + +To simplify communication between clients, Keycloak provides an extension of Spring's `RestTemplate` that handles bearer token authentication for you. +To enable this feature your security configuration must add the `KeycloakRestTemplate` bean. +Note that it must be scoped as a prototype to function correctly. + +For Java configuration: +[source] +---- + + +@Configuration +@EnableWebSecurity +@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) +public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { + + ... + + @Autowired + public KeycloakClientRequestFactory keycloakClientRequestFactory; + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public KeycloakRestTemplate keycloakRestTemplate() { + return new KeycloakRestTemplate(keycloakClientRequestFactory); + } + + ... +} +---- + +For XML configuration: +[source] +---- + + + + + +---- + +Your application code can then use `KeycloakRestTemplate` any time it needs to make a call to another client. +For example: +[source] +---- + + + +@Service +public class RemoteProductService implements ProductService { + + @Autowired + private KeycloakRestTemplate template; + + private String endpoint; + + @Override + public List getProducts() { + ResponseEntity response = template.getForEntity(endpoint, String[].class); + return Arrays.asList(response.getBody()); + } +} +---- + +== Spring Boot Configuration + +Spring Boot attempts to eagerly register filter beans with the web application context. +Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add two ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice. + + +[source] +---- + + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter +{ + ... + + @Bean + public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean( + KeycloakAuthenticationProcessingFilter filter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); + registrationBean.setEnabled(false); + return registrationBean; + } + + @Bean + public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean( + KeycloakPreAuthActionsFilter filter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); + registrationBean.setEnabled(false); + return registrationBean; + } + + ... +} +---- \ No newline at end of file diff --git a/topics/tomcat-adapter.adoc b/topics/tomcat-adapter.adoc new file mode 100755 index 0000000000..fc6b1b9d1a --- /dev/null +++ b/topics/tomcat-adapter.adoc @@ -0,0 +1,87 @@ + +[[_tomcat_adapter]] += Tomcat 6, 7 and 8 Adapters + +To be able to secure WAR apps deployed on Tomcat 6, 7 and 8 you must install the Keycloak Tomcat 6, 7 or 8 adapter into your Tomcat installation. +You then have to provide some extra configuration in each WAR you deploy to Tomcat. +Let's go over these steps. + +[[_tomcat_adapter_installation]] +== Adapter Installation + +Adapters are no longer included with the appliance or war distribution. +Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the adapter distro into Tomcat's `lib/` directory. +Including adapter's jars within your WEB-INF/lib directory will not work! The Keycloak adapter is implemented as a Valve and valve code must reside in Tomcat's main lib/ directory. + + +[source] +---- + +$ cd $TOMCAT_HOME/lib +$ unzip keycloak-tomcat6-adapter-dist.zip + or +$ unzip keycloak-tomcat7-adapter-dist.zip + or +$ unzip keycloak-tomcat8-adapter-dist.zip +---- + +== Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `META-INF/context.xml` file in your WAR package. +This is a Tomcat specific config file and you must define a Keycloak specific Valve. + +[source] +---- + + + + + +---- + +Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. + +Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. +Here's an example: + + +[source] +---- + + + + + customer-portal + + + + Customers + /* + + + user + + + + + BASIC + this is ignored currently + + + + admin + + + user + + +---- \ No newline at end of file From 8dd7e8e2e3eb17ab844c97ecb90bfc6f3034f104 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 18 Apr 2016 15:15:28 -0400 Subject: [PATCH 003/194] servlet filter --- topics/servlet-filter-adapter.adoc | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 topics/servlet-filter-adapter.adoc diff --git a/topics/servlet-filter-adapter.adoc b/topics/servlet-filter-adapter.adoc new file mode 100755 index 0000000000..07092c4a77 --- /dev/null +++ b/topics/servlet-filter-adapter.adoc @@ -0,0 +1,55 @@ + += Java Servlet Filter Adapter + +If you want to use Keycloak with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. +This adapter works a little differently than the other adapters. +You do not define security constraints in web.xml. +Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure. + +WARNING: Backchannel logout works a bit differently than the standard adapters. +Instead of invalidating the http session it instead marks the session id as logged out. +There's just no way of arbitrarily invalidating an http session based on a session id. + +[source] +---- + + + + + customer-portal + + + Keycloak Filter + org.keycloak.adapters.servlet.KeycloakOIDCFilter + + + Keycloak Filter + /keycloak/* + /protected/* + + +---- + +If you notice above, there are two url-patterns. + `/protected/*` are just the files we want protected. `/keycloak/*` url-pattern will handle callback from the keycloak server. +Note that you should configure your client in the Keycloak Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. +The Admin URL will make callbacks to the Admin URL to do things like backchannel logout. +So, the Admin URL in this example should be `http[s]://hostname/{context-root}/keycloak`. +There is an example of this in the distribution. + +The Keycloak filter has the same configuration parameters available as the other adapters except you must define them as filter init params instead of context params. + +To use this filter, include this maven artifact in your WAR poms + +[source] +---- + + + org.keycloak + keycloak-servlet-filter-adapter + &project.version; + +---- \ No newline at end of file From 294312dc81c19c57c3170665bdbc70a13e9f98b8 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 1 Jun 2016 13:02:44 +0200 Subject: [PATCH 004/194] Re-structuring --- .gitignore | 3 + README.adoc | 6 +- SUMMARY.adoc | 66 +++++++---- book.json | 39 +++++++ build.sh | 9 ++ gitlab-conversion.py | 107 ++++++++++++++++++ topics/{ => oidc/java}/adapter-context.adoc | 8 +- .../java}/adapter_error_handling.adoc | 2 +- topics/{ => oidc/java}/fuse-adapter.adoc | 2 +- topics/{ => oidc/java}/jaas.adoc | 2 +- .../java/java-adapter-config.adoc} | 83 ++++++-------- topics/oidc/java/java-adapters.adoc | 1 + topics/{ => oidc/java}/jboss-adapter.adoc | 8 +- topics/{ => oidc/java}/jetty8-adapter.adoc | 6 +- topics/{ => oidc/java}/jetty9-adapter.adoc | 6 +- topics/{ => oidc/java}/logout.adoc | 2 +- topics/{ => oidc/java}/multi-tenancy.adoc | 2 +- .../java}/servlet-filter-adapter.adoc | 2 +- .../{ => oidc/java}/spring-boot-adapter.adoc | 6 +- .../java}/spring-security-adapter.adoc | 18 +-- topics/{ => oidc/java}/tomcat-adapter.adoc | 6 +- topics/{ => oidc}/javascript-adapter.adoc | 2 +- .../oidc-generic.adoc} | 39 +++++-- topics/oidc/oidc-overview.adoc | 8 ++ topics/overview/overview.adoc | 0 topics/overview/supported-protocols.adoc | 0 topics/overview/what-are-client-adapters.adoc | 0 topics/preface.adoc | 2 +- topics/saml/saml-overview.adoc | 1 + 29 files changed, 317 insertions(+), 119 deletions(-) create mode 100755 book.json create mode 100755 build.sh create mode 100755 gitlab-conversion.py rename topics/{ => oidc/java}/adapter-context.adoc (85%) rename topics/{ => oidc/java}/adapter_error_handling.adoc (98%) rename topics/{ => oidc/java}/fuse-adapter.adoc (97%) rename topics/{ => oidc/java}/jaas.adoc (99%) rename topics/{oidc.adoc => oidc/java/java-adapter-config.adoc} (81%) mode change 100755 => 100644 create mode 100644 topics/oidc/java/java-adapters.adoc rename topics/{ => oidc/java}/jboss-adapter.adoc (98%) rename topics/{ => oidc/java}/jetty8-adapter.adoc (94%) rename topics/{ => oidc/java}/jetty9-adapter.adoc (98%) rename topics/{ => oidc/java}/logout.adoc (97%) rename topics/{ => oidc/java}/multi-tenancy.adoc (98%) rename topics/{ => oidc/java}/servlet-filter-adapter.adoc (98%) rename topics/{ => oidc/java}/spring-boot-adapter.adoc (96%) rename topics/{ => oidc/java}/spring-security-adapter.adoc (97%) rename topics/{ => oidc/java}/tomcat-adapter.adoc (96%) rename topics/{ => oidc}/javascript-adapter.adoc (99%) rename topics/{installed-applications.adoc => oidc/oidc-generic.adoc} (65%) mode change 100755 => 100644 create mode 100644 topics/oidc/oidc-overview.adoc create mode 100644 topics/overview/overview.adoc create mode 100644 topics/overview/supported-protocols.adoc create mode 100644 topics/overview/what-are-client-adapters.adoc create mode 100644 topics/saml/saml-overview.adoc diff --git a/.gitignore b/.gitignore index 44612eb8b4..39e1f9943f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +_book +node_modules + # Intellij ################### .idea diff --git a/README.adoc b/README.adoc index b1550581b0..08ecb3da6d 100755 --- a/README.adoc +++ b/README.adoc @@ -1,10 +1,10 @@ -Securing Client Applications Guide -====================== +{{book.title}} +=========================================== image:images/keycloak_logo.png[alt="Keycloak"] -*Keycloak* _Documentation_ for {{book.versions.swarm}} +*Keycloak* _Documentation_ for {{book.project.version}} http://www.keycloak.org diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 20e507cbb0..1cc0a7c026 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -1,25 +1,49 @@ -== Securing Client Applications Guide - -//. link:topics/templates/document-attributes.adoc[] -:imagesdir: images += {{book.title}} . link:topics/preface.adoc[Preface] - . link:topics/Overview.adoc[Overview] - . link:topics/oidc.adoc[OpenID Connect Client Adapters] - .. link:topics/jboss-adapter.adoc[JBoss/Wildfly Adapter] - .. link:topics/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] - .. link:topics/jetty9-adapter.adoc[Jetty 9.x Adapters] - .. link:topics/jetty8-adapter.adoc[Jetty 8.1.x Adapter] - .. link:topics/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] - .. link:topics/fuse-adapter.adoc[JBoss Fuse and Apache Karaf Adapter] - .. link:topics/javascript-adapter.adoc[Javascript Adapter] - .. link:topics/spring-boot-adapter.adoc[Spring Boot Adapter] - .. link:topics/spring-security-adapter.adoc[Spring Security Adapter] - .. link:topics/installed-applications.adoc[Installed Applications] - .. link:topics/logout.adoc[Logout] - .. link:topics/adapter_error_handling.adoc[Error Handling] - .. link:topics/multi-tenancy.adoc[ Multi Tenancy] - .. link:topics/jaas.adoc[JAAS plugin] - . link:topics/saml.adoc[SAML Client Adapters] + . link:topics/overview/overview.adoc[Overview] + .. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] + .. link:topics/overview/supported-protocols.adoc[Supported Protocols] + + . link:topics/oidc/oidc-overview.adoc[OpenID Connect] + + .. link:topics/oidc/java/java-adapters.adoc[Java Adapters] + ... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] + ... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] + ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse and Apache Karaf Adapter] + {% if book.community %} + ... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] + ... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] + ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] + ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] + ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] + {% endif %} + ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + ... link:topics/oidc/java/jaas.adoc[JAAS plugin] + ... link:topics/oidc/java/adapter-context.adoc[Keycloak Security Context] + ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] + ... link:topics/oidc/java/logout.adoc[Logout] + ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] + + .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] + + .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] + + . link:topics/saml/saml-overview.adoc[SAML] + + +// . link:topics/oidc.adoc[OpenID Connect Client Adapters] + +// +// +//{% if book.community %} +// +// +//{% endif %} +// .. link:topics/installed-applications.adoc[Installed Applications] +// +// +// +// . link:topics/saml.adoc[SAML Client Adapters] diff --git a/book.json b/book.json new file mode 100755 index 0000000000..d186d09b8c --- /dev/null +++ b/book.json @@ -0,0 +1,39 @@ +{ + "gitbook": "2.x.x", + "structure": { + "readme": "README.adoc" + }, + "plugins": [ + "toggle-chapters", + "ungrey", + "splitter" + ], + "variables": { + "title": "Keycloak Securing Client Applications Guide", + "community": true, + "product": false, + "images": "keycloak-images", + "appserver": { + "name": "Wildfly", + "version": "10" + }, + "adminguide": { + "name": "Keycloak Administration Guide", + "link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/" + }, + "web": { + "docs": { + "name": "keycloak.org/docs", + "link": "http://keycloak.org/docs" + }, + "downloads": { + "name": "keycloak.org/downloads", + "link": "http://keycloak.org/downloads" + } + }, + "project": { + "name": "Keycloak", + "version": "1.9.5.Final" + } + } +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..4befdf8200 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd $(readlink -f `dirname $0`) + +python gitlab-conversion.py +cd target +asciidoctor master.adoc + +xdg-open master.html diff --git a/gitlab-conversion.py b/gitlab-conversion.py new file mode 100755 index 0000000000..a69a7382ab --- /dev/null +++ b/gitlab-conversion.py @@ -0,0 +1,107 @@ +import sys, os, re, json, shutil, errno + +def transform(root, f, targetdir): + full = os.path.join(root, f) + input = open(full, 'r').read() + dir = os.path.join(targetdir, root) + if not os.path.exists(dir): + os.makedirs(dir) + output = open(os.path.join(dir, f), 'w') + input = applyTransformation(input) + output.write(input) + + +def applyTransformation(input): + for variable in re.findall(r"\{\{(.*?)\}\}", input): + tmp = variable.replace('.', '_') + input = input.replace(variable, tmp) + input = input.replace('{{', '{').replace('}}', '}') + input = re.sub(r"<}==true]\g<2>endif::[]", input) + input = re.sub(r"image:(\.\./)*", "image:", input) + input = re.sub(r"image::(\.\./)*", "image::", input) + return input + + +indir = 'topics' +targetdir = 'target' +if len(sys.argv) > 1: + targetdir = sys.argv[1] + +shutil.rmtree(os.path.join(targetdir, 'images'), ignore_errors=True) +shutil.rmtree(os.path.join(targetdir, 'keycloak-images'), ignore_errors=True) +shutil.rmtree(os.path.join(targetdir, 'rhsso-images'), ignore_errors=True) +shutil.copytree('images',os.path.join(targetdir, 'images')) +#shutil.copytree('keycloak-images',os.path.join(targetdir, 'keycloak-images')) +#shutil.copytree('rhsso-images',os.path.join(targetdir, 'rhsso-images')) + +tmp = os.path.join(targetdir, 'topics') +if not os.path.exists(tmp): + os.makedirs(tmp) + +# transform files +for root, dirs, filenames in os.walk(indir): + for f in filenames: + transform(root,f,targetdir) + +# Create master.doc includes +input = open('SUMMARY.adoc', 'r').read() +output = open(os.path.join(targetdir, 'master.adoc'), 'w') + +output.write(""" +:toc: +:toclevels: 3 +:numbered: + +include::document-attributes.adoc[] +""") + +input = re.sub(r"[ ]*\.+\s*link:(.*)\[(.*)\]", "include::\g<1>[]", input) +input = applyTransformation(input) +output.write(input) + +# parse book.json file and create document attributes +with open('book.json') as data_file: + data = json.load(data_file) + +variables = data['variables'] + +def makeAttributes(variables, variable, list): + for i in variables.keys(): + if variable is None: + tmp = i + else: + tmp = variable + '_' + i + if isinstance(variables[i],dict): + makeAttributes(variables[i], tmp, list) + elif isinstance(variables[i],bool): + boolval = 'false' + if variables[i]: + boolval = 'true' + list.append({tmp: boolval}) + else: + list.append({tmp: str(variables[i])}) + + +attributeList = [] +makeAttributes(variables, None, attributeList) + +output = open(os.path.join(targetdir, 'document-attributes.adoc'), 'w') +for attribute in attributeList: + for k in attribute.keys(): + output.write(':book_' + k + ": " + attribute[k] + "\n") + +print "Transformation complete!" + + + + + + + + + diff --git a/topics/adapter-context.adoc b/topics/oidc/java/adapter-context.adoc similarity index 85% rename from topics/adapter-context.adoc rename to topics/oidc/java/adapter-context.adoc index ba99e8f2bf..9ac083172f 100755 --- a/topics/adapter-context.adoc +++ b/topics/oidc/java/adapter-context.adoc @@ -1,10 +1,4 @@ -= KeycloakSecurityContext -:doctype: book -:sectnums: -:toc: left -:icons: font -:experimental: -:sourcedir: . +=== Keycloak Security Context The `KeycloakSecurityContext` interface is available if you need to look at the access token directly. This context is also useful if you need to get the encoded access token so you can make additional REST invocations. diff --git a/topics/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc similarity index 98% rename from topics/adapter_error_handling.adoc rename to topics/oidc/java/adapter_error_handling.adoc index 82c246b5f5..bf300cd7ca 100755 --- a/topics/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -1,6 +1,6 @@ [[_adapter_error_handling]] -= Error Handling +=== Error Handling Keycloak has some error handling facilities for servlet based client adapters. When an error is encountered in authentication, keycloak will call `HttpServletResponse.sendError()`. diff --git a/topics/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc similarity index 97% rename from topics/fuse-adapter.adoc rename to topics/oidc/java/fuse-adapter.adoc index 948cf69d4b..37888923f1 100755 --- a/topics/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter]] -= JBoss Fuse and Apache Karaf Adapter +=== JBoss Fuse and Apache Karaf Adapter Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.1 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. diff --git a/topics/jaas.adoc b/topics/oidc/java/jaas.adoc similarity index 99% rename from topics/jaas.adoc rename to topics/oidc/java/jaas.adoc index 5307d39e05..8145355ce3 100755 --- a/topics/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -1,6 +1,6 @@ [[_jaas_adapter]] -= JAAS plugin +=== JAAS plugin It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, but directly choose one of our adapters. However some applications and systems may still rely on pure legacy JAAS solution. diff --git a/topics/oidc.adoc b/topics/oidc/java/java-adapter-config.adoc old mode 100755 new mode 100644 similarity index 81% rename from topics/oidc.adoc rename to topics/oidc/java/java-adapter-config.adoc index 7c598b1d4e..26794cd084 --- a/topics/oidc.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -1,20 +1,9 @@ -[[_adapter_config]] - -= Adapters - -Keycloak can secure a wide variety of application types. -This section defines which application types are supported and how to configure and install them so that you can use Keycloak to secure your applications. - -These client adapters use an extension of the OpenID Connect protocol (a derivate of OAuth 2.0). This extension provides support for clustering, backchannel logout, and other non-standard adminstrative functions. -The Keycloak project also provides a separate, standalone, generic, SAML client adapter. -But that is describe in a separate document and has a different download. - - -== General Adapter Config +[[_java_adapter_config]] +=== Java Adapter Config Each adapter supported by Keycloak can be configured by a simple JSON text file. -This is what one might look like: +This is what one might look like: [source] @@ -45,117 +34,117 @@ This is what one might look like: "client-keystore-password" : "geheim", "client-key-password" : "geheim" } ----- +---- Some of these configuration switches may be adapter specific and some are common across all adapters. For Java adapters you can use `${...}` enclosure as System property replacement. For example `${jboss.server.config.dir}`. Also, you can obtain a template for this config file from the admin console. Go to the realm and select the application you want a template for. -Go to the `Installation` tab and this will provide you with a template that includes the public key of the realm. +Go to the `Installation` tab and this will provide you with a template that includes the public key of the realm. -Here is a description of each item: +Here is a description of each item: realm:: Name of the realm representing the users of your distributed applications and services. - This is _REQUIRED._ + This is _REQUIRED._ resource:: Username of the application. - Each application has a username that is used when the application connects with the Keycloak server to turn an access code into an access token (part of the OAuth 2.0 protocol). This is _REQUIRED._ + Each application has a username that is used when the application connects with the Keycloak server to turn an access code into an access token (part of the OAuth 2.0 protocol). This is _REQUIRED._ realm-public-key:: PEM format of public key. You can obtain this from the administration console. - This is _REQUIRED._ + This is _REQUIRED._ auth-server-url:: The base URL of the Keycloak Server. All other Keycloak pages and REST services are derived from this. - It is usually of the form `https://host:port/auth` This is _REQUIRED._ + It is usually of the form `https://host:port/auth` This is _REQUIRED._ ssl-required:: Ensures that all communication to and from the Keycloak server from the adapter is over HTTPS. This is _OPTIONAL_. The default value is _external_ meaning that HTTPS is required by default for external requests. - Valid values are 'all', 'external' and 'none'. + Valid values are 'all', 'external' and 'none'. use-resource-role-mappings:: If set to true, the adapter will look inside the token for application level role mappings for the user. If false, it will look at the realm level for user role mappings. This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. public-client:: If set to true, the adapter will not send credentials for the client to Keycloak. - The default value is _false_. + The default value is _false_. enable-cors:: This enables CORS support. It will handle CORS preflight requests. It will also look into the access token to determine valid origins. This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. cors-max-age:: If CORS is enabled, this sets the value of the `Access-Control-Max-Age` header. This is _OPTIONAL_. - If not set, this header is not returned in CORS responses. + If not set, this header is not returned in CORS responses. cors-allowed-methods:: If CORS is enabled, this sets the value of the `Access-Control-Allow-Methods` header. This should be a comma-separated string. This is _OPTIONAL_. - If not set, this header is not returned in CORS responses. + If not set, this header is not returned in CORS responses. cors-allowed-headers:: If CORS is enabled, this sets the value of the `Access-Control-Allow-Headers` header. This should be a comma-separated string. This is _OPTIONAL_. - If not set, this header is not returned in CORS responses. + If not set, this header is not returned in CORS responses. bearer-only:: This tells the adapter to only do bearer token authentication. That is, it will not do OAuth 2.0 redirects, but only accept bearer tokens through the `Authorization` header. This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. enable-basic-auth:: This tells the adapter to also support basic authentication. If this option is enabled, then _secret_ must also be provided. This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. expose-token:: If `true`, an authenticated browser client (via a Javascript HTTP invocation) can obtain the signed access token via the URL `root/k_query_bearer_token`. This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. credentials:: Specify the credentials of the application. This is an object notation where the key is the credential type and the value is the value of the credential type. Currently only `password` is supported. - This is _REQUIRED_. + This is _REQUIRED_. connection-pool-size:: Adapters will make separate HTTP invocations to the Keycloak Server to turn an access code into an access token. This config option defines how many connections to the Keycloak Server should be pooled. This is _OPTIONAL_. - The default value is `20`. + The default value is `20`. disable-trust-manager:: If the Keycloak Server requires HTTPS and this config option is set to `true` you do not have to specify a truststore. While convenient, this setting is not recommended as you will not be verifying the host name of the Keycloak Server. This is _OPTIONAL_. - The default value is `false`. + The default value is `false`. allow-any-hostname:: If the Keycloak Server requires HTTPS and this config option is set to `true` the Keycloak Server's certificate is validated via the truststore, but host name validation is not done. This is not a recommended. This seting may be useful in test environments This is _OPTIONAL_. - The default value is `false`. + The default value is `false`. truststore:: This setting is for Java adapters. @@ -166,55 +155,55 @@ truststore:: This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. You can create this truststore by extracting the public certificate of the Keycloak server's SSL keystore. - This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. + This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. truststore-password:: Password for the truststore keystore. - This is _REQUIRED_ if `truststore` is set. + This is _REQUIRED_ if `truststore` is set. client-keystore:: _Not supported yet, but we will support in future versions._ This setting is for Java adapters. This is the file path to a Java keystore file. This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the Keycloak server. - This is _OPTIONAL_. + This is _OPTIONAL_. client-keystore-password:: _Not supported yet, but we will support in future versions._ Password for the client keystore. - This is _REQUIRED_ if `client-keystore` is set. + This is _REQUIRED_ if `client-keystore` is set. client-key-password:: _Not supported yet, but we will support in future versions._ Password for the client's key. - This is _REQUIRED_ if `client-keystore` is set. + This is _REQUIRED_ if `client-keystore` is set. auth-server-url-for-backend-requests:: Alternative location of auth-server-url used just for backend requests. It must be absolute URI. - Useful especially in cluster (see <<_relative_uri_optimization,Relative URI Optimization>>) or if you would like to use _https_ for browser requests but stick with _http_ for backend requests etc. + Useful especially in cluster (see <<_relative_uri_optimization,Relative URI Optimization>>) or if you would like to use _https_ for browser requests but stick with _http_ for backend requests etc. always-refresh-token:: If _true_, Keycloak will refresh token in every request. - More info in <<_refresh_token_each_req,Refresh token in each request>> . + More info in <<_refresh_token_each_req,Refresh token in each request>> . register-node-at-startup:: If _true_, then adapter will send registration request to Keycloak. - It's _false_ by default and useful just in cluster (See <<_registration_app_nodes,Registration of application nodes to Keycloak>>) + It's _false_ by default and useful just in cluster (See <<_registration_app_nodes,Registration of application nodes to Keycloak>>) register-node-period:: Period for re-registration adapter to Keycloak. Useful in cluster. - See <<_registration_app_nodes,Registration of application nodes to Keycloak>> for details. + See <<_registration_app_nodes,Registration of application nodes to Keycloak>> for details. token-store:: Possible values are _session_ and _cookie_. Default is _session_, which means that adapter stores account info in HTTP Session. Alternative _cookie_ means storage of info in cookie. - See <<_stateless_token_store,Stateless token store>> for details. + See <<_stateless_token_store,Stateless token store>> for details. principal-attribute:: OpenID Connection ID Token attribute to populate the UserPrincipal name with. If token attribute is null, defaults to `sub`. - Possible values are `sub`, `preferred_username`, `email`, `name`, `nickname`, `given_name`, `family_name`. + Possible values are `sub`, `preferred_username`, `email`, `name`, `nickname`, `given_name`, `family_name`. turn-off-change-session-id-on-login:: The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off This is _OPTIONAL_. - The default value is _false_. + The default value is _false_. diff --git a/topics/oidc/java/java-adapters.adoc b/topics/oidc/java/java-adapters.adoc new file mode 100644 index 0000000000..274b47698a --- /dev/null +++ b/topics/oidc/java/java-adapters.adoc @@ -0,0 +1 @@ +== Java Adapters \ No newline at end of file diff --git a/topics/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc similarity index 98% rename from topics/jboss-adapter.adoc rename to topics/oidc/java/jboss-adapter.adoc index 351eba5206..d65ac497be 100755 --- a/topics/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -1,6 +1,6 @@ [[_jboss_adapter]] -= JBoss/Wildfly Adapter +=== JBoss/Wildfly Adapter To be able to secure WAR apps deployed on JBoss AS 7.1.1, JBoss EAP 6.x, or Wildfly, you must install and configure the Keycloak Subsystem. You then have two options to secure your WARs. @@ -9,7 +9,7 @@ Alternatively, you don't have to crack open your WARs at all and can apply Keycl Both methods are described in this section. [[_jboss_adapter_installation]] -== Adapter Installation +==== Adapter Installation Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on the Keycloak download site. @@ -144,7 +144,7 @@ public class CustomerService { We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain annotation when you want to propagate a keycloak security context to the EJB tier. -== Required Per WAR Configuration +==== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. @@ -206,7 +206,7 @@ Here's an example pulled from one of the examples that comes distributed with Ke ---- -== Securing WARs via Keycloak Subsystem +==== Securing WARs via Keycloak Subsystem You do not have to crack open a WAR to secure it with Keycloak. Alternatively, you can externally secure it via the Keycloak Adapter Subsystem. diff --git a/topics/jetty8-adapter.adoc b/topics/oidc/java/jetty8-adapter.adoc similarity index 94% rename from topics/jetty8-adapter.adoc rename to topics/oidc/java/jetty8-adapter.adoc index 39159755ad..2ccd38bd0d 100755 --- a/topics/jetty8-adapter.adoc +++ b/topics/oidc/java/jetty8-adapter.adoc @@ -1,13 +1,13 @@ [[_jetty8_adapter]] -= Jetty 8.1.x Adapter +=== Jetty 8.1.x Adapter Keycloak has a separate adapter for Jetty 8.1.x that you will have to install into your Jetty installation. You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. [[_jetty8_adapter_installation]] -== Adapter Installation +==== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. They are also available as a maven artifact. @@ -40,7 +40,7 @@ Edit start.ini and add keycloak to the options OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations,keycloak ---- -== Required Per WAR Configuration +==== Required Per WAR Configuration Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. Our 8.1.x adapter supports both keycloak.json and the jboss-web.xml advanced configuration. diff --git a/topics/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc similarity index 98% rename from topics/jetty9-adapter.adoc rename to topics/oidc/java/jetty9-adapter.adoc index ed0a8c6daa..d1972e4da9 100755 --- a/topics/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -1,13 +1,13 @@ [[_jetty9_adapter]] -= Jetty 9.x Adapters +=== Jetty 9.x Adapters Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will have to install into your Jetty installation. You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. [[_jetty9_adapter_installation]] -== Adapter Installation +==== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. They are also available as a maven artifact. @@ -34,7 +34,7 @@ $ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak ---- [[_jetty9_per_war]] -== Required Per WAR Configuration +==== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. diff --git a/topics/logout.adoc b/topics/oidc/java/logout.adoc similarity index 97% rename from topics/logout.adoc rename to topics/oidc/java/logout.adoc index da2a11e6b2..3a9c42dac4 100755 --- a/topics/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -1,5 +1,5 @@ -= Logout +=== Logout There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point the browser at the url `http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri`. diff --git a/topics/multi-tenancy.adoc b/topics/oidc/java/multi-tenancy.adoc similarity index 98% rename from topics/multi-tenancy.adoc rename to topics/oidc/java/multi-tenancy.adoc index b792d0bdcc..5f339f16be 100755 --- a/topics/multi-tenancy.adoc +++ b/topics/oidc/java/multi-tenancy.adoc @@ -1,5 +1,5 @@ -= Multi Tenancy +=== Multi Tenancy Multi Tenancy, in our context, means that one single target application (WAR) can be secured by a single (or clustered) Keycloak server, authenticating its users against different realms. In practice, this means that one application needs to use different `keycloak.json` files. diff --git a/topics/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc similarity index 98% rename from topics/servlet-filter-adapter.adoc rename to topics/oidc/java/servlet-filter-adapter.adoc index 07092c4a77..99e83244f7 100755 --- a/topics/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,5 +1,5 @@ -= Java Servlet Filter Adapter +=== Java Servlet Filter Adapter If you want to use Keycloak with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. This adapter works a little differently than the other adapters. diff --git a/topics/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc similarity index 96% rename from topics/spring-boot-adapter.adoc rename to topics/oidc/java/spring-boot-adapter.adoc index e3ae217af2..ef70d29bb1 100755 --- a/topics/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -1,11 +1,11 @@ -= Spring Boot Adapter +=== Spring Boot Adapter To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app. You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`). Let's go over these steps. [[_spring_boot_adapter_installation]] -== Adapter Installation +==== Adapter Installation The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all you need to do is add the Keycloak Spring Boot adapter JAR to your project. Depending on what container you are using with Spring Boot, you also need to add the appropriate Keycloak container adapter. @@ -29,7 +29,7 @@ If you are using Maven, add the following to your pom.xml (using Tomcat as an ex ---- [[_spring_boot_adapter_configuration]] -== Required Spring Boot Adapter Configuration +==== Required Spring Boot Adapter Configuration This section describes how to configure your Spring Boot app to use Keycloak. diff --git a/topics/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc similarity index 97% rename from topics/spring-security-adapter.adoc rename to topics/oidc/java/spring-security-adapter.adoc index cce17a5f46..2766317e32 100755 --- a/topics/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -1,5 +1,5 @@ -= Spring Security Adapter +=== Spring Security Adapter To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project. You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline. @@ -7,7 +7,7 @@ You then have to provide some extra beans in your Spring Security configuration Unlike the other Keycloak Adapters, you should not configure your security in web.xml. However, keycloak.json is still required. -== Adapter Installation +==== Adapter Installation Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. @@ -23,11 +23,11 @@ Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle ---- -== Spring Security Configuration +==== Spring Security Configuration The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax. -=== Java Configuration +===== Java Configuration Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a http://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/web/WebSecurityConfigurer.html[WebSecurityConfigurer] instance. The implementation allows customization by overriding methods. @@ -78,7 +78,7 @@ You must provide a session authentication strategy bean which should be of type Spring Security's `SessionFixationProtectionStrategy` is currently not supported because it changes the session identifier after login via Keycloak. If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier. -=== XML Configuration +===== XML Configuration While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose. @@ -148,13 +148,13 @@ While Spring Security's XML namespace simplifies configuration, customizing the ---- -== Multi Tenancy +==== Multi Tenancy The Keycloak Spring Security adapter also supports multi tenancy. Instead of injecting `AdapterDeploymentContextFactoryBean` with the path to `keycloak.json` you can inject an implementation of the `KeycloakConfigResolver` interface. More details on how to implement the `KeycloakConfigResolver` can be found in <<_multi_tenancy>>. -== Naming Security Roles +==== Naming Security Roles Spring Security, when using role-based authentication, requires that role names start with `ROLE_`. For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`. @@ -163,7 +163,7 @@ The class `org.keycloak.adapters.springsecurity.authentication.KeycloakAuthentic Use, for example, `org.springframework.security.core.authority.mapping.SimpleAuthorityMapper` to insert the `ROLE_` prefix and convert the role name to upper case. The class is part of Spring Security Core module. -== Client to Client Support +==== Client to Client Support To simplify communication between clients, Keycloak provides an extension of Spring's `RestTemplate` that handles bearer token authentication for you. To enable this feature your security configuration must add the `KeycloakRestTemplate` bean. @@ -227,7 +227,7 @@ public class RemoteProductService implements ProductService { } ---- -== Spring Boot Configuration +==== Spring Boot Configuration Spring Boot attempts to eagerly register filter beans with the web application context. Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add two ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice. diff --git a/topics/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc similarity index 96% rename from topics/tomcat-adapter.adoc rename to topics/oidc/java/tomcat-adapter.adoc index fc6b1b9d1a..06e93f794e 100755 --- a/topics/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -1,13 +1,13 @@ [[_tomcat_adapter]] -= Tomcat 6, 7 and 8 Adapters +=== Tomcat 6, 7 and 8 Adapters To be able to secure WAR apps deployed on Tomcat 6, 7 and 8 you must install the Keycloak Tomcat 6, 7 or 8 adapter into your Tomcat installation. You then have to provide some extra configuration in each WAR you deploy to Tomcat. Let's go over these steps. [[_tomcat_adapter_installation]] -== Adapter Installation +==== Adapter Installation Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on the Keycloak download site. @@ -28,7 +28,7 @@ $ unzip keycloak-tomcat7-adapter-dist.zip $ unzip keycloak-tomcat8-adapter-dist.zip ---- -== Required Per WAR Configuration +==== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. diff --git a/topics/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc similarity index 99% rename from topics/javascript-adapter.adoc rename to topics/oidc/javascript-adapter.adoc index e2bb8a7b5c..a9591730dd 100755 --- a/topics/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -1,5 +1,5 @@ -= Javascript Adapter +== Javascript Adapter The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications. This library is referenceable directly from the keycloak server. diff --git a/topics/installed-applications.adoc b/topics/oidc/oidc-generic.adoc old mode 100755 new mode 100644 similarity index 65% rename from topics/installed-applications.adoc rename to topics/oidc/oidc-generic.adoc index de5bbded5f..a697467c86 --- a/topics/installed-applications.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -1,23 +1,46 @@ +== Other OpenID Connect libraries -= Installed Applications +OAuth2 https://tools.ietf.org/html/rfc6749 +OpenID Connect http://openid.net/connect/ -Keycloak provides two special redirect uris for installed applications. + +=== Endpoints + +TODO + +=== Flows + +==== Authorization Grant + +==== Implicit + +==== Resource Owner Password Credentials + +==== Client Credentials + +=== Redirect URIs + +Keycloak provides two special redirect uris for installed applications. [[_installed_applications_url]] -== Installed Applications url +==== Installed Applications url -http://localhost +http://localhost This returns the code to a web server on the client as a query parameter. Any port number is allowed. -This makes it possible to start a web server for the installed application on any free port number without requiring changes in the `Admin Console`. +This makes it possible to start a web server for the installed application on any free port number without requiring changes in the `Admin Console`. [[_installed_applications_urn]] -== Installed Applications urn +==== Installed Applications urn -`urn:ietf:wg:oauth:2.0:oob` +`urn:ietf:wg:oauth:2.0:oob` If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. -With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. +With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. + +=== Session Management + +=== Dynamic Client Registration \ No newline at end of file diff --git a/topics/oidc/oidc-overview.adoc b/topics/oidc/oidc-overview.adoc new file mode 100644 index 0000000000..5be5b1b151 --- /dev/null +++ b/topics/oidc/oidc-overview.adoc @@ -0,0 +1,8 @@ +== OpenID Connect + +Keycloak can secure a wide variety of application types. +This section defines which application types are supported and how to configure and install them so that you can use Keycloak to secure your applications. + +These client adapters use an extension of the OpenID Connect protocol (a derivate of OAuth 2.0). This extension provides support for clustering, backchannel logout, and other non-standard adminstrative functions. +The Keycloak project also provides a separate, standalone, generic, SAML client adapter. +But that is describe in a separate document and has a different download. \ No newline at end of file diff --git a/topics/overview/overview.adoc b/topics/overview/overview.adoc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/topics/overview/what-are-client-adapters.adoc b/topics/overview/what-are-client-adapters.adoc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/topics/preface.adoc b/topics/preface.adoc index fb0c9657df..860e9fd39f 100755 --- a/topics/preface.adoc +++ b/topics/preface.adoc @@ -1,4 +1,4 @@ -= Preface +== Preface In some of the example listings, what is meant to be displayed on one line does not fit inside the available page width.These lines have been broken up. A '\' at the end of a line means that a break has been introduced to fit in the page, with the following lines indented. So: diff --git a/topics/saml/saml-overview.adoc b/topics/saml/saml-overview.adoc new file mode 100644 index 0000000000..98e42693b2 --- /dev/null +++ b/topics/saml/saml-overview.adoc @@ -0,0 +1 @@ +== SAML \ No newline at end of file From 5ee97837e2794b0f42ca7228cb2d02c3f83f7cd4 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 1 Jun 2016 13:30:02 +0200 Subject: [PATCH 005/194] ... --- SUMMARY.adoc | 1 + topics/oidc/java/fuse-adapter.adoc | 2 ++ topics/overview/supported-platforms.adoc | 1 + topics/overview/supported-protocols.adoc | 1 + topics/overview/what-are-client-adapters.adoc | 1 + 5 files changed, 6 insertions(+) create mode 100644 topics/overview/supported-platforms.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 1cc0a7c026..7dab10a63b 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -3,6 +3,7 @@ . link:topics/preface.adoc[Preface] . link:topics/overview/overview.adoc[Overview] .. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] + .. link:topics/overview/supported-platforms.adoc[Supported Platforms] .. link:topics/overview/supported-protocols.adoc[Supported Protocols] . link:topics/oidc/oidc-overview.adoc[OpenID Connect] diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 37888923f1..b74d5a52fa 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -2,6 +2,8 @@ [[_fuse_adapter]] === JBoss Fuse and Apache Karaf Adapter +NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported + Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.1 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. What is supported for Fuse/Karaf is: diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc new file mode 100644 index 0000000000..5273140c31 --- /dev/null +++ b/topics/overview/supported-platforms.adoc @@ -0,0 +1 @@ +== Supported Platforms \ No newline at end of file diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index e69de29bb2..77dd3dafeb 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -0,0 +1 @@ +== Supported Protocols \ No newline at end of file diff --git a/topics/overview/what-are-client-adapters.adoc b/topics/overview/what-are-client-adapters.adoc index e69de29bb2..bdce346c57 100644 --- a/topics/overview/what-are-client-adapters.adoc +++ b/topics/overview/what-are-client-adapters.adoc @@ -0,0 +1 @@ +== What are Client Adapters? \ No newline at end of file From 215236c736ee9addca2870f2ba4a1b867d0c8254 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 1 Jun 2016 16:25:57 +0200 Subject: [PATCH 006/194] Updated gitlab-conversion.py --- gitlab-conversion.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gitlab-conversion.py b/gitlab-conversion.py index a69a7382ab..630665f150 100755 --- a/gitlab-conversion.py +++ b/gitlab-conversion.py @@ -32,12 +32,15 @@ targetdir = 'target' if len(sys.argv) > 1: targetdir = sys.argv[1] -shutil.rmtree(os.path.join(targetdir, 'images'), ignore_errors=True) -shutil.rmtree(os.path.join(targetdir, 'keycloak-images'), ignore_errors=True) -shutil.rmtree(os.path.join(targetdir, 'rhsso-images'), ignore_errors=True) -shutil.copytree('images',os.path.join(targetdir, 'images')) -#shutil.copytree('keycloak-images',os.path.join(targetdir, 'keycloak-images')) -#shutil.copytree('rhsso-images',os.path.join(targetdir, 'rhsso-images')) +if os.path.exists(targetdir): + shutil.rmtree(targetdir) + +if os.path.isdir('images'): + shutil.copytree('images',os.path.join(targetdir, 'images')) +if os.path.isdir('keycloak-images'): + shutil.copytree('keycloak-images',os.path.join(targetdir, 'keycloak-images')) +if os.path.isdir('rhsso-images'): + shutil.copytree('rhsso-images',os.path.join(targetdir, 'rhsso-images')) tmp = os.path.join(targetdir, 'topics') if not os.path.exists(tmp): From 13f2ef4338ede17c19fbdecb554062c150c03b46 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Jun 2016 11:54:46 +0200 Subject: [PATCH 007/194] Added overview, supported platforms and what are client adapters! --- SUMMARY.adoc | 19 +---- topics/overview/overview.adoc | 7 ++ topics/overview/supported-platforms.adoc | 74 ++++++++++++++++++- topics/overview/supported-protocols.adoc | 1 + topics/overview/what-are-client-adapters.adoc | 6 +- 5 files changed, 87 insertions(+), 20 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 7dab10a63b..539a24f8be 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -30,21 +30,4 @@ .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] - . link:topics/saml/saml-overview.adoc[SAML] - - -// . link:topics/oidc.adoc[OpenID Connect Client Adapters] - -// -// -//{% if book.community %} -// -// -//{% endif %} -// .. link:topics/installed-applications.adoc[Installed Applications] -// -// -// -// . link:topics/saml.adoc[SAML Client Adapters] - - + . link:topics/saml/saml-overview.adoc[SAML] \ No newline at end of file diff --git a/topics/overview/overview.adoc b/topics/overview/overview.adoc index e69de29bb2..ea74de3034 100644 --- a/topics/overview/overview.adoc +++ b/topics/overview/overview.adoc @@ -0,0 +1,7 @@ +== Overview + +{{book.project.name}} supports both OpenID Connect (an extension to OAuth 2.0) and SAML 2.0. When securing clients and services the first thing you need to +decide is which of the two you are going to use. If you want you can also choose to secure some with OpenID Connect and others with SAML. + +To secure clients and services you are also going to need an adapter or library for the protocol you've selected. {{book.project.name}} comes with its own +adapters for selected platforms, but it is also possible to use generic OpenID Connect Resource Provider and SAML Service Provider libraries. \ No newline at end of file diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 5273140c31..3590ed4b3e 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -1 +1,73 @@ -== Supported Platforms \ No newline at end of file +== Supported Platforms + +=== OpenID Connect + +==== Java +* link:oidc/java/jboss-adapters.html[JBoss EAP] +{% if book.community %} + * link:oidc/java/jboss-adapters.html[WildFly] +{% endif %} +* link:oidc/java/fuse-adapter.html[Fuse] +{% if book.community %} + * link:oidc/java/tomcat-adapter.html[Tomcat] + * link:oidc/java/jetty-adapter.html[Jetty] +{% endif %} +* link:oidc/java/servlet-filter-adapter.html[Servlet Filter] +{% if book.community %} + * link:oidc/java/spring-adapter.html[Spring Security] (community) + * link:oidc/java/spring-boot-adapter.html[Spring Boot] (community) +{% endif %} + +==== JavaScript (client-side) +* link:oidc/javascript-adapter.html[JavaScript] + +=== Apache Cordova +* link:oidc/javascript-adapter.html[JavaScript] + +{% if book.community %} +==== Node.js +* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.title}} Connect] (community) +* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.title}} Auth Utils] (community) +{% endif %} + +{% if book.community %} +=== C# +* https://github.com/dylanplecki/KeycloakOwinAuthentication[OWIN] (community) +{% endif %} + +{% if book.community %} +=== Python +* https://pypi.python.org/pypi/python-openid/[python-openid] (generic) +{% endif %} + +{% if book.community %} +=== Android +* https://github.com/openid/AppAuth-Android[AppAuth] (generic) +* https://github.com/aerogear/aerogear-android-authz[AeroGear] (generic) +{% endif %} + +{% if book.community %} +=== iOS +* https://github.com/openid/AppAuth-iOS[AppAuth] (generic) +* https://github.com/aerogear/aerogear-ios-oauth2[AeroGear] (generic) +{% endif %} + +{% if book.community %} +==== Apache HTTP Server +* https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc] +{% endif %} + +=== SAML + +==== Java + +* link:oidc/java/jboss-adapters.html[JBoss EAP] +{% if book.community %} +* link:oidc/java/jboss-adapters.html[WildFly] +* link:oidc/java/tomcat-adapter.html[Tomcat] +* link:oidc/java/jetty-adapter.html[Jetty] +{% endif %} + +==== Apache HTTP Server + +* https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] \ No newline at end of file diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 77dd3dafeb..91becb0a40 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -1 +1,2 @@ +[[_supported_protocols]] == Supported Protocols \ No newline at end of file diff --git a/topics/overview/what-are-client-adapters.adoc b/topics/overview/what-are-client-adapters.adoc index bdce346c57..ba7c381349 100644 --- a/topics/overview/what-are-client-adapters.adoc +++ b/topics/overview/what-are-client-adapters.adoc @@ -1 +1,5 @@ -== What are Client Adapters? \ No newline at end of file +== What are Client Adapters? + +{{book.project.name}} client adapters are libraries that makes it very easy to secure applications and services with {{book.project.name}}. We call them +adapters rather than libraries as they provide a tight integration to the underlying platform and framework. This makes our adapters easy to use and they +require less boilerplate code than what is typically required by a library. \ No newline at end of file From b2831df24c94c5b4dee3d3b18c9ffd3202a5c47e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Jun 2016 13:03:57 +0200 Subject: [PATCH 008/194] Added supported protocols section (copy from admin guide) --- topics/overview/supported-protocols.adoc | 58 +++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 91becb0a40..250dc09b4e 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -1,2 +1,58 @@ [[_supported_protocols]] -== Supported Protocols \ No newline at end of file +== Supported Protocols + +=== OpenID Connect + +link:http://openid.net/connect/[Open ID Connect] (OIDC) is an authentication protocol that is an extension of link:https://tools.ietf.org/html/rfc6749[OAuth 2.0]. +While OAuth 2.0 is only a framework for building authorization protocols and is mainly incomplete, OIDC is a full-fledged authentication and authorization +protocol. OIDC also makes heavy use of the link:https://jwt.io[Json Web Token] (JWT) set of standards. These standards define a +identity token JSON format and ways to digitally sign and encrypt that data in a compact and web-friendly way. + +There is really two types of use cases when using OIDC. The first is an application that asks the {{book.project.name}} server to authenticate +a user for them. After a successful login, the application will receive an _identity token_ and an _access token_. The _identity token_ +contains information about the user such as username, email, and other profile information. The _access token_ is digitally signed by +the realm and contains access information (like user role mappings) that the application can use to determine what resources the user +is allowed to access on the application. + +The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks {{book.project.name}} +to obtain an _access token_ it can use to invoke on other remote services on behalf of the user. {{book.project.name}} authenticates the user +then asks the user for consent to grant access to the client requesting it. The client then receives the _access token_. This _access token_ +is digitally signed by the realm. The client can make REST invocations on remote services using this _access token_. The REST service +extracts the _access token_, verifies the signature of the token, then decides based on access information within the token whether or not to process +the request. + +=== SAML 2.0 + +link://https://saml.org/fill/this/in[SAML 2.0] is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora +of WS-* specifications so it tends to be a bit more verbose than OIDC. SAML 2.0 is primarily an authentication protocol +that works by exchanging XML documents between the authentication server and the application. XML signatures and encryption +is used to verify requests and responses. + +In {{book.project.name}} SAML serves two types of use cases: browser applications and REST invocations. + +There is really two types of use cases when using SAML. The first is an application that asks the {{book.project.name}} server to authenticate +a user for them. After a successful login, the application will receive an XML document that contains +something called a SAML assertion that specify various attributes about the user. This XML document is digitally signed by +the realm and contains access information (like user role mappings) that the application can use to determine what resources the user +is allowed to access on the application. + +The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks {{book.project.name}} +to obtain an SAML assertion it can use to invoke on other remote services on behalf of the user. + +=== OIDC vs. SAML + +Choosing between OIDC and SAML is not just a matter of using a newer, sexier protocol (OIDC) instead of the old, mature, dinosaur (SAML). +{{book.project.name}} has chosen OIDC as the protocol we use to both recommend and write all our extensions on top of. +SAML tends to be a bit more verbose than OIDC. + +Beyond verbosity of exchanged data, if you compare the specifications you'll find that OIDC was designed to work with the +web while SAML was retrofitted to work on top of the web. For example, +OIDC is also much better suited for HTML5/JavaScript applications because it is +much much simpler to implement on the client side than SAML. Since tokens are in the JSON format, +they can be directly consumed by JavaScript. Also, you'll find many nice little switches and features that +make implementing security in your web applications easier. For example, check out the iframe trick that the specification +uses to easily determine if a user is still logged in or not. + +SAML has its uses though. As you see the OIDC specifications evolve you see they implement more and more features that +SAML has had for years. What we often see is that people pick SAML over OIDC because of the perception that it is more mature +and also because they already have existing applications that are secured by it. From 1f117ac64a436156618edce199795bcd70866464 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Jun 2016 14:38:58 +0200 Subject: [PATCH 009/194] Added client registration chapter --- SUMMARY.adoc | 5 +- topics/client-registration.adoc | 157 +++++++++++++++++++ topics/oidc/java/application-clustering.adoc | 131 ++++++++++++++++ topics/oidc/java/java-adapter-config.adoc | 113 ++++++------- topics/oidc/java/java-adapters.adoc | 7 +- topics/oidc/oidc-overview.adoc | 8 +- 6 files changed, 348 insertions(+), 73 deletions(-) create mode 100644 topics/client-registration.adoc create mode 100644 topics/oidc/java/application-clustering.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 539a24f8be..e75bf64081 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -25,9 +25,12 @@ ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] + ... link:topics/oidc/java/application-clustering.adoc[Application Clustering] .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] - . link:topics/saml/saml-overview.adoc[SAML] \ No newline at end of file + . link:topics/saml/saml-overview.adoc[SAML] + + . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file diff --git a/topics/client-registration.adoc b/topics/client-registration.adoc new file mode 100644 index 0000000000..304436ebd3 --- /dev/null +++ b/topics/client-registration.adoc @@ -0,0 +1,157 @@ +== Client Registration + +In order for an application or service to utilize {{book.project.name}} it has to register a client in {{book.project.name}}. +An admin can do this through the admin console (or admin REST endpoints), but clients can also register themselves through the {{book.project.name}} client +registration service. + +The Client Registration Service provides built-in support for {{book.project.name}} Client Representations, OpenID Connect Client Meta Data and SAML Entity Descriptors. +The Client Registration Service endpoint is `/realms//clients-registrations/`. + +The built-in supported `providers` are: + +* default - {{book.project.name}} Client Representation (JSON) +* install - {{book.project.name}} Adapter Configuration (JSON) +* openid-connect - OpenID Connect Client Metadata Description (JSON) +* saml2-entity-descriptor - SAML Entity Descriptor (XML) + +The following sections will describe how to use the different providers. + +=== Authentication + +To invoke the Client Registration Services you need a token. The token can be a bearer token, an initial access token or a registration access token. + +==== Bearer Token + +The bearer token can be issued on behalf of a user or a Service Account. The following permissions are required to invoke the endpoints (see link:{{book.adminguide.link}}[{{book.adminguide.name}}] for more details): + +* create-client or manage-client - To create clients +* view-client or manage-client - To view clients +* manage-client - To update or delete client + +If you are using a bearer token to create clients it's recommend to use a token from a Service Account with only the `create-client` role (see link:{{book.adminguide.link}}[{{book.adminguide.name}}] for more details). + +==== Initial Access Token + +The recommended approach to registering new clients is by using initial access tokens. +An initial access token can only be used to create clients and has a configurable expiration as well as a configurable limit on how many clients can be created. + +An initial access token can be created through the admin console. +To create a new initial access token first select the realm in the admin console, then click on `Realm Settings` in the menu on the left, followed by `Initial Access Tokens` in the tabs displayed in the page. + +You will now be able to see any existing initial access tokens. If you have access you can delete tokens that are no longer required. You can only retrieve the +value of the token when you are creating it. To create a new token click on `Create`. You can now optionally add how long the token should be valid, also how +many clients can be created using the token. After you click on `Save` the token value is displayed. + +It is important that you copy/paste this token now as you won't be able to retrieve it later. If you forget to copy/paste it, then delete the token and create another one. + +The token value is used as a standard bearer token when invoking the Client Registration Services, by adding it to the Authorization header in the request. +For example: + +[source] +---- +Authorization: bearer eyJhbGciOiJSUz... +---- + +==== Registration Access Token + +When you create a client through the Client Registration Service the response will include a registration access token. +The registration access token provides access to retrieve the client configuration later, but also to update or delete the client. +The registration access token is included with the request in the same way as a bearer token or initial access token. +Registration access tokens are only valid once when it's used the response will include a new token. + +If a client was created outside of the Client Registration Service it won't have a registration access token associated with it. +You can create one through the admin console. This can also be useful if you loose the token for a particular client. +To create a new token find the client in the admin console and click on `Credentials`. Then click on `Generate registration access token`. + +=== {{book.project.name}} Representations + +The `default` client registration provider can be used to create, retrieve, update and delete a client. +It uses {{book.project.name}} Client Representation format which provides support for configuring clients exactly as they can be configured through the admin +console, including for example configuring protocol mappers. + +To create a client create a Client Representation (JSON) then do a HTTP POST to `/realms//clients-registrations/default`. + +It will return a Client Representation that also includes the registration access token. +You should save the registration access token somewhere if you want to retrieve the config, update or delete the client later. + +To retrieve the Client Representation then do a HTTP GET to `/realms//clients-registrations/default/`. + +It will also return a new registration access token. + +To update the Client Representation then do a HTTP PUT to with the updated Client Representation to: +`/realms//clients-registrations/default/`. + +It will also return a new registration access token. + +To delete the Client Representation then do a HTTP DELETE to: +`/realms//clients-registrations/default/` + +=== {{book.project.name}} Adapter Configuration + +The `installation` client registration provider can be used to retrieve the adapter configuration for a client. +In addition to token authentication you can also authenticate with client credentials using HTTP basic authentication. +To do this include the following header in the request: + +[source] +---- +Authorization: basic BASE64(client-id + ':' + client-secret) +---- + +To retrieve the Adapter Configuration then do a HTTP GET to `/realms//clients-registrations/install/`. + +No authentication is required for public clients. +This means that for the JavaScript adapter you can load the client configuration directly from {{book.project.name}} using the above URL. + +=== OpenID Connect Dynamic Client Registration + +{{book.project.name}} implements https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration], which extends https://tools.ietf.org/html/rfc7591[OAuth 2.0 Dynamic Client Registration Protocol] and https://tools.ietf.org/html/rfc7592[OAuth 2.0 Dynamic Client Registration Management Protocol]. + +The endpoint to use these specifications to register clients in {{book.project.name}} is `/realms//clients-registrations/openid-connect[/]`. + +This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm, `/realms//.well-known/openid-configuration`. + +=== SAML Entity Descriptors + +The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients. +It doesn't support retrieving, updating or deleting clients. +For those operations the {{book.project.name}} representation endpoints should be used. +When creating a client a {{book.project.name}} Client Representation is returned with details about the created client, including a registration access token. + +To create a client do a HTTP POST with the SAML Entity Descriptor to `/realms//clients-registrations/saml2-entity-descriptor`. + +=== Example using CURL + +The following example creates a client with the clientId `myclient` using CURL. You need to replace `eyJhbGciOiJSUz...` with a proper initial access token or +bearer token. + +[source,bash] +---- +curl -X POST \ + -d '{ "clientId": "myclient" }' \ + -H "Content-Type:application/json" \ + -H "Authorization: bearer eyJhbGciOiJSUz..." \ + http://localhost:8080/auth/realms/master/clients-registrations/default +---- + +=== Example using Java Client Registration API + +The Client Registration Java API makes it easy to use the Client Registration Service using Java. +To use include the dependency `org.keycloak:keycloak-client-registration-api:>VERSION<` from Maven. + +For full instructions on using the Client Registration refer to the JavaDocs. +Below is an example of creating a client. You need to replace `eyJhbGciOiJSUz...` with a proper initial access token or bearer token. + +[source,java] +---- +String token = "eyJhbGciOiJSUz..."; + +ClientRepresentation client = new ClientRepresentation(); +client.setClientId(CLIENT_ID); + +ClientRegistration reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/myrealm/clients").build(); +reg.auth(Auth.token(token)); + +client = reg.create(client); + +String registrationAccessToken = client.getRegistrationAccessToken(); +---- diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc new file mode 100644 index 0000000000..fbdecdaac7 --- /dev/null +++ b/topics/oidc/java/application-clustering.adoc @@ -0,0 +1,131 @@ +[[_applicationclustering]] += Application Clustering + +This chapter is focused on clustering support for your own AS7, EAP6 or Wildfly applications, which are secured by Keycloak. +We support various deployment scenarios according if your application is: + +* stateless or stateful +* distributable (replicated http session) or non-distributable and just relying on sticky sessions provided by loadbalancer +* deployed on same or different cluster hosts where keycloak servers are deployed + +The situation is a bit tricky as application communicates with Keycloak directly within user's browser (for example redirecting to login screen), but there is also backend (out-of-bound) communication between keycloak and application, which is hidden from end-user and his browser and hence can't rely on sticky sessions. + +NOTE: To enable distributable (replicated) HTTP Sessions in your application, you may need to do some additional steps. +Usually you need to put tag into `WEB-INF/web.xml` file of your application and possibly do some additional steps to configure underlying cluster cache (In case of Wildfly, the implementation of cluster cache is based on Infinispan). These steps are server specific, so consult documentation of your application server for more details. + +== Stateless token store + +By default, the servlet web application secured by Keycloak uses HTTP session to store information about authenticated user account. +This means that this info could be replicated across cluster and your application will safely survive failover of some cluster node. + +However if you don't need or don't want to use HTTP Session, you may alternatively save all info about authenticated account into cookie. +This is useful especially if your application is: + +* stateless application without need of HTTP Session, but with requirement to be safe to failover of some cluster node +* stateful application, but you don't want sensitive token data to be saved in HTTP session +* stateless application relying on loadbalancer, which is not aware of sticky sessions (in this case cookie is your only way) + +To configure this, you can add this line to configuration of your adapter in `WEB-INF/keycloak.json` of your application: +[source] +---- + + +"token-store": "cookie" +---- + +Default value of `token-store` is `session`, hence saving data in HTTP session. + +One limitation of cookie store is, that whole info about account is passed in cookie KEYCLOAK_ADAPTER_STATE in each HTTP request. +Hence it's not the best for network performance. +Another small limitation is limited support for Single-Sign out. +It works without issues if you init servlet logout (HttpServletRequest.logout) from this application itself as the adapter will delete the KEYCLOAK_ADAPTER_STATE cookie. +But back-channel logout initialized from different application can't be propagated by Keycloak to this application with cookie store. +Hence it's recommended to use very short value of access token timeout (1 minute for example). + +== Relative URI optimization + +In many deployment scenarios will be Keycloak and secured applications deployed on same cluster hosts. +For this case Keycloak already provides option to use relative URI as value of option _auth-server-url_ in `WEB-INF/keycloak.json` . In this case, the URI of Keycloak server is resolved from the URI of current request. + +For example if your loadbalancer is on _https://loadbalancer.com/myapp_ and auth-server-url is _/auth_, then relative URI of Keycloak is resolved to be _https://loadbalancer.com/auth_ . + +For cluster setup, it may be even better to use option _auth-server-url-for-backend-request_ . This allows to configure that backend requests between Keycloak and your application will be sent directly to same cluster host without additional round-trip through loadbalancer. +So for this, it's good to configure values in `WEB-INF/keycloak.json` like this: +[source] +---- + + +"auth-server-url": "/auth", +"auth-server-url-for-backend-requests": "http://${jboss.host.name}:8080/auth" +---- + +This would mean that browser requests (like redirecting to Keycloak login screen) will be still resolved relatively to current request URI like _https://loadbalancer.com/myapp_, but backend (out-of-bound) requests between keycloak and your app are sent always to same cluster host with application . + +Note that additionally to network optimization, you may not need "https" in this case as application and keycloak are communicating directly within same cluster host. + +== Admin URL configuration + +Admin URL for particular application can be configured in Keycloak admin console. +It's used by Keycloak server to send backend requests to application for various tasks, like logout users or push revocation policies. + +For example logout of user from Keycloak works like this: + +. User sends logout request from one of applications where he is logged. +. Then application will send logout request to Keycloak +. Keycloak server logout user in itself, and then it re-sends logout request by backend channel to all applications where user is logged. + Keycloak is using admin URL for this. + So logout is propagated to all apps. + +You may again use relative values for admin URL, but in cluster it may not be the best similarly like in <<_relative_uri_optimization,previous section>> . + +Some examples of possible values of admin URL are: + +http://${jboss.host.name}:8080/myapp:: + This is best choice if "myapp" is deployed on same cluster hosts like Keycloak and is distributable. + In this case Keycloak server sends logout request to itself, hence no communication with loadbalancer or other cluster nodes and no additional network traffic. + +http://${application.session.host}:8080/myapp:: + Keycloak will track hosts where is particular HTTP Session served and it will send session invalidation message to proper cluster node. + +[[_registration_app_nodes]] +== Registration of application nodes to Keycloak + +Previous section describes how can Keycloak send logout request to proper application node. +However in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. +For example push new notBefore for realm or application, or logout all users from all applications on all cluster nodes. + +In this case Keycloak should be aware of all application cluster nodes, so it could send event to all of them. +To achieve this, we support auto-discovery mechanism: + +. Once new application node joins cluster, it sends registration request to Keycloak server +. The request may be re-sent to Keycloak in configured periodic intervals +. If Keycloak won't receive re-registration request within specified timeout (should be greater than period from point 2) then it automatically unregister particular node +. Node is also unregistered in Keycloak when it sends unregistration request, which is usually during node shutdown or application undeployment. + This may not work properly for forced shutdown when undeployment listeners are not invoked, so here you need to rely on automatic unregistration from point 3 . + +Sending startup registrations and periodic re-registration is disabled by default, as it's main usecase is just cluster deployment. +In `WEB-INF/keycloak.json` of your application, you can specify: + +[source] +---- +"register-node-at-startup": true, +"register-node-period": 600, +---- +which means that registration is sent at startup (accurately when 1st request is served by the application node) and then it's resent each 10 minutes. + +In Keycloak admin console you can specify the maximum node re-registration timeout (makes sense to have it bigger than _register-node-period_ from adapter configuration for particular application). Also you can manually add and remove cluster nodes in admin console, which is useful if you don't want to rely on adapter's automatic registration or if you want to remove stale application nodes, which weren't unregistered (for example due to forced shutdown). + +[[_refresh_token_each_req]] +== Refresh token in each request + +By default, application adapter tries to refresh access token when it's expired (period can be specified as <<_token_timeouts,Access Token Lifespan>>) . However if you don't want to rely on the fact, that Keycloak is able to successfully propagate admin events like logout to your application nodes, then you have possibility to configure adapter to refresh access token in each HTTP request. + +In `WEB-INF/keycloak.json` you can configure: + +[source] +---- +"always-refresh-token": true +---- + +Note that this has big performance impact. +It's useful just if performance is not priority, but security is critical and you can't rely on logout and push notBefore propagation from Keycloak to applications. diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 26794cd084..8067941f38 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -2,11 +2,10 @@ [[_java_adapter_config]] === Java Adapter Config -Each adapter supported by Keycloak can be configured by a simple JSON text file. +Each Java adapter supported by Keycloak can be configured by a simple JSON file. This is what one might look like: - -[source] +[source,json] ---- { "realm" : "demo", @@ -36,84 +35,75 @@ This is what one might look like: } ---- -Some of these configuration switches may be adapter specific and some are common across all adapters. -For Java adapters you can use `${...}` enclosure as System property replacement. -For example `${jboss.server.config.dir}`. -Also, you can obtain a template for this config file from the admin console. -Go to the realm and select the application you want a template for. -Go to the `Installation` tab and this will provide you with a template that includes the public key of the realm. - -Here is a description of each item: +You can use `${...}` enclosure for system property replacement. For example `${jboss.server.config.dir}` would be replaced by `/path/to/{{book.project.name}}`. +The initial config file can be ontained from the the admin console. This can be done by opening the admin console, select `Clients` from the menu and clicking +on the corresponding client. Once the page for the client is opened click on the `Installation` tab and select `Keycloak OIDC JSON`. +Here is a description of each configuration option: realm:: Name of the realm representing the users of your distributed applications and services. This is _REQUIRED._ resource:: - Username of the application. - Each application has a username that is used when the application connects with the Keycloak server to turn an access code into an access token (part of the OAuth 2.0 protocol). This is _REQUIRED._ - -realm-public-key:: - PEM format of public key. - You can obtain this from the administration console. + The client-id of the application. Each application has a client-id that is used to identify the application. This is _REQUIRED._ +realm-public-key:: + PEM format of the realm public key. You can obtain this from the administration console. + This is OPTION._ + auth-server-url:: - The base URL of the Keycloak Server. - All other Keycloak pages and REST services are derived from this. - It is usually of the form `https://host:port/auth` This is _REQUIRED._ + The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `https://host:port/auth`. + This is _REQUIRED._ ssl-required:: - Ensures that all communication to and from the Keycloak server from the adapter is over HTTPS. + Ensures that all communication to and from the {{book.project.name}} server is over HTTPS. + In production this should be set to `all`. This is _OPTIONAL_. - The default value is _external_ meaning that HTTPS is required by default for external requests. + The default value is _external_ meaning that HTTPS is required by default for external requests. Valid values are 'all', 'external' and 'none'. use-resource-role-mappings:: - If set to true, the adapter will look inside the token for application level role mappings for the user. - If false, it will look at the realm level for user role mappings. + If set to true, the adapter will look inside the token for application level role mappings for the user. If false, it will look at the realm level for user role mappings. This is _OPTIONAL_. The default value is _false_. public-client:: If set to true, the adapter will not send credentials for the client to Keycloak. + This is _OPTIONAL_. The default value is _false_. enable-cors:: - This enables CORS support. - It will handle CORS preflight requests. - It will also look into the access token to determine valid origins. + This enables CORS support. It will handle CORS preflight requests. It will also look into the access token to determine valid origins. This is _OPTIONAL_. The default value is _false_. cors-max-age:: - If CORS is enabled, this sets the value of the `Access-Control-Max-Age` header. + If CORS is enabled, this sets the value of the `Access-Control-Max-Age` header. This is _OPTIONAL_. If not set, this header is not returned in CORS responses. cors-allowed-methods:: - If CORS is enabled, this sets the value of the `Access-Control-Allow-Methods` header. + If CORS is enabled, this sets the value of the `Access-Control-Allow-Methods` header. This should be a comma-separated string. This is _OPTIONAL_. If not set, this header is not returned in CORS responses. cors-allowed-headers:: - If CORS is enabled, this sets the value of the `Access-Control-Allow-Headers` header. + If CORS is enabled, this sets the value of the `Access-Control-Allow-Headers` header. This should be a comma-separated string. This is _OPTIONAL_. If not set, this header is not returned in CORS responses. bearer-only:: - This tells the adapter to only do bearer token authentication. - That is, it will not do OAuth 2.0 redirects, but only accept bearer tokens through the `Authorization` header. + This should be set to _true_ for services. If enabled the adapter will not attempt to authenticate users, but only verify bearer tokens. This is _OPTIONAL_. The default value is _false_. enable-basic-auth:: - This tells the adapter to also support basic authentication. - If this option is enabled, then _secret_ must also be provided. + This tells the adapter to also support basic authentication. If this option is enabled, then _secret_ must also be provided. This is _OPTIONAL_. The default value is _false_. @@ -123,9 +113,8 @@ expose-token:: The default value is _false_. credentials:: - Specify the credentials of the application. - This is an object notation where the key is the credential type and the value is the value of the credential type. - Currently only `password` is supported. + Specify the credentials of the application. This is an object notation where the key is the credential type and the value is the value of the credential type. + Currently `password` and `jwt` is supported. This is _REQUIRED_. connection-pool-size:: @@ -135,69 +124,63 @@ connection-pool-size:: The default value is `20`. disable-trust-manager:: - If the Keycloak Server requires HTTPS and this config option is set to `true` you do not have to specify a truststore. - While convenient, this setting is not recommended as you will not be verifying the host name of the Keycloak Server. + If the {{book.project.name}} server requires HTTPS and this config option is set to `true` you do not have to specify a truststore. + This setting should only be used during development and *never* in production as it will disable verification of SSL certificates. This is _OPTIONAL_. The default value is `false`. allow-any-hostname:: - If the Keycloak Server requires HTTPS and this config option is set to `true` the Keycloak Server's certificate is validated via the truststore, but host name validation is not done. - This is not a recommended. + If the {{book.project.name}} server requires HTTPS and this config option is set to `true` the Keycloak Server's certificate is validated via the truststore, + but host name validation is not done. + This setting should only be used during development and *never* in production as it will disable verification of SSL certificates. This seting may be useful in test environments This is _OPTIONAL_. The default value is `false`. truststore:: - This setting is for Java adapters. - The value is the file path to a Java keystore file. + The value is the file path to a keystore file. If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead. - Used for outgoing HTTPS communications to the Keycloak server. + Used for outgoing HTTPS communications to the {{book.project.name}} server. Client making HTTPS requests need a way to verify the host of the server they are talking to. This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. You can create this truststore by extracting the public certificate of the Keycloak server's SSL keystore. - This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. + This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. truststore-password:: Password for the truststore keystore. - This is _REQUIRED_ if `truststore` is set. + This is _REQUIRED_ if `truststore` is set and the truststore requires a password. client-keystore:: - _Not supported yet, but we will support in future versions._ This setting is for Java adapters. - This is the file path to a Java keystore file. - This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the Keycloak server. + This is the file path to a keystore file. + This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the {{book.project.name}} server. This is _OPTIONAL_. client-keystore-password:: - _Not supported yet, but we will support in future versions._ Password for the client keystore. - This is _REQUIRED_ if `client-keystore` is set. + Password for the client keystore. + This is _REQUIRED_ if `client-keystore` is set. client-key-password:: - _Not supported yet, but we will support in future versions._ Password for the client's key. - This is _REQUIRED_ if `client-keystore` is set. - -auth-server-url-for-backend-requests:: - Alternative location of auth-server-url used just for backend requests. - It must be absolute URI. - Useful especially in cluster (see <<_relative_uri_optimization,Relative URI Optimization>>) or if you would like to use _https_ for browser requests but stick with _http_ for backend requests etc. + Password for the client's key. + This is _REQUIRED_ if `client-keystore` is set. always-refresh-token:: - If _true_, Keycloak will refresh token in every request. - More info in <<_refresh_token_each_req,Refresh token in each request>> . + If _true_, the adapter will refresh token in every request. register-node-at-startup:: If _true_, then adapter will send registration request to Keycloak. - It's _false_ by default and useful just in cluster (See <<_registration_app_nodes,Registration of application nodes to Keycloak>>) + It's _false_ by default and useful only when application is clustered. + See link:application-clustering.html[Application Clustering] for details register-node-period:: Period for re-registration adapter to Keycloak. - Useful in cluster. - See <<_registration_app_nodes,Registration of application nodes to Keycloak>> for details. + Useful when application is clustered. + See link:application-clustering.html[Application Clustering] for details token-store:: Possible values are _session_ and _cookie_. Default is _session_, which means that adapter stores account info in HTTP Session. Alternative _cookie_ means storage of info in cookie. - See <<_stateless_token_store,Stateless token store>> for details. + See link:application-clustering.html[Application Clustering] for details principal-attribute:: OpenID Connection ID Token attribute to populate the UserPrincipal name with. @@ -205,5 +188,5 @@ principal-attribute:: Possible values are `sub`, `preferred_username`, `email`, `name`, `nickname`, `given_name`, `family_name`. turn-off-change-session-id-on-login:: - The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off This is _OPTIONAL_. + The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true if you want to turn this off This is _OPTIONAL_. The default value is _false_. diff --git a/topics/oidc/java/java-adapters.adoc b/topics/oidc/java/java-adapters.adoc index 274b47698a..a6a5f10113 100644 --- a/topics/oidc/java/java-adapters.adoc +++ b/topics/oidc/java/java-adapters.adoc @@ -1 +1,6 @@ -== Java Adapters \ No newline at end of file +== Java Adapters + +{{book.project.name}} comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform. + +All Java adapters share a set of common configuration options described in the link:java-adapter-config.html[Java Adapters Config] chapter. There are also +a few more chapters that are relevant to all Java adapters. \ No newline at end of file diff --git a/topics/oidc/oidc-overview.adoc b/topics/oidc/oidc-overview.adoc index 5be5b1b151..8436712c35 100644 --- a/topics/oidc/oidc-overview.adoc +++ b/topics/oidc/oidc-overview.adoc @@ -1,8 +1,4 @@ == OpenID Connect -Keycloak can secure a wide variety of application types. -This section defines which application types are supported and how to configure and install them so that you can use Keycloak to secure your applications. - -These client adapters use an extension of the OpenID Connect protocol (a derivate of OAuth 2.0). This extension provides support for clustering, backchannel logout, and other non-standard adminstrative functions. -The Keycloak project also provides a separate, standalone, generic, SAML client adapter. -But that is describe in a separate document and has a different download. \ No newline at end of file +This section describes how you can secure applications and services with OpenID Connect using either {{book.project.name}} adapters or generic OpenID Connect +Resource Provider libraries. \ No newline at end of file From 44a55fd1362592cf87b6d127ee0c0c1ee46706fb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Jun 2016 15:16:53 +0200 Subject: [PATCH 010/194] Change title --- book.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book.json b/book.json index d186d09b8c..39fcf1969b 100755 --- a/book.json +++ b/book.json @@ -9,7 +9,7 @@ "splitter" ], "variables": { - "title": "Keycloak Securing Client Applications Guide", + "title": "Securing Applications and Services Guide", "community": true, "product": false, "images": "keycloak-images", From fda62c21b7bf79078471cfab20416dbe9dc36a61 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 2 Jun 2016 16:29:37 +0200 Subject: [PATCH 011/194] Fuse adapter documentation --- topics/oidc/java/fuse-adapter.adoc | 91 ++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index b74d5a52fa..c8833f4154 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -4,7 +4,9 @@ NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported -Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.1 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. +Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . +It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.2 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] +under the covers and Jetty is used for running various kinds of web applications. What is supported for Fuse/Karaf is: @@ -13,7 +15,88 @@ What is supported for Fuse/Karaf is: * Security for http://camel.apache.org/[Apache Camel] Jetty endpoints running with http://camel.apache.org/jetty.html[Camel Jetty] component. * Security for http://cxf.apache.org/[Apache CXF] endpoints running on their own separate http://cxf.apache.org/docs/jetty-configuration.html[Jetty engine]. * Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet. -* Security for SSH and JMX admin access. -* Security for http://hawt.io/[Hawt.io admin console] . +* Security for SSH and JMX admin access. -The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory `examples/fuse` . \ No newline at end of file +==== How to secure your web applications inside Fuse + +The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory `fuse` . Most of the steps should be understandable from testing and +understanding the demo. + +Basically all mentioned web applications require to inject Keycloak Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different +according to application type. + + +===== Classic WAR application + +The needed steps are: + +* Declare needed constraints in `/WEB-INF/web.xml` +* Add `jetty-web.xml` file with the authenticator to `/WEB-INF/jetty-web.xml` and add `/WEB-INF/keycloak.json` with your Keycloak configuration +* Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in MANIFEST.MF file in header `Import-Package`. It's +recommended to use maven-bundle-plugin similarly like Fuse examples are doing, but note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package +as it's not used by application or Blueprint or Spring descriptor, but it's used just in jetty-web.xml file. + +Take a look at `customer-portal-app` from fuse example for inspiration. + +===== Servlet web application deployed by pax-whiteboard-extender + +The needed steps are: + +* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. +Example `product-portal-app` declares this in `OSGI-INF/blueprint/blueprint.xml` . Note that your servlet needs to depend on it. +* Steps 2,3 are same like for classic WAR + +Take a look at `product-portal-app` for inspiration. + +===== Apache camel application + +You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] endpoint by adding securityHandler with `KeycloakJettyAuthenticator` and +proper security constraints injected. Take a look at `OSGI-INF/blueprint/blueprint.xml` configuration in `camel` application on example of how it can be done in details. + +===== Apache CXF endpoint + +It's recommended to run your CXF endpoints secured by Keycloak on separate Jetty engine. You need to add `META-INF/spring/beans.xml` to your application +and then declare `httpj:engine-factory` with Jetty SecurityHandler with injected `KeycloakJettyAuthenticator` inside. + +Fore more details, take a look at example application `cxf-ws` from Keycloak Fuse demo, which is using separate endpoint on +http://localhost:8282 . All the important configuration inside this application is declared in `META-INF/spring/beans.xml` . + +===== Builtin CXF web applications + +Some services automatically come with deployed servlets on startup. One of such examples is CXF servlet running on +http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which Keycloak is currently using, +is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by Keycloak. +You can see the `OSGI-INF/blueprint/blueprint.xml` inside `cxf-jaxrs` example, which adds JAX-RS `customerservice` endpoint and more importantly, it secures whole `/cxf` context. + +As a side effect, all other CXF services running on default CXF HTTP destination will be secured too. Once you uninstall feature `keycloak-fuse-6.2-example`, the +original unsecured servlet on `/cxf` context is deployed back and hence context will become unsecured again. + +It's recommended to use your own Jetty engine for your apps (similarly like `cxf-jaxws` application is doing). + + +==== How to secure Fuse admin services + +===== SSH authentication to Fuse terminal with Keycloak credentials + +Keycloak mainly addresses usecases for authentication of web applications, however if your admin services (like fuse admin console) are protected +with Keycloak, it may be good to protect non-web services like SSH with Keycloak credentials too. It's possible to do it by using JAAS login module, which +allows to remotely connect to Keycloak and verify credentials based on <<_direct_access_grants,Direct Access Grants>> . + +Example steps for enable SSH authentication require changing the configuration of `sshRealm` in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`, then adding +file `$FUSE_HOME/etc/keycloak-direct-access.json` (this is default location, which can be changed) and install the needed feature `keycloak-jaas`. It's described in details +in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . + + +===== JMX authentication with Keycloak credentials + +This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may +be better to use just hawt.io/jolokia as jolokia agent is installed in http://hawt.io by default. + +You need to configure `jmxRealm` in `$FUSE_HOME/etc/org.apache.karaf.management.cfg`, then adding file `$FUSE_HOME/etc/keycloak-direct-access.json` +(this is default location, which can be changed) and install the needed feature `keycloak-jaas`. +It's described in details in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . + + +===== Secure Fuse admin console + +Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak. \ No newline at end of file From c9fc21ee22551a991561e9d4449174916a44384a Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 12:07:45 -0400 Subject: [PATCH 012/194] initial saml --- SUMMARY.adoc | 36 +++++- .../saml/java/MigrationFromOlderVersions.adoc | 11 ++ topics/saml/java/assertion-api.adoc | 105 +++++++++++++++++ topics/saml/java/debugging.adoc | 6 + topics/saml/java/error_handling.adoc | 41 +++++++ topics/saml/java/general-config.adoc | 58 +++++++++ .../saml/java/general-config/idp_element.adoc | 30 +++++ .../general-config/idp_keys_subelement.adoc | 6 + .../idp_singlelogoutservice_subelement.adoc | 46 ++++++++ .../idp_singlesignonservice_subelement.adoc | 33 ++++++ .../roleidentifiers_element.adoc | 20 ++++ topics/saml/java/general-config/sp-keys.adoc | 29 +++++ .../java/general-config/sp-keys/key_pems.adoc | 6 + .../sp-keys/keystore_element.adoc | 19 +++ .../saml/java/general-config/sp_element.adoc | 49 ++++++++ .../sp_principalname_mapping_element.adoc | 11 ++ topics/saml/java/idp-registration.adoc | 5 + topics/saml/java/java-adapters.adoc | 4 + topics/saml/java/jboss-adapter.adoc | 9 ++ .../jboss_adapter_installation.adoc | 111 ++++++++++++++++++ .../required_per_war_configuration.adoc | 62 ++++++++++ .../java/jboss-adapter/securing_wars.adoc | 82 +++++++++++++ topics/saml/java/jetty-adapter.adoc | 6 + .../jetty-adapter/jetty8-installation.adoc | 32 +++++ .../jetty-adapter/jetty8-per_war_config.adoc | 5 + .../jetty-adapter/jetty9_installation.adoc | 27 +++++ .../jetty-adapter/jetty9_per_war_config.adoc | 64 ++++++++++ topics/saml/java/logout.adoc | 6 + topics/saml/java/saml_adapter_overview.adoc | 6 + topics/saml/java/servlet-filter-adapter.adoc | 53 +++++++++ topics/saml/java/tomcat-adapter.adoc | 8 ++ .../tomcat_adapter_installation.adoc | 22 ++++ .../tomcat_adapter_per_war_config.adoc | 53 +++++++++ topics/saml/saml-overview.adoc | 5 +- 34 files changed, 1064 insertions(+), 2 deletions(-) create mode 100644 topics/saml/java/MigrationFromOlderVersions.adoc create mode 100644 topics/saml/java/assertion-api.adoc create mode 100644 topics/saml/java/debugging.adoc create mode 100644 topics/saml/java/error_handling.adoc create mode 100644 topics/saml/java/general-config.adoc create mode 100644 topics/saml/java/general-config/idp_element.adoc create mode 100644 topics/saml/java/general-config/idp_keys_subelement.adoc create mode 100644 topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc create mode 100644 topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc create mode 100644 topics/saml/java/general-config/roleidentifiers_element.adoc create mode 100644 topics/saml/java/general-config/sp-keys.adoc create mode 100644 topics/saml/java/general-config/sp-keys/key_pems.adoc create mode 100644 topics/saml/java/general-config/sp-keys/keystore_element.adoc create mode 100644 topics/saml/java/general-config/sp_element.adoc create mode 100644 topics/saml/java/general-config/sp_principalname_mapping_element.adoc create mode 100644 topics/saml/java/idp-registration.adoc create mode 100644 topics/saml/java/java-adapters.adoc create mode 100644 topics/saml/java/jboss-adapter.adoc create mode 100644 topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc create mode 100644 topics/saml/java/jboss-adapter/required_per_war_configuration.adoc create mode 100644 topics/saml/java/jboss-adapter/securing_wars.adoc create mode 100644 topics/saml/java/jetty-adapter.adoc create mode 100644 topics/saml/java/jetty-adapter/jetty8-installation.adoc create mode 100644 topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc create mode 100644 topics/saml/java/jetty-adapter/jetty9_installation.adoc create mode 100644 topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc create mode 100644 topics/saml/java/logout.adoc create mode 100644 topics/saml/java/saml_adapter_overview.adoc create mode 100644 topics/saml/java/servlet-filter-adapter.adoc create mode 100644 topics/saml/java/tomcat-adapter.adoc create mode 100644 topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc create mode 100644 topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index e75bf64081..b5aec1b67e 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -32,5 +32,39 @@ .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] . link:topics/saml/saml-overview.adoc[SAML] - + .. link:topics/saml/java/java-adapters.adoc[Java Adapters] + ... link:topics/saml/java/general-config.adoc[General Adapter Config] + .... link:topics/saml/java/general-config/sp_element.adoc[SP Element] + .... link:topics/saml/java/general-config/sp-keys.adoc[SP Keys and Key elements] + ..... link:topics/saml/java/general-config/sp-keys/keystore_element.adoc[KeyStore Element] + ..... link:topics/saml/java/general-config/sp-keys/key_pems.adoc[Key PEMS] + .... link:topics/saml/java/general-config/sp_principalname_mapping_element.adoc[SP PrincipalNameMapping element] + .... link:topics/saml/java/general-config/roleidentifiers_element.adoc[RoleIdentifiers element] + .... link:topics/saml/java/general-config/idp_element.adoc[IDP Element] + .... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] + .... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] + .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] + ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] + .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] + .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Required Per WAR Configuration] + .... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] + {% if book.community %} + ... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] + .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] + .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Required Per WAR Configuration] + ... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] + .... link:topics/saml/java/jetty9_installation.adoc[Jetty 9 Adapter Installation] + .... link:topics/saml/java/jetty9_per_war_config.adoc[Jetty 9 Required Per WAR Configuration] + .... link:topics/saml/java/jetty8-installation.adoc[Jetty 8 Adapter Installation] + .... link:topics/saml/java/jetty8-per_war_config.adoc[Jetty 8 Required Per WAR Configuration] + {% endif %} + ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] + ... link:topics/saml/java/logout.adoc[Logout] + ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] + ... link:topics/saml/java/error_handling.adoc[Error Handling] + ... link:topics/saml/java/debugging.adoc[Troubleshooting] + {% if book.community %} + ... link:topics/saml/java/saml_adapter_MigrationFromOlderVersions.adoc[Migration from older versions] + {% endif %} . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file diff --git a/topics/saml/java/MigrationFromOlderVersions.adoc b/topics/saml/java/MigrationFromOlderVersions.adoc new file mode 100644 index 0000000000..dc944a6dbf --- /dev/null +++ b/topics/saml/java/MigrationFromOlderVersions.adoc @@ -0,0 +1,11 @@ +==== Migration from older versions + +===== Migrating to 1.9.0 + +====== SAML SP Client Adapter Changes + +Keycloak SAML SP Client Adapter now requires a specific endpoint, `/saml` to be registered with your IDP. +The SamlFilter must also be bound to /saml in addition to any other binding it has. +This had to be done because SAML POST binding would eat the request input stream and this would be really bad for clients that relied on it. + + diff --git a/topics/saml/java/assertion-api.adoc b/topics/saml/java/assertion-api.adoc new file mode 100644 index 0000000000..d2a32a5248 --- /dev/null +++ b/topics/saml/java/assertion-api.adoc @@ -0,0 +1,105 @@ + +==== Obtaining Assertion Attributes + +After a successful SAML login, your application code may want to obtain attribute values passed with the SAML assertion. `HttpServletRequest.getUserPrincipal` returns a Principal object that you can typecast into a Keycloak specific class called `org.keycloak.adapters.saml.SamlPrincipal`. +This object allows you to look at the raw assertion and also has convenience functions to look up attribute values. + + +[source,java] +---- +package org.keycloak.adapters.saml; + +public class SamlPrincipal implements Serializable, Principal { + /** + * Get full saml assertion + * + * @return + */ + public AssertionType getAssertion() { + ... + } + + /** + * Get SAML subject sent in assertion + * + * @return + */ + public String getSamlSubject() { + ... + } + + /** + * Subject nameID format + * + * @return + */ + public String getNameIDFormat() { + ... + } + + @Override + public String getName() { + ... + } + + /** + * Convenience function that gets Attribute value by attribute name + * + * @param name + * @return + */ + public List getAttributes(String name) { + ... + + } + + /** + * Convenience function that gets Attribute value by attribute friendly name + * + * @param friendlyName + * @return + */ + public List getFriendlyAttributes(String friendlyName) { + ... + } + + /** + * Convenience function that gets first value of an attribute by attribute name + * + * @param name + * @return + */ + public String getAttribute(String name) { + ... + } + + /** + * Convenience function that gets first value of an attribute by attribute name + * + * + * @param friendlyName + * @return + */ + public String getFriendlyAttribute(String friendlyName) { + ... + } + + /** + * Get set of all assertion attribute names + * + * @return + */ + public Set getAttributeNames() { + ... + } + + /** + * Get set of all assertion friendly attribute names + * + * @return + */ + public Set getFriendlyNames() { + ... + } +} +---- diff --git a/topics/saml/java/debugging.adoc b/topics/saml/java/debugging.adoc new file mode 100644 index 0000000000..5b567045f6 --- /dev/null +++ b/topics/saml/java/debugging.adoc @@ -0,0 +1,6 @@ + +==== Troubleshooting + +The best way to troubleshoot some problems is to turn on debugging for saml in both the client adapter and the keycloak server. +To do this turn on debugging int the `org.keycloak.saml` package to `debug` in your log4j or other logging framework. +Turning this on allows you to see the SAML requests and response documents being sent to and from the server. diff --git a/topics/saml/java/error_handling.adoc b/topics/saml/java/error_handling.adoc new file mode 100644 index 0000000000..82be3d8057 --- /dev/null +++ b/topics/saml/java/error_handling.adoc @@ -0,0 +1,41 @@ + +==== Error Handling + +Keycloak has some error handling facilities for servlet based client adapters. +When an error is encountered in authentication, keycloak will call `HttpServletResponse.sendError()`. +You can set up an error-page within your `web.xml` file to handle the error however you want. +Keycloak may throw 400, 401, 403, and 500 errors. + + +[source,xml] +---- + + 404 + /ErrorHandler + +---- + +Keycloak also sets an `HttpServletRequest` attribute that you can retrieve. +The attribute name is `org.keycloak.adapters.spi.AuthenticationError`. +Typecast this object to: `org.keycloak.adapters.saml.SamlAuthenticationError`. +This class can tell you exactly what happened. +If this attribute is not set, then the adapter was not responsible for the error code. + + +[source,xml] +---- +public class SamlAuthenticationError implements AuthenticationError { + public static enum Reason { + EXTRACTION_FAILURE, + INVALID_SIGNATURE, + ERROR_STATUS + } + + public Reason getReason() { + return reason; + } + public StatusResponseType getStatus() { + return status; + } +} +---- diff --git a/topics/saml/java/general-config.adoc b/topics/saml/java/general-config.adoc new file mode 100644 index 0000000000..a24b5a03b8 --- /dev/null +++ b/topics/saml/java/general-config.adoc @@ -0,0 +1,58 @@ +[[_saml-general-config]] + +==== General Adapter Config + +Each SAML adapter supported by Keycloak can be configured by a simple XML text file. +This is what one might look like: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +Some of these configuration switches may be adapter specific and some are common across all adapters. +For Java adapters you can use `${...}` enclosure as System property replacement. +For example `${jboss.server.config.dir}`. + + diff --git a/topics/saml/java/general-config/idp_element.adoc b/topics/saml/java/general-config/idp_element.adoc new file mode 100644 index 0000000000..51e5395310 --- /dev/null +++ b/topics/saml/java/general-config/idp_element.adoc @@ -0,0 +1,30 @@ + +===== IDP Element + +Everything in the IDP element describes the settings for the IDP the SP is communicating with. + +[source,xml] +---- + +... + +---- +entityID:: + This is the issuer ID of the IDP. _REQUIRED._. + +signaturesRequired:: + If set to true, the client adapter will sign every document it sends to the IDP. + Also, the client will expect that the IDP will be signing an documents sent to it. + This switch sets the default for all request and response types, but you will see later that you have some fine grain control over this. _OPTIONAL._ + +signatureAlgorithm:: + This is the signature algorithm that the IDP expects signed documents to use _OPTIONAL._. + The default value is RSA_SHA256, but you can also use RSA_SHA1, RSA_256, RSA_512, and DSA_SHA1. + +signatureCanonicalizationMethod:: + This is the signature canonicalization method that the IDP expects signed documents to use _OPTIONAL._. + The default value is `http://www.w3.org/2001/10/xml-exc-c14n#` and should be good for most IDPs. + diff --git a/topics/saml/java/general-config/idp_keys_subelement.adoc b/topics/saml/java/general-config/idp_keys_subelement.adoc new file mode 100644 index 0000000000..2102210147 --- /dev/null +++ b/topics/saml/java/general-config/idp_keys_subelement.adoc @@ -0,0 +1,6 @@ + +===== IDP Keys subelement + +The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP. +It is defined in the same way as the <<_sp_keys,SP's Key's element>>. +But again, you only have to define one certificate or public key reference. diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc new file mode 100644 index 0000000000..531be3a93f --- /dev/null +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -0,0 +1,46 @@ + +===== IDP SingleLogoutService sub element + +The `SingleLogoutService` sub element defines the logout SAML endpoint of the IDP. + +[source,xml] +---- + +---- + +signRequest:: + Should the client sign logout requests it makes to the IDP? _OPTIONAL._. + Defaults to whatever the IDP `signaturesRequired` element value is. + +signResponse:: + Should the client sign logout responses it sends to the IDP requests? _OPTIONAL._. + Defaults to whatever the IDP `signaturesRequired` element value is. + +validateRequestSignature:: + Should the client expect signed logout request documents from the IDP? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + +validateResponseSignature:: + Should the client expect signed logout response documents from the IDP? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + +requestBinding:: + This is the SAML binding type used for communicating SAML requests to the IDP _OPTIONAL._. + The default value is POST, but you can set it to REDIRECT as well. + +responseBinding:: + This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be POST or REDIRECT _OPTIONAL._. + The default value is POST, but you can set it to REDIRECT as well. + +postBindingUrl:: + This is the URL for the IDP's logout service when using the POST binding. _REQUIRED_ if using the POST binding at all. + +redirectBindingUrl:: + This is the URL for the IDP's logout service when using the REDIRECT binding. _REQUIRED_ if using the REDIRECT binding at all. + + diff --git a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc new file mode 100644 index 0000000000..0c80cdb957 --- /dev/null +++ b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc @@ -0,0 +1,33 @@ + +===== IDP SingleSignOnService sub element + +The `SignleSignOnService` sub element defines the login SAML endpoint of the IDP. + +[source,xml] +---- + + +---- +signRequest:: + Should the client sign authn requests? _OPTIONAL._. + Defaults to whatever the IDP `signaturesRequired` element value is. + +validateResponseSignature:: + Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + +requestBinding:: + This is the SAML binding type used for communicating with the IDP _OPTIONAL._. + The default value is POST, but you can set it to REDIRECT as well. + +responseBinding:: + SAML allows the client to request what binding type it wants authn responses to use. + The values of this can be POST or REDIRECT _OPTIONAL._. + The default is that the client will not request a specific binding type for responses. + +bindingUrl:: + This is the URL for the ID login service that the client will send requests to. _REQUIRED._. + + diff --git a/topics/saml/java/general-config/roleidentifiers_element.adoc b/topics/saml/java/general-config/roleidentifiers_element.adoc new file mode 100644 index 0000000000..369b4011be --- /dev/null +++ b/topics/saml/java/general-config/roleidentifiers_element.adoc @@ -0,0 +1,20 @@ + +===== RoleIdentifiers element + +[source,xml] +---- + + + + + + +---- + +This element is optional. +It defines which SAML attribute values in the assertion should be mapped to a Java EE role. +By default `Role` attribute values are converted to Java EE roles. +Some IDPs send roles via a `member` or `memberOf` attribute assertion. +You define one or more `Attribute` elements to specify which SAML attributes must be converted into roles. + + diff --git a/topics/saml/java/general-config/sp-keys.adoc b/topics/saml/java/general-config/sp-keys.adoc new file mode 100644 index 0000000000..3c73007b88 --- /dev/null +++ b/topics/saml/java/general-config/sp-keys.adoc @@ -0,0 +1,29 @@ +[[_saml-sp-keys]] + +===== SP Keys and Key elements + +If the IDP requires that the SP sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. +For client signed documents you must define both the private and public key or certificate that will be used to sign documents. +For encryption, you only have to define the private key that will be used to decrypt. + +There are two ways to describe your keys. +Either they are stored within a Java KeyStore or you can cut and paste the keys directly within `keycloak-saml.xml` in the PEM format. + +[source,xml] +---- + + + + + + + + + +---- + +The `Key` element has two optional attributes `signing` and `encryption`. +When set to true these tell the adapter what the key will be used for. +If both attributes are set to true, then the key will be used for both signing documents and decrypting encrypted assertions. +You must set at least one of these attributes to true. + diff --git a/topics/saml/java/general-config/sp-keys/key_pems.adoc b/topics/saml/java/general-config/sp-keys/key_pems.adoc new file mode 100644 index 0000000000..a156d364c9 --- /dev/null +++ b/topics/saml/java/general-config/sp-keys/key_pems.adoc @@ -0,0 +1,6 @@ + +====== Key PEMS + +Within the `Key` element you alternatively declare your keys and certificates directly using the sub elements `PrivateKeyPem`, `PublicKeyPem`, and `CertificatePem`. +The values contained in these elements must conform to the PEM key format. +You usually use this option if you are generating keys using `openssl` diff --git a/topics/saml/java/general-config/sp-keys/keystore_element.adoc b/topics/saml/java/general-config/sp-keys/keystore_element.adoc new file mode 100644 index 0000000000..d43c768e89 --- /dev/null +++ b/topics/saml/java/general-config/sp-keys/keystore_element.adoc @@ -0,0 +1,19 @@ +[[_saml-keystore]] + +====== KeyStore element + +file:: + File path to the key store. _OPTIONAL._ The file or resource attribute must be set. + +resource:: + WAR resource path to the KeyStore. + This is a path used in method call to ServletContext.getResourceAsStream(). _OPTIONAL._ The file or resource attribute must be set. + +password:: + The password of the KeyStore _REQUIRED._ + +You can and must also specify references to your private keys and certificates within the Java KeyStore. +The `PrivateKey` and `Certificate` elements do this. +The `alias` attribute defines the alias within the KeyStore for the key. +For `PrivateKey`, a password is required to access this key specify that value in the `password` attribute. + diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc new file mode 100644 index 0000000000..0ee76710d6 --- /dev/null +++ b/topics/saml/java/general-config/sp_element.adoc @@ -0,0 +1,49 @@ + +===== SP Element + +Here is the explanation of the SP element attributes + +[source,xml] +---- + + +... + +---- +entityID:: + This is the identifier for this client. + The IDP needs this value to determine who the client is that is communicating with it. _REQUIRED._ + +sslPolicy:: + This is the SSL policy the adapter will enforce. + Valid values are: ALL, EXTERNAL, and NONE. + For ALL, all requests must come in via HTTPS. + For EXTERNAL, only non-private IP addresses must come over the wire via HTTPS. + For NONE, no requests are required to come over via HTTPS. + This is _OPTIONAL._ and defaults to EXTERNAL. + +nameIDPolicyFormat:: + SAML clients can request a specific NameID Subject format. + Fill in this value if you want a specific format. + It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` _OPTIONAL._. + By default, no special format is requested. + +forceAuthentication:: + SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. + Set this to `true` if you want this. _OPTIONAL._. + Set to `false` by default. + +isPassive:: + SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. + Set this to `true` if you want this. + Do not use together with `forceAuthentication` as they are opposite. _OPTIONAL._. + Set to `false` by default. + +turnOffChangeSessionIdOnLogin:: + The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off This is _OPTIONAL_. + The default value is _false_. + diff --git a/topics/saml/java/general-config/sp_principalname_mapping_element.adoc b/topics/saml/java/general-config/sp_principalname_mapping_element.adoc new file mode 100644 index 0000000000..8c703a1f5e --- /dev/null +++ b/topics/saml/java/general-config/sp_principalname_mapping_element.adoc @@ -0,0 +1,11 @@ + +===== SP PrincipalNameMapping element + +This element is optional. +When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method. +The `policy` attribute defines the policy used to populate this value. +The values are `FROM_NAME_ID`. +This policy just grabs whatever the SAML subject value is. +The other is `FROM_ATTRIBUTE`. +This will pull the value of Principal.getName() from one of the attributes in the SAML assertion received from the server. +The default value is `FROM_NAME_ID`. diff --git a/topics/saml/java/idp-registration.adoc b/topics/saml/java/idp-registration.adoc new file mode 100644 index 0000000000..c1cb78d025 --- /dev/null +++ b/topics/saml/java/idp-registration.adoc @@ -0,0 +1,5 @@ + +==== Registering with an IDP + +For each servlet based adapter, the endpoint you register for the assert consumer service url and and single logout service +must be the base url of your servlet application with `/saml` appended to it i.e. https://example.com/contextPath/saml diff --git a/topics/saml/java/java-adapters.adoc b/topics/saml/java/java-adapters.adoc new file mode 100644 index 0000000000..db4148cdcb --- /dev/null +++ b/topics/saml/java/java-adapters.adoc @@ -0,0 +1,4 @@ + +=== Java Adapters + +{{book.project.name}} comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform. diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/jboss-adapter.adoc new file mode 100644 index 0000000000..050e5822d3 --- /dev/null +++ b/topics/saml/java/jboss-adapter.adoc @@ -0,0 +1,9 @@ +[[_saml-jboss-adapter]] + +==== JBoss/Wildfly Adapter + +To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and configure the Keycloak SAML Adapter Subsystem. +You then provide a keycloak config, `/WEB-INF/keycloak-saml.xml` file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml. +Both methods are described in this section. + + diff --git a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc new file mode 100644 index 0000000000..330525c9ed --- /dev/null +++ b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc @@ -0,0 +1,111 @@ + +[[_saml-jboss-adapter-installation]] +===== Adapter Installation + +SAML Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +Install on Wildfly 9 or 10: + +[source] +---- + +$ cd $WILDFLY_HOME +$ unzip keycloak-saml-wildfly-adapter-dist.zip +---- + +Install on JBoss EAP 6.x: + +[source] +---- + +$ cd $JBOSS_HOME +$ unzip keycloak-saml-eap6-adapter-dist.zip +---- + +This zip file creates new JBoss Modules specific to the Wildfly Keycloak SAML Adapter within your Wildfly distro. + +After adding the Keycloak modules, you must then enable the Keycloak SAML Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. + +There is a CLI script that will help you modify your server configuration. +Start the server and run the script from the server's bin directory: + +[source] +---- + +$ cd $JBOSS_HOME/bin +$ jboss-cli.sh -c --file=adapter-install-saml.cli +---- +The script will add the extension, subsystem, and optional security-domain as described below. + +[source,xml] +---- + + + + + ... + + + + + ... + +---- + +The keycloak security domain should be used with EJBs and other components when you need the security context created in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. +Otherwise this configuration is optional. + +[source,xml] +---- + + + + +... + + + + + + +---- + +For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: + +[source,xml] +---- + +import org.jboss.ejb3.annotation.SecurityDomain; +import org.jboss.resteasy.annotations.cache.NoCache; + +import javax.annotation.security.RolesAllowed; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import java.util.ArrayList; +import java.util.List; + +@Path("customers") +@Stateless +@SecurityDomain("keycloak") +public class CustomerService { + + @EJB + CustomerDB db; + + @GET + @Produces("application/json") + @NoCache + @RolesAllowed("db_user") + public List getCustomers() { + return db.getCustomers(); + } +} +---- + +We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain annotation when you want to propagate a keycloak security context to the EJB tier. + diff --git a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc new file mode 100644 index 0000000000..bd307dc22d --- /dev/null +++ b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc @@ -0,0 +1,62 @@ + +===== Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. + +Next you must set the `auth-method` to `KEYCLOAK-SAML` in `web.xml`. +You also have to use standard servlet security to specify role-base constraints on your URLs. +Here's an example pulled from one of the examples that comes distributed with Keycloak. + +[source,xml] +---- + + + + customer-portal + + + + Admins + /admin/* + + + admin + + + CONFIDENTIAL + + + + + Customers + /customers/* + + + user + + + CONFIDENTIAL + + + + + KEYCLOAK-SAML + this is ignored currently + + + + admin + + + user + + +---- + + diff --git a/topics/saml/java/jboss-adapter/securing_wars.adoc b/topics/saml/java/jboss-adapter/securing_wars.adoc new file mode 100644 index 0000000000..a0d5b4360c --- /dev/null +++ b/topics/saml/java/jboss-adapter/securing_wars.adoc @@ -0,0 +1,82 @@ + +===== Securing WARs via Keycloak SAML Subsystem + +You do not have to crack open a WAR to secure it with Keycloak. +Alternatively, you can externally secure it via the Keycloak SAML Adapter Subsystem. +While you don't have to specify KEYCLOAK-SAML as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. +You do not, however, have to create a `WEB-INF/keycloak-saml.xml` file. +This metadata is instead defined within XML in your server's `domain.xml` or `standalone.xml` subsystem configuration section. + +[source,xml] +---- + + + + + + + + + + ... + + + + +---- + +The `secure-deployment` `name` attribute identifies the WAR you want to secure. +Its value is the `module-name` defined in `web.xml` with `.war` appended. +The rest of the configuration uses the same XML syntax as `keycloak-saml.xml` configuration defined in <<_adapter_config,general adapter configuration>>. + +An example configuration: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- diff --git a/topics/saml/java/jetty-adapter.adoc b/topics/saml/java/jetty-adapter.adoc new file mode 100644 index 0000000000..6a85ed2798 --- /dev/null +++ b/topics/saml/java/jetty-adapter.adoc @@ -0,0 +1,6 @@ + +==== Jetty SAML Adapters + +To be able to secure WAR apps deployed on Jetty you must install the {{book.project.name}} Jetty 9.x or 8.x SAML adapter into your Jetty installation. +You then have to provide some extra configuration in each WAR you deploy to Jetty. +Let's go over these steps. diff --git a/topics/saml/java/jetty-adapter/jetty8-installation.adoc b/topics/saml/java/jetty-adapter/jetty8-installation.adoc new file mode 100644 index 0000000000..1533d9aa2e --- /dev/null +++ b/topics/saml/java/jetty-adapter/jetty8-installation.adoc @@ -0,0 +1,32 @@ + +===== Jetty 8 Adapter Installation + +Keycloak has a separate SAML adapter for Jetty 8.1.x that you will have to install into your Jetty installation. +You then have to provide some extra configuration in each WAR you deploy to Jetty. +Let's go over these steps. + +Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the Jetty 8.1.x distro into Jetty 8.1.x's root directory. +Including adapter's jars within your WEB-INF/lib directory will not work! + +[source] +---- +$ cd $JETTY_HOME +$ unzip keycloak-saml-jetty81-adapter-dist.zip +---- +Next, you will have to enable the keycloak option. +Edit start.ini and add keycloak to the options + +[source] +---- +#=========================================================== +# Start classpath OPTIONS. +# These control what classes are on the classpath +# for a full listing do +# java -jar start.jar --list-options +#----------------------------------------------------------- +OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations,keycloak +---- + diff --git a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc new file mode 100644 index 0000000000..073cfd8b97 --- /dev/null +++ b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc @@ -0,0 +1,5 @@ + +===== Jetty 8 Required Per WAR Configuration + +Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. +See <<_jetty9_per_war,Required Per WAR Configuration>> diff --git a/topics/saml/java/jetty-adapter/jetty9_installation.adoc b/topics/saml/java/jetty-adapter/jetty9_installation.adoc new file mode 100644 index 0000000000..2554091131 --- /dev/null +++ b/topics/saml/java/jetty-adapter/jetty9_installation.adoc @@ -0,0 +1,27 @@ +[[_jetty9_adapter_installation]] + +===== Jetty 9 Adapter Installation + +Keycloak has a separate SAML adapter for Jetty 9.x. +You then have to provide some extra configuration in each WAR you deploy to Jetty. +Let's go over these steps. + +Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the Jetty 9.x distro into Jetty 9.x's root directory. +Including adapter's jars within your WEB-INF/lib directory will not work! + +[source] +---- +$ cd $JETTY_HOME +$ unzip keycloak-saml-jetty92-adapter-dist.zip +---- +Next, you will have to enable the keycloak module for your jetty.base. + +[source] +---- +$ cd your-base +$ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak +---- + diff --git a/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc b/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc new file mode 100644 index 0000000000..415373de79 --- /dev/null +++ b/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc @@ -0,0 +1,64 @@ + +[[_saml-jetty9-per-war]] +===== Jetty 9 Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `WEB-INF/jetty-web.xml` file in your WAR package. +This is a Jetty specific config file and you must define a Keycloak specific authenticator within it. + +[source,xml] +---- + + + + + + + + + + +---- + +Next you must create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. + +Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. +Here's an example: + +[source,xml] +---- + + + customer-portal + + + + Customers + /* + + + user + + + CONFIDENTIAL + + + + + BASIC + this is ignored currently + + + + admin + + + user + + +---- diff --git a/topics/saml/java/logout.adoc b/topics/saml/java/logout.adoc new file mode 100644 index 0000000000..e30c08282a --- /dev/null +++ b/topics/saml/java/logout.adoc @@ -0,0 +1,6 @@ +==== Logout + +There are multiple ways you can logout from a web application. +For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point +the browser at any url of your web application that has a security constraing and pass in a query parameter GLO, i.e. `http://myapp?GLO=true`. +This will log you out if you have an SSO session with your browser. diff --git a/topics/saml/java/saml_adapter_overview.adoc b/topics/saml/java/saml_adapter_overview.adoc new file mode 100644 index 0000000000..9e85b2abe4 --- /dev/null +++ b/topics/saml/java/saml_adapter_overview.adoc @@ -0,0 +1,6 @@ += Overview + +This document describes the Keycloak SAML client adapter and how it can be configured for a variety of platforms. +The Keycloak SAML client adapter is a standalone component that provides generic SAML 2.0 support for your web applications. +There are no Keycloak server extensions built into it. +As long as the IDP you are talking to supports standard SAML, the Keycloak SAML client adapter should be able to integrate with it. diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/topics/saml/java/servlet-filter-adapter.adoc new file mode 100644 index 0000000000..434d3b1480 --- /dev/null +++ b/topics/saml/java/servlet-filter-adapter.adoc @@ -0,0 +1,53 @@ + +==== Java Servlet Filter Adapter + +If you want to use SAML with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. +This adapter works a little differently than the other adapters. +You do not define security constraints in web.xml. +Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure. + +WARNING: Backchannel logout works a bit differently than the standard adapters. +Instead of invalidating the http session it instead marks the session id as logged out. +There's just no way of arbitrarily invalidating an http session based on a session id. + +WARNING: Backchannel logout does not currently work when you have a clustered application that uses the SAML filter. + +[source,xml] +---- + + + customer-portal + + + Keycloak Filter + org.keycloak.adapters.saml.servlet.SamlFilter + + + Keycloak Filter + /* + + +---- + +The Keycloak filter has the same configuration parameters available as the other adapters except you must define them as filter init params instead of context params. + +You can define multiple filter mappings if you have various different secure and unsecure url patterns. + +WARNING: You must have a filter mapping that covers `/saml`. +This mapping covers all server callbacks. + +When registering SPs with an IDP, you must register `http[s]://hostname/{context-root}/saml` as your Assert Consumer Service URL and Single Logout Service URL. + +To use this filter, include this maven artifact in your WAR poms + +[source,xml] +---- + + org.keycloak + keycloak-saml-servlet-filter-adapter + &project.version; + +---- diff --git a/topics/saml/java/tomcat-adapter.adoc b/topics/saml/java/tomcat-adapter.adoc new file mode 100644 index 0000000000..a6540451fe --- /dev/null +++ b/topics/saml/java/tomcat-adapter.adoc @@ -0,0 +1,8 @@ +[[_saml-tomcat-adapter]] + +==== Tomcat SAML adapters + +To be able to secure WAR apps deployed on Tomcat 6, 7 and 8 you must install the Keycloak Tomcat 6, 7 or 8 SAML adapter into your Tomcat installation. +You then have to provide some extra configuration in each WAR you deploy to Tomcat. +Let's go over these steps. + diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc b/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc new file mode 100644 index 0000000000..635a1ed19b --- /dev/null +++ b/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc @@ -0,0 +1,22 @@ + +[[_saml-tomcat-adapter-installation]] +===== Adapter Installation + +Adapters are no longer included with the appliance or war distribution. +Each adapter is a separate download on the Keycloak download site. +They are also available as a maven artifact. + +You must unzip the adapter distro into Tomcat's `lib/` directory. +Including adapter's jars within your WEB-INF/lib directory will not work! The Keycloak SAML adapter is implemented as a Valve and valve code must reside in Tomcat's main lib/ directory. + + +[source] +---- +$ cd $TOMCAT_HOME/lib +$ unzip keycloak-saml-tomcat6-adapter-dist.zip + or +$ unzip keycloak-saml-tomcat7-adapter-dist.zip + or +$ unzip keycloak-saml-tomcat8-adapter-dist.zip +---- + diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc new file mode 100644 index 0000000000..8f867af000 --- /dev/null +++ b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc @@ -0,0 +1,53 @@ + +===== Required Per WAR Configuration + +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. + +The first thing you must do is create a `META-INF/context.xml` file in your WAR package. +This is a Tomcat specific config file and you must define a Keycloak specific Valve. + +[source,xml] +---- + + + +---- + +Next you must create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>>section. + +Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. +Here's an example: + +[source,xml] +---- + + + customer-portal + + + + Customers + /* + + + user + + + + + BASIC + this is ignored currently + + + + admin + + + user + + +---- diff --git a/topics/saml/saml-overview.adoc b/topics/saml/saml-overview.adoc index 98e42693b2..11d65a9f89 100644 --- a/topics/saml/saml-overview.adoc +++ b/topics/saml/saml-overview.adoc @@ -1 +1,4 @@ -== SAML \ No newline at end of file +== SAML + +This section describes how you can secure applications and services with SAML using either {{book.project.name}} client adapters or generic +SAML provider libraries. \ No newline at end of file From c4a177dc0692ac1785a91a5bd7bd185819c8a329 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 12:10:13 -0400 Subject: [PATCH 013/194] initial saml --- SUMMARY.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index b5aec1b67e..a103272990 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -65,6 +65,6 @@ ... link:topics/saml/java/error_handling.adoc[Error Handling] ... link:topics/saml/java/debugging.adoc[Troubleshooting] {% if book.community %} - ... link:topics/saml/java/saml_adapter_MigrationFromOlderVersions.adoc[Migration from older versions] + ... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] {% endif %} . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file From be1924c0087266ef1744026d09d8abfacd13452f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 12:11:24 -0400 Subject: [PATCH 014/194] initial saml --- SUMMARY.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index a103272990..dadd2717c2 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -53,10 +53,10 @@ .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Required Per WAR Configuration] ... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] - .... link:topics/saml/java/jetty9_installation.adoc[Jetty 9 Adapter Installation] - .... link:topics/saml/java/jetty9_per_war_config.adoc[Jetty 9 Required Per WAR Configuration] - .... link:topics/saml/java/jetty8-installation.adoc[Jetty 8 Adapter Installation] - .... link:topics/saml/java/jetty8-per_war_config.adoc[Jetty 8 Required Per WAR Configuration] + .... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] + .... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Required Per WAR Configuration] + .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] + .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Required Per WAR Configuration] {% endif %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] From 8cd0ce78d1b131a4912483e3da2b3654a973a016 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 12:13:25 -0400 Subject: [PATCH 015/194] initial saml --- SUMMARY.adoc | 1 - topics/preface.adoc | 20 -------------------- 2 files changed, 21 deletions(-) delete mode 100755 topics/preface.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index dadd2717c2..9c0b376e62 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -1,6 +1,5 @@ = {{book.title}} - . link:topics/preface.adoc[Preface] . link:topics/overview/overview.adoc[Overview] .. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] .. link:topics/overview/supported-platforms.adoc[Supported Platforms] diff --git a/topics/preface.adoc b/topics/preface.adoc deleted file mode 100755 index 860e9fd39f..0000000000 --- a/topics/preface.adoc +++ /dev/null @@ -1,20 +0,0 @@ -== Preface - -In some of the example listings, what is meant to be displayed on one line does not fit inside the available page width.These lines have been broken up. A '\' at the end of a line means that a break has been introduced to fit in the page, with the following lines indented. -So: - -[source] ----- -Let's pretend to have an extremely \ -long line that \ -does not fit -This one is short ----- -Is really: - -[source] ----- -Let's pretend to have an extremely long line that does not fit -This one is short ----- - From 0775ca0658aaaf9112807ea0f41dea237bccc665 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Jun 2016 18:49:55 +0200 Subject: [PATCH 016/194] Fix headings in clustering chapter --- topics/oidc/java/application-clustering.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index fbdecdaac7..b2d0b0a834 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -1,5 +1,5 @@ [[_applicationclustering]] -= Application Clustering +=== Application Clustering This chapter is focused on clustering support for your own AS7, EAP6 or Wildfly applications, which are secured by Keycloak. We support various deployment scenarios according if your application is: @@ -13,7 +13,7 @@ The situation is a bit tricky as application communicates with Keycloak directly NOTE: To enable distributable (replicated) HTTP Sessions in your application, you may need to do some additional steps. Usually you need to put tag into `WEB-INF/web.xml` file of your application and possibly do some additional steps to configure underlying cluster cache (In case of Wildfly, the implementation of cluster cache is based on Infinispan). These steps are server specific, so consult documentation of your application server for more details. -== Stateless token store +==== Stateless token store By default, the servlet web application secured by Keycloak uses HTTP session to store information about authenticated user account. This means that this info could be replicated across cluster and your application will safely survive failover of some cluster node. @@ -42,7 +42,7 @@ It works without issues if you init servlet logout (HttpServletRequest.logout) f But back-channel logout initialized from different application can't be propagated by Keycloak to this application with cookie store. Hence it's recommended to use very short value of access token timeout (1 minute for example). -== Relative URI optimization +==== Relative URI optimization In many deployment scenarios will be Keycloak and secured applications deployed on same cluster hosts. For this case Keycloak already provides option to use relative URI as value of option _auth-server-url_ in `WEB-INF/keycloak.json` . In this case, the URI of Keycloak server is resolved from the URI of current request. @@ -63,7 +63,7 @@ This would mean that browser requests (like redirecting to Keycloak login screen Note that additionally to network optimization, you may not need "https" in this case as application and keycloak are communicating directly within same cluster host. -== Admin URL configuration +==== Admin URL configuration Admin URL for particular application can be configured in Keycloak admin console. It's used by Keycloak server to send backend requests to application for various tasks, like logout users or push revocation policies. @@ -88,7 +88,7 @@ http://${application.session.host}:8080/myapp:: Keycloak will track hosts where is particular HTTP Session served and it will send session invalidation message to proper cluster node. [[_registration_app_nodes]] -== Registration of application nodes to Keycloak +==== Registration of application nodes to Keycloak Previous section describes how can Keycloak send logout request to proper application node. However in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. @@ -116,7 +116,7 @@ which means that registration is sent at startup (accurately when 1st request is In Keycloak admin console you can specify the maximum node re-registration timeout (makes sense to have it bigger than _register-node-period_ from adapter configuration for particular application). Also you can manually add and remove cluster nodes in admin console, which is useful if you don't want to rely on adapter's automatic registration or if you want to remove stale application nodes, which weren't unregistered (for example due to forced shutdown). [[_refresh_token_each_req]] -== Refresh token in each request +==== Refresh token in each request By default, application adapter tries to refresh access token when it's expired (period can be specified as <<_token_timeouts,Access Token Lifespan>>) . However if you don't want to rely on the fact, that Keycloak is able to successfully propagate admin events like logout to your application nodes, then you have possibility to configure adapter to refresh access token in each HTTP request. From 5c99a41b66b0a3a61d9d56de089ddc020e4f3e2a Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 16:50:43 -0400 Subject: [PATCH 017/194] saml general config --- topics/saml/java/general-config.adoc | 6 ++-- .../saml/java/general-config/idp_element.adoc | 24 ++++++++------- .../general-config/idp_keys_subelement.adoc | 18 ++++++++++-- .../idp_singlelogoutservice_subelement.adoc | 23 ++++++++------- .../idp_singlesignonservice_subelement.adoc | 20 ++++++++----- .../roleidentifiers_element.adoc | 7 +++-- topics/saml/java/general-config/sp-keys.adoc | 9 ++---- .../java/general-config/sp-keys/key_pems.adoc | 19 ++++++++++-- .../sp-keys/keystore_element.adoc | 28 ++++++++++++++---- .../saml/java/general-config/sp_element.adoc | 26 +++++++++-------- .../sp_principalname_mapping_element.adoc | 29 +++++++++++++++---- 11 files changed, 142 insertions(+), 67 deletions(-) diff --git a/topics/saml/java/general-config.adoc b/topics/saml/java/general-config.adoc index a24b5a03b8..8f7a995d76 100644 --- a/topics/saml/java/general-config.adoc +++ b/topics/saml/java/general-config.adoc @@ -2,7 +2,7 @@ ==== General Adapter Config -Each SAML adapter supported by Keycloak can be configured by a simple XML text file. +Each SAML client adapter supported by {{book.project.name}} can be configured by a simple XML text file. This is what one might look like: [source,xml] @@ -52,7 +52,7 @@ This is what one might look like: ---- Some of these configuration switches may be adapter specific and some are common across all adapters. -For Java adapters you can use `${...}` enclosure as System property replacement. -For example `${jboss.server.config.dir}`. +For Java adapters you can use `$\{...}` enclosure as System property replacement. +For example `$\{jboss.server.config.dir}`. diff --git a/topics/saml/java/general-config/idp_element.adoc b/topics/saml/java/general-config/idp_element.adoc index 51e5395310..2bbf03664c 100644 --- a/topics/saml/java/general-config/idp_element.adoc +++ b/topics/saml/java/general-config/idp_element.adoc @@ -1,7 +1,7 @@ ===== IDP Element -Everything in the IDP element describes the settings for the IDP the SP is communicating with. +Everything in the IDP element describes the settings for the identity provider (authentication server) the SP is communicating with. [source,xml] ---- @@ -12,19 +12,23 @@ Everything in the IDP element describes the settings for the IDP the SP is commu ... ---- + +Here are the attribute config options you can specify within the `IDP` element declaration. + entityID:: - This is the issuer ID of the IDP. _REQUIRED._. + This is the issuer ID of the IDP. This setting is _REQUIRED._. signaturesRequired:: - If set to true, the client adapter will sign every document it sends to the IDP. - Also, the client will expect that the IDP will be signing an documents sent to it. - This switch sets the default for all request and response types, but you will see later that you have some fine grain control over this. _OPTIONAL._ - + If set to `true`, the client adapter will sign every document it sends to the IDP. + Also, the client will expect that the IDP will be signing any documents sent to it. + This switch sets the default for all request and response types, but you will see later that you have some fine grain control over this. + This setting is _OPTIONAL_ and will default to `false`. signatureAlgorithm:: - This is the signature algorithm that the IDP expects signed documents to use _OPTIONAL._. - The default value is RSA_SHA256, but you can also use RSA_SHA1, RSA_256, RSA_512, and DSA_SHA1. - + This is the signature algorithm that the IDP expects signed documents to use. + Allowed values are: `RSA_SHA1`, `RSA_SHA256`, `RSA_SHA512`, and `DSA_SHA1`. + This setting is _OPTIONAL_ + and defaults to `RSA_SHA256`. signatureCanonicalizationMethod:: - This is the signature canonicalization method that the IDP expects signed documents to use _OPTIONAL._. + This is the signature canonicalization method that the IDP expects signed documents to use. This setting is _OPTIONAL._. The default value is `http://www.w3.org/2001/10/xml-exc-c14n#` and should be good for most IDPs. diff --git a/topics/saml/java/general-config/idp_keys_subelement.adoc b/topics/saml/java/general-config/idp_keys_subelement.adoc index 2102210147..c4283037c7 100644 --- a/topics/saml/java/general-config/idp_keys_subelement.adoc +++ b/topics/saml/java/general-config/idp_keys_subelement.adoc @@ -2,5 +2,19 @@ ===== IDP Keys subelement The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP. -It is defined in the same way as the <<_sp_keys,SP's Key's element>>. -But again, you only have to define one certificate or public key reference. +It is defined in the same way as the <>. +But again, you only have to define one certificate or public key reference. + +[source,xml] +---- + + ... + + + + + + + + +---- diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc index 531be3a93f..90d983cca1 100644 --- a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -1,7 +1,8 @@ ===== IDP SingleLogoutService sub element -The `SingleLogoutService` sub element defines the logout SAML endpoint of the IDP. +The `SingleLogoutService` sub element defines the logout SAML endpoint of the IDP. The client adapter will send requests +to the IDP formatted via the settings within this element when it wants to logout. [source,xml] ---- @@ -16,31 +17,31 @@ The `SingleLogoutService` sub element defines the logout SAML endpoint of the ID ---- signRequest:: - Should the client sign logout requests it makes to the IDP? _OPTIONAL._. + Should the client sign logout requests it makes to the IDP? This setting _OPTIONAL._. Defaults to whatever the IDP `signaturesRequired` element value is. signResponse:: - Should the client sign logout responses it sends to the IDP requests? _OPTIONAL._. + Should the client sign logout responses it sends to the IDP requests? This setting _OPTIONAL._. Defaults to whatever the IDP `signaturesRequired` element value is. validateRequestSignature:: - Should the client expect signed logout request documents from the IDP? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + Should the client expect signed logout request documents from the IDP? This setting is _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. validateResponseSignature:: - Should the client expect signed logout response documents from the IDP? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + Should the client expect signed logout response documents from the IDP? This setting is _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating SAML requests to the IDP _OPTIONAL._. - The default value is POST, but you can set it to REDIRECT as well. + This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL._. + The default value is `POST`, but you can set it to REDIRECT as well. responseBinding:: - This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be POST or REDIRECT _OPTIONAL._. - The default value is POST, but you can set it to REDIRECT as well. + This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL._. + The default value is `POST`, but you can set it to `REDIRECT` as well. postBindingUrl:: - This is the URL for the IDP's logout service when using the POST binding. _REQUIRED_ if using the POST binding at all. + This is the URL for the IDP's logout service when using the POST binding. This setting is _REQUIRED_ if using the `POST` binding. redirectBindingUrl:: - This is the URL for the IDP's logout service when using the REDIRECT binding. _REQUIRED_ if using the REDIRECT binding at all. + This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is _REQUIRED_ if using the REDIRECT binding. diff --git a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc index 0c80cdb957..c172bd3996 100644 --- a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc @@ -1,7 +1,9 @@ ===== IDP SingleSignOnService sub element -The `SignleSignOnService` sub element defines the login SAML endpoint of the IDP. +The `SingleSignOnService` sub element defines the login SAML endpoint of the IDP. +The client adapter will send requests +to the IDP formatted via the settings within this element when it wants to login. [source,xml] ---- @@ -11,23 +13,27 @@ The `SignleSignOnService` sub element defines the login SAML endpoint of the IDP requestBinding="post" bindingUrl="url"/> ---- + +Here are the config attributes you can define on this element: + signRequest:: - Should the client sign authn requests? _OPTIONAL._. + Should the client sign authn requests? This setting is _OPTIONAL._. Defaults to whatever the IDP `signaturesRequired` element value is. validateResponseSignature:: - Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? + This setting _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating with the IDP _OPTIONAL._. - The default value is POST, but you can set it to REDIRECT as well. + This is the SAML binding type used for communicating with the IDP. This setting is _OPTIONAL._. + The default value is `POST`, but you can set it to `REDIRECT` as well. responseBinding:: SAML allows the client to request what binding type it wants authn responses to use. - The values of this can be POST or REDIRECT _OPTIONAL._. + The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL._. The default is that the client will not request a specific binding type for responses. bindingUrl:: - This is the URL for the ID login service that the client will send requests to. _REQUIRED._. + This is the URL for the IDP login service that the client will send requests to. This setting is _REQUIRED._. diff --git a/topics/saml/java/general-config/roleidentifiers_element.adoc b/topics/saml/java/general-config/roleidentifiers_element.adoc index 369b4011be..ff0a3313ca 100644 --- a/topics/saml/java/general-config/roleidentifiers_element.adoc +++ b/topics/saml/java/general-config/roleidentifiers_element.adoc @@ -1,6 +1,9 @@ ===== RoleIdentifiers element +The `RoleIdentifiers` element defines what SAML attributes within the assertion received from the user should be used +as role identifiers within the Java EE Security Context for the user. + [source,xml] ---- @@ -11,10 +14,8 @@ ---- -This element is optional. -It defines which SAML attribute values in the assertion should be mapped to a Java EE role. By default `Role` attribute values are converted to Java EE roles. Some IDPs send roles via a `member` or `memberOf` attribute assertion. -You define one or more `Attribute` elements to specify which SAML attributes must be converted into roles. +You can define one or more `Attribute` elements to specify which SAML attributes must be converted into roles. diff --git a/topics/saml/java/general-config/sp-keys.adoc b/topics/saml/java/general-config/sp-keys.adoc index 3c73007b88..d76a1f700c 100644 --- a/topics/saml/java/general-config/sp-keys.adoc +++ b/topics/saml/java/general-config/sp-keys.adoc @@ -2,22 +2,19 @@ ===== SP Keys and Key elements -If the IDP requires that the SP sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. +If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt. There are two ways to describe your keys. -Either they are stored within a Java KeyStore or you can cut and paste the keys directly within `keycloak-saml.xml` in the PEM format. +They can be stored within a Java KeyStore or you can or you can cut and paste the keys directly within `keycloak-saml.xml` in the PEM format. [source,xml] ---- - - - - + ... ---- diff --git a/topics/saml/java/general-config/sp-keys/key_pems.adoc b/topics/saml/java/general-config/sp-keys/key_pems.adoc index a156d364c9..59072e58fd 100644 --- a/topics/saml/java/general-config/sp-keys/key_pems.adoc +++ b/topics/saml/java/general-config/sp-keys/key_pems.adoc @@ -1,6 +1,21 @@ ====== Key PEMS -Within the `Key` element you alternatively declare your keys and certificates directly using the sub elements `PrivateKeyPem`, `PublicKeyPem`, and `CertificatePem`. +Within the `Key` element you declare your keys and certificates directly using the sub elements +`PrivateKeyPem`, `PublicKeyPem`, and `CertificatePem`. The values contained in these elements must conform to the PEM key format. -You usually use this option if you are generating keys using `openssl` +You usually use this option if you are generating keys using `openssl` or similar command line tool. + +[source,xml] +---- + + + + + + + + +---- + +Here are the XML config attributes that are defined with the `KeyStore` element. + file:: File path to the key store. _OPTIONAL._ The file or resource attribute must be set. @@ -12,8 +30,8 @@ resource:: password:: The password of the KeyStore _REQUIRED._ -You can and must also specify references to your private keys and certificates within the Java KeyStore. -The `PrivateKey` and `Certificate` elements do this. -The `alias` attribute defines the alias within the KeyStore for the key. -For `PrivateKey`, a password is required to access this key specify that value in the `password` attribute. - +If you are defining keys that the SP will use to sign document, you must also specify references to your private keys +and certificates within the Java KeyStore. +The `PrivateKey` and `Certificate` elements in the above example define an `alias` that points to the key or cert +within the keystore. Keystores require an additional password to access private keys. +In the `PrivateKey` element you must define this password within a `password` attribute. diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 0ee76710d6..8edc55405c 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -16,34 +16,36 @@ Here is the explanation of the SP element attributes ---- entityID:: This is the identifier for this client. - The IDP needs this value to determine who the client is that is communicating with it. _REQUIRED._ + The IDP needs this value to determine who the client is that is communicating with it. This setting _REQUIRED._ sslPolicy:: This is the SSL policy the adapter will enforce. - Valid values are: ALL, EXTERNAL, and NONE. - For ALL, all requests must come in via HTTPS. - For EXTERNAL, only non-private IP addresses must come over the wire via HTTPS. - For NONE, no requests are required to come over via HTTPS. - This is _OPTIONAL._ and defaults to EXTERNAL. + Valid values are: `ALL`, `EXTERNAL`, and `NONE`. + For `ALL`, all requests must come in via HTTPS. + For `EXTERNAL`, only non-private IP addresses must come over the wire via HTTPS. + For `NONE`, no requests are required to come over via HTTPS. + This is _OPTIONAL._ and defaults to `EXTERNAL`. nameIDPolicyFormat:: SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. - It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` _OPTIONAL._. + It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` + This setting is _OPTIONAL._. By default, no special format is requested. forceAuthentication:: SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. - Set this to `true` if you want this. _OPTIONAL._. + Set this to `true` if you want this. This setting is _OPTIONAL._. Set to `false` by default. isPassive:: SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to `true` if you want this. - Do not use together with `forceAuthentication` as they are opposite. _OPTIONAL._. - Set to `false` by default. + Do not use together with `forceAuthentication` as they are opposite. This setting is _OPTIONAL._. + It is set to `false` by default. turnOffChangeSessionIdOnLogin:: - The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). Change this to true if you want to turn this off This is _OPTIONAL_. - The default value is _false_. + The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). + Change this to `true` if you want to turn this off. It is recommended you do not turn it off. + The default value is `false`. diff --git a/topics/saml/java/general-config/sp_principalname_mapping_element.adoc b/topics/saml/java/general-config/sp_principalname_mapping_element.adoc index 8c703a1f5e..32c0d2ddcb 100644 --- a/topics/saml/java/general-config/sp_principalname_mapping_element.adoc +++ b/topics/saml/java/general-config/sp_principalname_mapping_element.adoc @@ -2,10 +2,27 @@ ===== SP PrincipalNameMapping element This element is optional. -When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method. +When creating a Java Principal object that you obtain from methods like `HttpServletRequest.getUserPrincipal()`, you can +define what name that is returned by the `Principal.getName()` method. + +[source,xml] +---- + + + + + + + + +---- + + The `policy` attribute defines the policy used to populate this value. -The values are `FROM_NAME_ID`. -This policy just grabs whatever the SAML subject value is. -The other is `FROM_ATTRIBUTE`. -This will pull the value of Principal.getName() from one of the attributes in the SAML assertion received from the server. -The default value is `FROM_NAME_ID`. +The possible values for this attribute are: + +FROM_NAME_ID:: + This policy just uses whatever the SAML subject value is. This is the default setting +FROM_ATTRIBUTE:: + This will pull the value from one of the attributes declared in the SAML assertion received from the server. + You'll need to specify the name of the SAML assertion attribute to use within the `attribute` XML attribute. From dc0ebc2695a25916bd5a281e3457aeb97bc29fec Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 17:18:42 -0400 Subject: [PATCH 018/194] saml general config --- SUMMARY.adoc | 2 +- topics/saml/java/jboss-adapter.adoc | 2 +- .../jboss_adapter_installation.adoc | 47 ++++++++++++++----- .../required_per_war_configuration.adoc | 10 ++-- .../java/jboss-adapter/securing_wars.adoc | 10 ++-- 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 9c0b376e62..be30ca59e8 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -45,7 +45,7 @@ .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] - .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Required Per WAR Configuration] + .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] .... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] {% if book.community %} ... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/jboss-adapter.adoc index 050e5822d3..56460177d2 100644 --- a/topics/saml/java/jboss-adapter.adoc +++ b/topics/saml/java/jboss-adapter.adoc @@ -2,7 +2,7 @@ ==== JBoss/Wildfly Adapter -To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and configure the Keycloak SAML Adapter Subsystem. +To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and configure the {{book.project.name}} SAML Adapter Subsystem. You then provide a keycloak config, `/WEB-INF/keycloak-saml.xml` file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml. Both methods are described in this section. diff --git a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc index 330525c9ed..2a3e0e38c2 100644 --- a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc +++ b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc @@ -2,30 +2,52 @@ [[_saml-jboss-adapter-installation]] ===== Adapter Installation -SAML Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. -They are also available as a maven artifact. +Each adapter is a separate download on the {{book.project.name}} download site. -Install on Wildfly 9 or 10: +{% if book.community %} +Install on Wildfly 9 or 10, or JBoss EAP 7: [source] ---- $ cd $WILDFLY_HOME $ unzip keycloak-saml-wildfly-adapter-dist.zip ----- +---- +{% endif %} -Install on JBoss EAP 6.x: +{% if book.community %} +Install on JBoss EAP 6.x: [source] ---- $ cd $JBOSS_HOME $ unzip keycloak-saml-eap6-adapter-dist.zip ----- +---- +{% endif %} -This zip file creates new JBoss Modules specific to the Wildfly Keycloak SAML Adapter within your Wildfly distro. +{% if book.product %} +Install on JBoss EAP 6.x: +[source] +---- -After adding the Keycloak modules, you must then enable the Keycloak SAML Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. +$ cd $JBOSS_HOME +$ unzip RH-SSO-saml-eap6-adapter.zip +---- + +Install on JBoss EAP 7.x: +[source] +---- + +$ cd $JBOSS_HOME +$ unzip RH-SSO-saml-eap7-adapter.zip +---- +{% endif %} + + +These zip files create new JBoss Modules specific to the Wildfly/JBoss EPKeycloak SAML Adapter within your Wildfly or JBoss EAP distro. + +After adding the modules, you must then enable the {{book.project.name}} SAML Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. There is a CLI script that will help you modify your server configuration. Start the server and run the script from the server's bin directory: @@ -53,7 +75,8 @@ The script will add the extension, subsystem, and optional security-domain as de ---- -The keycloak security domain should be used with EJBs and other components when you need the security context created in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. +The `keycloak` security domain should be used with EJBs and other components when you need the security context created +in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. Otherwise this configuration is optional. [source,xml] @@ -72,7 +95,8 @@ Otherwise this configuration is optional. ---- -For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: +For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, +you'll want to annotate it with the `@SecurityDomain` annotation as follows: [source,xml] ---- @@ -107,5 +131,6 @@ public class CustomerService { } ---- -We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain annotation when you want to propagate a keycloak security context to the EJB tier. +We hope to improve our integration in the future so that you don't have to specify the +`@SecurityDomain` annotation when you want to propagate a keycloak security context to the EJB tier. diff --git a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc index bd307dc22d..47276e2598 100644 --- a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc +++ b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc @@ -1,14 +1,14 @@ -===== Required Per WAR Configuration +===== Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <> section. Next you must set the `auth-method` to `KEYCLOAK-SAML` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. -Here's an example pulled from one of the examples that comes distributed with Keycloak. +Here's an example _web.xml_ file: [source,xml] ---- @@ -57,6 +57,8 @@ Here's an example pulled from one of the examples that comes distributed with Ke user ----- +---- + +All standard servlet settings except the `auth-method` setting. diff --git a/topics/saml/java/jboss-adapter/securing_wars.adoc b/topics/saml/java/jboss-adapter/securing_wars.adoc index a0d5b4360c..4443e4b694 100644 --- a/topics/saml/java/jboss-adapter/securing_wars.adoc +++ b/topics/saml/java/jboss-adapter/securing_wars.adoc @@ -1,11 +1,11 @@ -===== Securing WARs via Keycloak SAML Subsystem +===== Securing WARs via {{book.project.name}} SAML Subsystem -You do not have to crack open a WAR to secure it with Keycloak. -Alternatively, you can externally secure it via the Keycloak SAML Adapter Subsystem. +You do not have to crack open a WAR to secure it with {{book.project.name}}. +Alternatively, you can externally secure it via the {{book.project.name}} SAML Adapter Subsystem. While you don't have to specify KEYCLOAK-SAML as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. You do not, however, have to create a `WEB-INF/keycloak-saml.xml` file. -This metadata is instead defined within XML in your server's `domain.xml` or `standalone.xml` subsystem configuration section. +This metadata is instead defined within the XML in your server's `domain.xml` or `standalone.xml` subsystem configuration section. [source,xml] ---- @@ -27,7 +27,7 @@ This metadata is instead defined within XML in your server's `domain.xml` or `st The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. -The rest of the configuration uses the same XML syntax as `keycloak-saml.xml` configuration defined in <<_adapter_config,general adapter configuration>>. +The rest of the configuration uses the same XML syntax as `keycloak-saml.xml` configuration defined in <>. An example configuration: From 32938bacca6ac81884f2f8c6e0f145740b463851 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 17:20:05 -0400 Subject: [PATCH 019/194] fixes --- topics/saml/java/general-config/idp_keys_subelement.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/idp_keys_subelement.adoc b/topics/saml/java/general-config/idp_keys_subelement.adoc index c4283037c7..195d1564ea 100644 --- a/topics/saml/java/general-config/idp_keys_subelement.adoc +++ b/topics/saml/java/general-config/idp_keys_subelement.adoc @@ -2,7 +2,7 @@ ===== IDP Keys subelement The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP. -It is defined in the same way as the <>. +It is defined in the same way as the <>. But again, you only have to define one certificate or public key reference. [source,xml] From 31c474f37bbef5c3d27f9f0a34f01190da57c962 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 17:33:28 -0400 Subject: [PATCH 020/194] fixes --- SUMMARY.adoc | 6 ++--- topics/saml/java/assertion-api.adoc | 4 ++- topics/saml/java/debugging.adoc | 2 +- topics/saml/java/error_handling.adoc | 10 +++---- topics/saml/java/idp-registration.adoc | 2 +- .../jetty-adapter/jetty8-per_war_config.adoc | 4 +-- .../jetty-adapter/jetty9_per_war_config.adoc | 4 +-- topics/saml/java/logout.adoc | 4 +-- topics/saml/java/servlet-filter-adapter.adoc | 26 ++++++++++--------- .../tomcat_adapter_installation.adoc | 3 ++- .../tomcat_adapter_per_war_config.adoc | 4 +-- 11 files changed, 37 insertions(+), 32 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index be30ca59e8..f254c1fd1e 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -50,12 +50,12 @@ {% if book.community %} ... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] - .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Required Per WAR Configuration] + .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Per WAR Configuration] ... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] .... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] - .... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Required Per WAR Configuration] + .... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Per WAR Configuration] .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] - .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Required Per WAR Configuration] + .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] {% endif %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] diff --git a/topics/saml/java/assertion-api.adoc b/topics/saml/java/assertion-api.adoc index d2a32a5248..28e6919adb 100644 --- a/topics/saml/java/assertion-api.adoc +++ b/topics/saml/java/assertion-api.adoc @@ -1,7 +1,9 @@ ==== Obtaining Assertion Attributes -After a successful SAML login, your application code may want to obtain attribute values passed with the SAML assertion. `HttpServletRequest.getUserPrincipal` returns a Principal object that you can typecast into a Keycloak specific class called `org.keycloak.adapters.saml.SamlPrincipal`. +After a successful SAML login, your application code may want to obtain attribute values passed with the SAML assertion. +`HttpServletRequest.getUserPrincipal()` returns a `Principal` object that you can typecast into a {{book.project.name}} specific class +called `org.keycloak.adapters.saml.SamlPrincipal`. This object allows you to look at the raw assertion and also has convenience functions to look up attribute values. diff --git a/topics/saml/java/debugging.adoc b/topics/saml/java/debugging.adoc index 5b567045f6..84fb0696a7 100644 --- a/topics/saml/java/debugging.adoc +++ b/topics/saml/java/debugging.adoc @@ -1,6 +1,6 @@ ==== Troubleshooting -The best way to troubleshoot some problems is to turn on debugging for saml in both the client adapter and the keycloak server. +The best way to troubleshoot some problems is to turn on debugging for saml in both the client adapter and the {{book.project.name}} server. To do this turn on debugging int the `org.keycloak.saml` package to `debug` in your log4j or other logging framework. Turning this on allows you to see the SAML requests and response documents being sent to and from the server. diff --git a/topics/saml/java/error_handling.adoc b/topics/saml/java/error_handling.adoc index 82be3d8057..9033571fd2 100644 --- a/topics/saml/java/error_handling.adoc +++ b/topics/saml/java/error_handling.adoc @@ -1,10 +1,10 @@ ==== Error Handling -Keycloak has some error handling facilities for servlet based client adapters. -When an error is encountered in authentication, keycloak will call `HttpServletResponse.sendError()`. -You can set up an error-page within your `web.xml` file to handle the error however you want. -Keycloak may throw 400, 401, 403, and 500 errors. +{{book.project.name}} has some error handling facilities for servlet based client adapters. +When an error is encountered in authentication, the client adapter will call `HttpServletResponse.sendError()`. +You can set up an `error-page` within your `web.xml` file to handle the error however you want. +The client adapter may throw 400, 401, 403, and 500 errors. [source,xml] @@ -15,7 +15,7 @@ Keycloak may throw 400, 401, 403, and 500 errors. ---- -Keycloak also sets an `HttpServletRequest` attribute that you can retrieve. +The client adapter also sets an `HttpServletRequest` attribute that you can retrieve. The attribute name is `org.keycloak.adapters.spi.AuthenticationError`. Typecast this object to: `org.keycloak.adapters.saml.SamlAuthenticationError`. This class can tell you exactly what happened. diff --git a/topics/saml/java/idp-registration.adoc b/topics/saml/java/idp-registration.adoc index c1cb78d025..630a29ae32 100644 --- a/topics/saml/java/idp-registration.adoc +++ b/topics/saml/java/idp-registration.adoc @@ -2,4 +2,4 @@ ==== Registering with an IDP For each servlet based adapter, the endpoint you register for the assert consumer service url and and single logout service -must be the base url of your servlet application with `/saml` appended to it i.e. https://example.com/contextPath/saml +must be the base url of your servlet application with `/saml` appended to it i.e. `https://example.com/contextPath/saml` diff --git a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc index 073cfd8b97..75b5381438 100644 --- a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc +++ b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc @@ -1,5 +1,5 @@ -===== Jetty 8 Required Per WAR Configuration +===== Jetty 8 Per WAR Configuration Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. -See <<_jetty9_per_war,Required Per WAR Configuration>> +See <> diff --git a/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc b/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc index 415373de79..e27def08a0 100644 --- a/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc +++ b/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc @@ -1,6 +1,6 @@ [[_saml-jetty9-per-war]] -===== Jetty 9 Required Per WAR Configuration +===== Jetty 9 Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. @@ -22,7 +22,7 @@ This is a Jetty specific config file and you must define a Keycloak specific aut ---- Next you must create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: diff --git a/topics/saml/java/logout.adoc b/topics/saml/java/logout.adoc index e30c08282a..25528af316 100644 --- a/topics/saml/java/logout.adoc +++ b/topics/saml/java/logout.adoc @@ -1,6 +1,6 @@ ==== Logout There are multiple ways you can logout from a web application. -For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point -the browser at any url of your web application that has a security constraing and pass in a query parameter GLO, i.e. `http://myapp?GLO=true`. +For Java EE servlet containers, you can call `HttpServletRequest.logout()`. For any other browser application, you can point +the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. `http://myapp?GLO=true`. This will log you out if you have an SSO session with your browser. diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/topics/saml/java/servlet-filter-adapter.adoc index 434d3b1480..f8c3ab5a02 100644 --- a/topics/saml/java/servlet-filter-adapter.adoc +++ b/topics/saml/java/servlet-filter-adapter.adoc @@ -1,14 +1,15 @@ ==== Java Servlet Filter Adapter -If you want to use SAML with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. +If you want to use SAML with a Java servlet application that doesn't have an adapter for that servlet platform, you can +opt to use the servlet filter adapter that {{book.project.name}} has. This adapter works a little differently than the other adapters. You do not define security constraints in web.xml. -Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure. +Instead you define a filter mapping using the {{book.project.name}} servlet filter adapter to secure the url patterns you want to secure. -WARNING: Backchannel logout works a bit differently than the standard adapters. -Instead of invalidating the http session it instead marks the session id as logged out. -There's just no way of arbitrarily invalidating an http session based on a session id. +NOTE: Backchannel logout works a bit differently than the standard adapters. + Instead of invalidating the http session it instead marks the session id as logged out. + There's just no way of arbitrarily invalidating an http session based on a session id. WARNING: Backchannel logout does not currently work when you have a clustered application that uses the SAML filter. @@ -32,12 +33,13 @@ WARNING: Backchannel logout does not currently work when you have a clustered ap ---- -The Keycloak filter has the same configuration parameters available as the other adapters except you must define them as filter init params instead of context params. +The {{book.project.name}} filter has the same configuration parameters available as the other adapters except you must +define them as filter init params instead of context params. You can define multiple filter mappings if you have various different secure and unsecure url patterns. WARNING: You must have a filter mapping that covers `/saml`. -This mapping covers all server callbacks. + This mapping covers all server callbacks. When registering SPs with an IDP, you must register `http[s]://hostname/{context-root}/saml` as your Assert Consumer Service URL and Single Logout Service URL. @@ -45,9 +47,9 @@ To use this filter, include this maven artifact in your WAR poms [source,xml] ---- - - org.keycloak - keycloak-saml-servlet-filter-adapter - &project.version; - + + org.keycloak + keycloak-saml-servlet-filter-adapter + &project.version; + ---- diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc b/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc index 635a1ed19b..9f6ecf19dc 100644 --- a/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc +++ b/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc @@ -7,7 +7,8 @@ Each adapter is a separate download on the Keycloak download site. They are also available as a maven artifact. You must unzip the adapter distro into Tomcat's `lib/` directory. -Including adapter's jars within your WEB-INF/lib directory will not work! The Keycloak SAML adapter is implemented as a Valve and valve code must reside in Tomcat's main lib/ directory. +Including adapter's jars within your WEB-INF/lib directory will not work! The Keycloak SAML adapter is implemented as +a Valve and valve code must reside in Tomcat's main lib/ directory. [source] diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc index 8f867af000..7b63cc8068 100644 --- a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc +++ b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc @@ -1,5 +1,5 @@ -===== Required Per WAR Configuration +===== Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. @@ -14,7 +14,7 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>>section. +The format of this config file is describe in the <<> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: From 2439546f24eac0483a72166e312a3a529342db7d Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 17:59:25 -0400 Subject: [PATCH 021/194] mod-auth-mellon --- SUMMARY.adoc | 1 + .../mod-auth-mellon-config-download.png | Bin 0 -> 257694 bytes .../mod-auth-mellon-config-download.png | Bin 0 -> 254290 bytes topics/saml/mod-auth-mellon.adoc | 25 ++++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 keycloak-images/mod-auth-mellon-config-download.png create mode 100644 rhsso-images/mod-auth-mellon-config-download.png create mode 100644 topics/saml/mod-auth-mellon.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index f254c1fd1e..3fd60bed92 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -66,4 +66,5 @@ {% if book.community %} ... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] {% endif %} + .. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file diff --git a/keycloak-images/mod-auth-mellon-config-download.png b/keycloak-images/mod-auth-mellon-config-download.png new file mode 100644 index 0000000000000000000000000000000000000000..1de2b0af39349679de37a85ac8c6e5d8d659c12c GIT binary patch literal 257694 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 z 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

p_%UGdgg*t{%upc6c%F~1lS?E9(TL0r}~=| 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 literal 0 HcmV?d00001 diff --git a/topics/saml/mod-auth-mellon.adoc b/topics/saml/mod-auth-mellon.adoc new file mode 100644 index 0000000000..0467afb9a0 --- /dev/null +++ b/topics/saml/mod-auth-mellon.adoc @@ -0,0 +1,25 @@ +[[_mod_auth_mellon]] + +=== mod_auth_mellon Apache HTTPD Module + +The https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD +as a proxy, then you can use _mod_auth_mellon_ to secure your web application with SAML. Configuration of this adapter +is beyond the scope of this document. Please see the _mod_auth_mellon_ Github repo for more details on configuration. + +To configure _mod_auth_mellon_ you'll need + +* IDP entity descriptor XML file. This describes the connection to {{book.project.name}} or another SAML IDP +* SP entity descriptor XML file. This describes the SAML connections and config for the application you are securing. +* Private key PEM file. This is a text file that defines the private key the application will use to sign documents. It is + in the PEM format +* Certificate PEM file. This is a text file that defines the certificate for your application. +* _mod_auth_mellon_ specific Apache HTTPD module config. + +If you have already defined and registered the client application within a realm on the {{book.project.name}} application server, +{{book.project.name}} can generate all the files you need except the Apache HTTPD module config. +Go to the `Installation` tab of your SAML client and select the `Mod Auth Mellon files` option. + +.mod_auth_mellon config download +image:../../{{book.images}}/mod-auth-mellon-config-download.png[] + +Click the `Download` button and you will download a zip file that contains the XML descriptor and pem files you need. From 849414d1582dc5ca523cbc25a55f0d78e6142554 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 2 Jun 2016 18:04:37 -0400 Subject: [PATCH 022/194] fixes --- topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc | 2 +- topics/saml/java/servlet-filter-adapter.adoc | 4 +++- .../java/tomcat-adapter/tomcat_adapter_per_war_config.adoc | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc index 75b5381438..90f66c7c57 100644 --- a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc +++ b/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc @@ -2,4 +2,4 @@ ===== Jetty 8 Per WAR Configuration Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. -See <> +See <> diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/topics/saml/java/servlet-filter-adapter.adoc index f8c3ab5a02..f377f4f4b2 100644 --- a/topics/saml/java/servlet-filter-adapter.adoc +++ b/topics/saml/java/servlet-filter-adapter.adoc @@ -4,7 +4,9 @@ If you want to use SAML with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that {{book.project.name}} has. This adapter works a little differently than the other adapters. -You do not define security constraints in web.xml. +You still have to specify a `/WEB-INF/keycloak-saml.xml` file as defined in +the <> section, but +you do not define security constraints in _web.xml_. Instead you define a filter mapping using the {{book.project.name}} servlet filter adapter to secure the url patterns you want to secure. NOTE: Backchannel logout works a bit differently than the standard adapters. diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc index 7b63cc8068..3ea4a65066 100644 --- a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc +++ b/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc @@ -14,7 +14,7 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<> section. +The format of this config file is describe in the <> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: From f823a1fc3dc26922f3205c8e5cc2030873acfcc7 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 3 Jun 2016 09:05:27 +0200 Subject: [PATCH 023/194] Fix links --- topics/oidc/java/java-adapter-config.adoc | 6 ++-- topics/oidc/java/java-adapters.adoc | 2 +- topics/oidc/java/servlet-filter-adapter.adoc | 2 +- topics/oidc/java/spring-boot-adapter.adoc | 2 +- topics/oidc/java/spring-security-adapter.adoc | 2 +- topics/oidc/javascript-adapter.adoc | 2 +- topics/overview/supported-platforms.adoc | 28 +++++++++---------- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 8067941f38..cce3a132a8 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -169,18 +169,18 @@ always-refresh-token:: register-node-at-startup:: If _true_, then adapter will send registration request to Keycloak. It's _false_ by default and useful only when application is clustered. - See link:application-clustering.html[Application Clustering] for details + See <> for details register-node-period:: Period for re-registration adapter to Keycloak. Useful when application is clustered. - See link:application-clustering.html[Application Clustering] for details + See <> for details token-store:: Possible values are _session_ and _cookie_. Default is _session_, which means that adapter stores account info in HTTP Session. Alternative _cookie_ means storage of info in cookie. - See link:application-clustering.html[Application Clustering] for details + See <> for details principal-attribute:: OpenID Connection ID Token attribute to populate the UserPrincipal name with. diff --git a/topics/oidc/java/java-adapters.adoc b/topics/oidc/java/java-adapters.adoc index a6a5f10113..4aea42c559 100644 --- a/topics/oidc/java/java-adapters.adoc +++ b/topics/oidc/java/java-adapters.adoc @@ -2,5 +2,5 @@ {{book.project.name}} comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform. -All Java adapters share a set of common configuration options described in the link:java-adapter-config.html[Java Adapters Config] chapter. There are also +All Java adapters share a set of common configuration options described in the <> chapter. There are also a few more chapters that are relevant to all Java adapters. \ No newline at end of file diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 99e83244f7..035ecefd41 100755 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,4 +1,4 @@ - +[[_servlet_filter_adapter]] === Java Servlet Filter Adapter If you want to use Keycloak with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index ef70d29bb1..87f59a036d 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -1,4 +1,4 @@ - +[[_spring_boot_adapter]] === Spring Boot Adapter To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app. diff --git a/topics/oidc/java/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc index 2766317e32..84e8b6c3ad 100755 --- a/topics/oidc/java/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -1,4 +1,4 @@ - +[[_spring_security_adapter]] === Spring Security Adapter To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project. diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index a9591730dd..3bda03a5c7 100755 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -1,4 +1,4 @@ - +[[_javascript_adapter]] == Javascript Adapter The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications. diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 3590ed4b3e..1a630d16dc 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -3,26 +3,26 @@ === OpenID Connect ==== Java -* link:oidc/java/jboss-adapters.html[JBoss EAP] +* <> {% if book.community %} - * link:oidc/java/jboss-adapters.html[WildFly] + * <> {% endif %} -* link:oidc/java/fuse-adapter.html[Fuse] +* <> {% if book.community %} - * link:oidc/java/tomcat-adapter.html[Tomcat] - * link:oidc/java/jetty-adapter.html[Jetty] + * <> + * <>, <> {% endif %} -* link:oidc/java/servlet-filter-adapter.html[Servlet Filter] +* <> {% if book.community %} - * link:oidc/java/spring-adapter.html[Spring Security] (community) - * link:oidc/java/spring-boot-adapter.html[Spring Boot] (community) + * <> (community) + * <> (community) {% endif %} ==== JavaScript (client-side) -* link:oidc/javascript-adapter.html[JavaScript] +* <> === Apache Cordova -* link:oidc/javascript-adapter.html[JavaScript] +* <> {% if book.community %} ==== Node.js @@ -61,11 +61,11 @@ ==== Java -* link:oidc/java/jboss-adapters.html[JBoss EAP] +* <> {% if book.community %} -* link:oidc/java/jboss-adapters.html[WildFly] -* link:oidc/java/tomcat-adapter.html[Tomcat] -* link:oidc/java/jetty-adapter.html[Jetty] +* <> +* <> +* <> {% endif %} ==== Apache HTTP Server From d53fe6a8f42f9f7f9cb772d29a257cc3bc2e4ad6 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 3 Jun 2016 09:10:01 +0200 Subject: [PATCH 024/194] Replace Keycloak with project name --- topics/oidc/java/java-adapter-config.adoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index cce3a132a8..03000d5a58 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -2,7 +2,7 @@ [[_java_adapter_config]] === Java Adapter Config -Each Java adapter supported by Keycloak can be configured by a simple JSON file. +Each Java adapter supported by {{book.project.name}} can be configured by a simple JSON file. This is what one might look like: [source,json] @@ -71,7 +71,7 @@ use-resource-role-mappings:: The default value is _false_. public-client:: - If set to true, the adapter will not send credentials for the client to Keycloak. + If set to true, the adapter will not send credentials for the client to {{book.project.name}}. This is _OPTIONAL_. The default value is _false_. @@ -118,8 +118,8 @@ credentials:: This is _REQUIRED_. connection-pool-size:: - Adapters will make separate HTTP invocations to the Keycloak Server to turn an access code into an access token. - This config option defines how many connections to the Keycloak Server should be pooled. + Adapters will make separate HTTP invocations to the {{book.project.name}} server to turn an access code into an access token. + This config option defines how many connections to the {{book.project.name}} server should be pooled. This is _OPTIONAL_. The default value is `20`. @@ -130,7 +130,7 @@ disable-trust-manager:: The default value is `false`. allow-any-hostname:: - If the {{book.project.name}} server requires HTTPS and this config option is set to `true` the Keycloak Server's certificate is validated via the truststore, + If the {{book.project.name}} server requires HTTPS and this config option is set to `true` the {{book.project.name}} server's certificate is validated via the truststore, but host name validation is not done. This setting should only be used during development and *never* in production as it will disable verification of SSL certificates. This seting may be useful in test environments This is _OPTIONAL_. @@ -143,7 +143,7 @@ truststore:: Client making HTTPS requests need a way to verify the host of the server they are talking to. This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. - You can create this truststore by extracting the public certificate of the Keycloak server's SSL keystore. + You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. truststore-password:: @@ -167,12 +167,12 @@ always-refresh-token:: If _true_, the adapter will refresh token in every request. register-node-at-startup:: - If _true_, then adapter will send registration request to Keycloak. + If _true_, then adapter will send registration request to {{book.project.name}}. It's _false_ by default and useful only when application is clustered. See <> for details register-node-period:: - Period for re-registration adapter to Keycloak. + Period for re-registration adapter to {{book.project.name}}. Useful when application is clustered. See <> for details From d1f3dc049fed7274da2f032a8097d84a09becdea Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 3 Jun 2016 10:02:59 +0200 Subject: [PATCH 025/194] Updated JBoss adapter chapter --- topics/oidc/java/jboss-adapter.adoc | 204 +++++++++++++++------------- 1 file changed, 111 insertions(+), 93 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index d65ac497be..f4d861fe13 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -1,110 +1,137 @@ [[_jboss_adapter]] -=== JBoss/Wildfly Adapter +{% if book.community %} +=== JBoss EAP/Wildfly Adapter +{% endif %} +{% if book.product %} +=== JBoss EAP Adapter +{% endif %} -To be able to secure WAR apps deployed on JBoss AS 7.1.1, JBoss EAP 6.x, or Wildfly, you must install and configure the Keycloak Subsystem. -You then have two options to secure your WARs. -You can provide a keycloak config file in your WAR and change the auth-method to KEYCLOAK within web.xml. -Alternatively, you don't have to crack open your WARs at all and can apply Keycloak via the Keycloak Subsystem configuration in standalone.xml. -Both methods are described in this section. +To be able to secure WAR apps deployed on JBoss EAP {% if book.community %}, WildFly or JBoss AS{% endif %}, you must install and configure the +{{book.project.name}} adapter subsystem. You then have two options to secure your WARs. + +You can provide an adapter config file in your WAR and change the auth-method to KEYCLOAK within web.xml. + +Alternatively, you don't have to modify your WAR at all and you can secure it via the {{book.project.name}} adapter subsystem configuration in `standalone.xml`. +Both methods are described in this section. [[_jboss_adapter_installation]] ==== Adapter Installation -Adapters are no longer included with the appliance or war distribution. -Each adapter is a separate download on the Keycloak download site. -They are also available as a maven artifact. +Adapters are available as a separate archive and are also available as Maven artifacts. -Install on Wildfly 9 or 10: +{% if book.community %} +Install on Wildfly 9 or 10: -[source] +[source,bash] ---- - $ cd $WILDFLY_HOME -$ unzip keycloak-wildfly-adapter-dist.zip ----- -Install on Wildfly 8: - -[source] +$ unzip keycloak-wildfly-adapter-dist-{{book.project.version}}.zip ---- +Install on Wildfly 8: + +[source,bash] +---- $ cd $WILDFLY_HOME -$ unzip keycloak-wf8-adapter-dist.zip ----- -Install on JBoss EAP 6.x: +$ unzip keycloak-wf8-adapter-dist-{{book.project.version}}.zip +---- + +Install on JBoss EAP 7: [source] ---- +$ cd $EAP_HOME +$ unzip keycloak-eap7-adapter-dist-{{book.project.version}}.zip +---- +Install on JBoss EAP 6: + +[source] +---- +$ cd $EAP_HOME +$ unzip keycloak-eap6-adapter-dist-{{book.project.version}}.zip +---- + +Install on JBoss AS 7.1: + +[source] +---- $ cd $JBOSS_HOME -$ unzip keycloak-eap6-adapter-dist.zip ----- -Install on JBoss AS 7.1.1: +$ unzip keycloak-as7-adapter-dist-{{book.project.version}}.zip +---- +{% endif %} + +{% if book.product %} +Install on JBoss EAP 7: [source] ---- +$ cd $EAP_HOME +$ unzip RH-SSO-{{book.project.version}}-eap7-adapter.zip +---- -$ cd $JBOSS_HOME -$ unzip keycloak-as7-adapter-dist.zip +Install on JBoss EAP 6: + +[source] +---- +$ cd $EAP_HOME +$ unzip RH-SSO-{{book.project.version}}-eap6-adapter.zip +---- +{% endif %} + +This ZIP archive contains JBoss Modules specific to the {{book.project.name}} adapter. It also contains JBoss CLI scripts to install and configure the adapter. + +Once the ZIP archive is extracted you have to enable the {{book.project.name}} subystem in the server configuration (i.e. `standalone.xml`). The easiest way to +do this is to use the supplied JBoss CLI scripts. + +To install and configure the adapter, first start the server and then run the JBoss CLI installation script : + +[source] +---- +$ ./bin/jboss-cli.sh -c --file=adapter-install.cli +---- + +The script will add the required configuration to the server configuration file. + +For JBoss EAP 7 {% if book.community %} and WildFly 9+{% endif %} there is also an offline CLI script that can be used to install the adapter while the server +is not running: + +[source] +---- +$ ./bin/jboss-cli.sh -c --file=adapter-install-offline.cli ---- -This zip file creates new JBoss Modules specific to the Wildfly Keycloak Adapter within your Wildfly distro. +If you are planning to add it manually you need to add the extension and subsystem definition to the server configuration: -After adding the Keycloak modules, you must then enable the Keycloak Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. +[source,xml] +---- + + + ... + -There is a CLI script that will help you modify your server configuration. -Start the server and run the script from the server's bin directory: - -[source] + + + ... + ---- -$ cd $JBOSS_HOME/bin -$ jboss-cli.sh -c --file=adapter-install.cli ----- -The script will add the extension, subsystem, and optional security-domain as described below. +If you need to be able to propagate the security context from the web tier to the EJB tier you also need to add the `keycloak` security domain: -For more recent versions of WildFly there's also a offline CLI script that can be used to install the adapter while the server is not running: - -[source] +[source,xml] ---- - -$ cd $JBOSS_HOME/bin -$ jboss-cli.sh -c --file=adapter-install-offline.cli ----- - -[source] ----- - - - - - - ... - - - - - ... - ----- - -The keycloak security domain should be used with EJBs and other components when you need the security context created in the secured web tier to be propagated to the EJBs (other EE component) you are invoking. -Otherwise this configuration is optional. - -[source] ----- - - - + -... - - - - - + ... + + + + + + ... ---- For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: @@ -142,30 +169,27 @@ public class CustomerService { } ---- -We hope to improve our integration in the future so that you don't have to specify the @SecurityDomain annotation when you want to propagate a keycloak security context to the EJB tier. - ==== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. -Here's an example pulled from one of the examples that comes distributed with Keycloak. +Here's an example: -[source] +[source,xml] ---- - - customer-portal + application @@ -208,16 +232,13 @@ Here's an example pulled from one of the examples that comes distributed with Ke ==== Securing WARs via Keycloak Subsystem -You do not have to crack open a WAR to secure it with Keycloak. -Alternatively, you can externally secure it via the Keycloak Adapter Subsystem. +You do not have to modify your WAR to secure it with {{book.project.title}}. Instead you can externally secure it via the {{book.project.title}} Adapter Subsystem. While you don't have to specify KEYCLOAK as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. You do not, however, have to create a `WEB-INF/keycloak.json` file. -This metadata is instead defined within XML in your server's `domain.xml` or `standalone.xml` subsystem configuration section. +This metadata is instead defined within server configuration (i.e. `standalone.xml`) in the {{book.project.title}} subsystem definition. - -[source] +[source,xml] ---- - @@ -236,25 +257,22 @@ This metadata is instead defined within XML in your server's `domain.xml` or `st ---- - The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_adapter_config,general adapter configuration>>. The exception is the `credential` element. -To make it easier for you, you can go to the Keycloak Adminstration Console and go to the Application/Installation tab of the application this WAR is aligned with. +To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. It provides an example XML file you can cut and paste. There is an additional convenience format for this XML if you have multiple WARs you are deployment that are secured by the same domain. This format allows you to define common configuration items in one place under the `realm` element. - -[source] +[source,xml] ---- - - MIGfMA0GCSqGSIb3DQEBA + MIGfMA0GCSqGSIb3DQEBA... http://localhost:8080/auth external From 43ffe3c962a012828ff379bca14c939fc185ac09 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 3 Jun 2016 12:35:36 +0200 Subject: [PATCH 026/194] Complete Java chapters --- SUMMARY.adoc | 2 +- topics/oidc/java/adapter-context.adoc | 25 ++-- topics/oidc/java/adapter_error_handling.adoc | 58 +++----- topics/oidc/java/application-clustering.adoc | 138 ++++++++----------- topics/oidc/java/jaas.adoc | 37 ++--- topics/oidc/java/logout.adoc | 5 +- topics/oidc/java/multi-tenancy.adoc | 68 ++++++--- topics/oidc/java/servlet-filter-adapter.adoc | 43 +++--- topics/oidc/oidc-generic.adoc | 1 + 9 files changed, 183 insertions(+), 194 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 3fd60bed92..cc60f2abca 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -20,7 +20,7 @@ {% endif %} ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/oidc/java/jaas.adoc[JAAS plugin] - ... link:topics/oidc/java/adapter-context.adoc[Keycloak Security Context] + ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] diff --git a/topics/oidc/java/adapter-context.adoc b/topics/oidc/java/adapter-context.adoc index 9ac083172f..41319b0c76 100755 --- a/topics/oidc/java/adapter-context.adoc +++ b/topics/oidc/java/adapter-context.adoc @@ -1,12 +1,19 @@ -=== Keycloak Security Context +=== Security Context -The `KeycloakSecurityContext` interface is available if you need to look at the access token directly. -This context is also useful if you need to get the encoded access token so you can make additional REST invocations. -In servlet environments it is available in secured invocations as an attribute in HttpServletRequest. -Or, it is available in secure and insecure requests in the HttpSession for browser apps. +The `KeycloakSecurityContext` interface is available if you need to access to the tokens directly. This could be useful if you want to retrieve additional +details from the token (such as user profile information) or you want to invoke a RESTful service that is protected by {{book.project.title}}. -[source] +In servlet environments it is available in secured invocations as an attribute in HttpServletRequest: +[source,java] +---- +httpServletRequest + .getAttribute(KeycloakSecurityContext.class.getName()); +---- + +Or, it is available in secure and insecure requests in the HttpSession: + +[source,java] +---- +httpServletRequest.getSession() + .getAttribute(KeycloakSecurityContext.class.getName()); ---- -httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName()); -httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName()); ----- diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc index bf300cd7ca..6e6beadfa9 100755 --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -2,59 +2,33 @@ [[_adapter_error_handling]] === Error Handling -Keycloak has some error handling facilities for servlet based client adapters. -When an error is encountered in authentication, keycloak will call `HttpServletResponse.sendError()`. +{{book.project.name}} has some error handling facilities for servlet based client adapters. +When an error is encountered in authentication, {{book.project.title}} will call `HttpServletResponse.sendError()`. You can set up an error-page within your `web.xml` file to handle the error however you want. -Keycloak may throw 400, 401, 403, and 500 errors. +{{book.project.title}} may throw 400, 401, 403, and 500 errors. - -[source] +[source,xml] ---- - - 404 /ErrorHandler ---- -Keycloak also sets an `HttpServletRequest` attribute that you can retrieve. -The attribute name is `org.keycloak.adapters.spi.AuthenticationError`. -Typecast this object to: `org.keycloak.adapters.OIDCAuthenticationError`. -This class can tell you exactly what happened. -If this attribute is not set, then the adapter was not responsible for the error code. +{{book.project.name}} also sets a `HttpServletRequest` attribute that you can retrieve. +The attribute name is `org.keycloak.adapters.spi.AuthenticationError`, which should be casted to to `org.keycloak.adapters.OIDCAuthenticationError`. +For example: -[source] +[source,java] ---- +import org.keycloak.adapters.OIDCAuthenticationError; +import org.keycloak.adapters.OIDCAuthenticationError.Reason; +... -public class OIDCAuthenticationError implements AuthenticationError { - public static enum Reason { - NO_BEARER_TOKEN, - NO_REDIRECT_URI, - INVALID_STATE_COOKIE, - OAUTH_ERROR, - SSL_REQUIRED, - CODE_TO_TOKEN_FAILURE, - INVALID_TOKEN, - STALE_TOKEN, - NO_AUTHORIZATION_HEADER - } +OIDCAuthenticationError error = (OIDCAuthenticationError) httpServletRequest + .getAttribute('org.keycloak.adapters.spi.AuthenticationError'); - private Reason reason; - private String description; - - public OIDCAuthenticationError(Reason reason, String description) { - this.reason = reason; - this.description = description; - } - - public Reason getReason() { - return reason; - } - - public String getDescription() { - return description; - } -} ----- \ No newline at end of file +Reason reason = error.getReason(); +System.out.println(reason.name()); +---- \ No newline at end of file diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index b2d0b0a834..50a8d591bb 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -1,131 +1,113 @@ [[_applicationclustering]] === Application Clustering -This chapter is focused on clustering support for your own AS7, EAP6 or Wildfly applications, which are secured by Keycloak. -We support various deployment scenarios according if your application is: +This chapter is related to supporting clustered applications deployed to JBoss EAP{% if book.community %}, WildFly and JBoss AS{% endif %}. -* stateless or stateful -* distributable (replicated http session) or non-distributable and just relying on sticky sessions provided by loadbalancer -* deployed on same or different cluster hosts where keycloak servers are deployed +There are several options depending on if your application is: -The situation is a bit tricky as application communicates with Keycloak directly within user's browser (for example redirecting to login screen), but there is also backend (out-of-bound) communication between keycloak and application, which is hidden from end-user and his browser and hence can't rely on sticky sessions. +* Stateless or stateful +* Distributable (replicated http session) or non-distributable +* Relying on sticky sessions provided by load balancer +* Hosted on same domain as {{book.project.name}} -NOTE: To enable distributable (replicated) HTTP Sessions in your application, you may need to do some additional steps. -Usually you need to put tag into `WEB-INF/web.xml` file of your application and possibly do some additional steps to configure underlying cluster cache (In case of Wildfly, the implementation of cluster cache is based on Infinispan). These steps are server specific, so consult documentation of your application server for more details. +Dealing with clustering is not quite as simple as for a regular application. Mainly due to the fact that both the browser and the server-side application +sends requests to {{book.project.name}}, so it's not as simple as enabling sticky sessions on your load balancer. ==== Stateless token store -By default, the servlet web application secured by Keycloak uses HTTP session to store information about authenticated user account. -This means that this info could be replicated across cluster and your application will safely survive failover of some cluster node. +By default, the web application secured by {{book.project.name}} uses the HTTP session to store security context. This means that you either have to +enable sticky sessions or replicate the HTTP session. -However if you don't need or don't want to use HTTP Session, you may alternatively save all info about authenticated account into cookie. -This is useful especially if your application is: +As an alternative to storing the security context in the HTTP session the adapter can be configured to store this in a cookie instead. This is useful if you want +to make your application stateless or if you don't want to store the security context in the HTTP session. -* stateless application without need of HTTP Session, but with requirement to be safe to failover of some cluster node -* stateful application, but you don't want sensitive token data to be saved in HTTP session -* stateless application relying on loadbalancer, which is not aware of sticky sessions (in this case cookie is your only way) - -To configure this, you can add this line to configuration of your adapter in `WEB-INF/keycloak.json` of your application: -[source] +To use the cookie store for saving the security context, edit your applications `WEB-INF/keycloak.json` and add: +[source,json] ---- - - "token-store": "cookie" ---- -Default value of `token-store` is `session`, hence saving data in HTTP session. +NOTE: The default value for `token-store` is `session`, which stores the security context in the HTTP session. -One limitation of cookie store is, that whole info about account is passed in cookie KEYCLOAK_ADAPTER_STATE in each HTTP request. -Hence it's not the best for network performance. -Another small limitation is limited support for Single-Sign out. -It works without issues if you init servlet logout (HttpServletRequest.logout) from this application itself as the adapter will delete the KEYCLOAK_ADAPTER_STATE cookie. -But back-channel logout initialized from different application can't be propagated by Keycloak to this application with cookie store. -Hence it's recommended to use very short value of access token timeout (1 minute for example). +One limitation of using the cookie store is that the whole security context is passed in the cookie for every HTTP request. This may impact performance. + +Another small limitation is limited support for Single-Sign Out. It works without issues if you init servlet logout (HttpServletRequest.logout) from the +application itself as the adapter will delete the KEYCLOAK_ADAPTER_STATE cookie. However, back-channel logout initialized from a different application isn't +propagated by {{book.project.name}} to applications using cookie store. Hence it's recommended to use a short value for the access token timeout (for example 1 minute). ==== Relative URI optimization -In many deployment scenarios will be Keycloak and secured applications deployed on same cluster hosts. -For this case Keycloak already provides option to use relative URI as value of option _auth-server-url_ in `WEB-INF/keycloak.json` . In this case, the URI of Keycloak server is resolved from the URI of current request. +In deployment scenarios where {{book.project.name}} and the application is hosted on the same domain (through a reverse proxy or load balancer) it can be +convenient to use relative URI options in your client configuration. -For example if your loadbalancer is on _https://loadbalancer.com/myapp_ and auth-server-url is _/auth_, then relative URI of Keycloak is resolved to be _https://loadbalancer.com/auth_ . +With relative URIs the URI is resolved as relative to the URL of the URL used to access {{book.project.name}}. -For cluster setup, it may be even better to use option _auth-server-url-for-backend-request_ . This allows to configure that backend requests between Keycloak and your application will be sent directly to same cluster host without additional round-trip through loadbalancer. -So for this, it's good to configure values in `WEB-INF/keycloak.json` like this: -[source] ----- - - -"auth-server-url": "/auth", -"auth-server-url-for-backend-requests": "http://${jboss.host.name}:8080/auth" ----- - -This would mean that browser requests (like redirecting to Keycloak login screen) will be still resolved relatively to current request URI like _https://loadbalancer.com/myapp_, but backend (out-of-bound) requests between keycloak and your app are sent always to same cluster host with application . - -Note that additionally to network optimization, you may not need "https" in this case as application and keycloak are communicating directly within same cluster host. +For example if the URL to your application is `https://acme.org/myapp` and the URL to {{book.project.name}} is `https://acme.org/auth`, then you can use +the redirect-uri `/myapp` instead of `https://acme.org/myapp`. ==== Admin URL configuration -Admin URL for particular application can be configured in Keycloak admin console. -It's used by Keycloak server to send backend requests to application for various tasks, like logout users or push revocation policies. +Admin URL for a particular application can be configured in the {{book.project.name}} Administration Console. +It's used by the {{book.project.name}} server to send backend requests to the application for various tasks, like logout users or push revocation policies. -For example logout of user from Keycloak works like this: +For example the way backchannel logout works is: -. User sends logout request from one of applications where he is logged. -. Then application will send logout request to Keycloak -. Keycloak server logout user in itself, and then it re-sends logout request by backend channel to all applications where user is logged. - Keycloak is using admin URL for this. - So logout is propagated to all apps. +. User sends logout request from one application +. The application sends logout request to {{book.project.name}} +. The {{book.project.name}} server invalidates the user session +. The {{book.project.name}} server then sends a backchannel request to application with a admin url that are associated with the session +. When an application receives the logout request it invalidates the corresponding HTTP session -You may again use relative values for admin URL, but in cluster it may not be the best similarly like in <<_relative_uri_optimization,previous section>> . - -Some examples of possible values of admin URL are: - -http://${jboss.host.name}:8080/myapp:: - This is best choice if "myapp" is deployed on same cluster hosts like Keycloak and is distributable. - In this case Keycloak server sends logout request to itself, hence no communication with loadbalancer or other cluster nodes and no additional network traffic. +Some examples of possible values of admin URL are: http://${application.session.host}:8080/myapp:: - Keycloak will track hosts where is particular HTTP Session served and it will send session invalidation message to proper cluster node. + {{book.project.name}} tracks hosts associated with the HTTP Session and will send session invalidation message to the associated node. [[_registration_app_nodes]] -==== Registration of application nodes to Keycloak +==== Registration of application nodes -Previous section describes how can Keycloak send logout request to proper application node. -However in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. -For example push new notBefore for realm or application, or logout all users from all applications on all cluster nodes. +The previous describes how {{book.project.name}} can send logout request to node associated with a specific HTTP session. +However, in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. +For example to push a new not before policy to the application or to logout all users from the application. -In this case Keycloak should be aware of all application cluster nodes, so it could send event to all of them. +In this case {{book.project.name}} needs to be aware of all application cluster nodes, so it can send the event to all of them. To achieve this, we support auto-discovery mechanism: -. Once new application node joins cluster, it sends registration request to Keycloak server -. The request may be re-sent to Keycloak in configured periodic intervals -. If Keycloak won't receive re-registration request within specified timeout (should be greater than period from point 2) then it automatically unregister particular node -. Node is also unregistered in Keycloak when it sends unregistration request, which is usually during node shutdown or application undeployment. - This may not work properly for forced shutdown when undeployment listeners are not invoked, so here you need to rely on automatic unregistration from point 3 . +. When a new application node joins the cluster, it sends a registration request to the {{book.project.name}} server +. The request may be re-sent to {{book.project.name}} in configured periodic intervals +. If the {{book.project.name}} server doesn't receive a re-registration request within a specified timeout then it automatically unregisters the specific node +. The node is also unregistered in {{book.project.name}} when it sends an unregistration request, which is usually during node shutdown or application undeployment. + This may not work properly for forced shutdown when undeployment listeners are not invoked, which results in the need for automatic unregistration -Sending startup registrations and periodic re-registration is disabled by default, as it's main usecase is just cluster deployment. -In `WEB-INF/keycloak.json` of your application, you can specify: +Sending startup registrations and periodic re-registration is disabled by default as it's only required for some clustered applications. + +To enable the feature edit the `WEB-INF/keycloak.json` file for your application and add: [source] ---- "register-node-at-startup": true, "register-node-period": 600, ----- -which means that registration is sent at startup (accurately when 1st request is served by the application node) and then it's resent each 10 minutes. +---- -In Keycloak admin console you can specify the maximum node re-registration timeout (makes sense to have it bigger than _register-node-period_ from adapter configuration for particular application). Also you can manually add and remove cluster nodes in admin console, which is useful if you don't want to rely on adapter's automatic registration or if you want to remove stale application nodes, which weren't unregistered (for example due to forced shutdown). +This means the adapter will send the registration request on startup and re-register every 10 minutes. + +In the {{book.project.name}} Administration Console you can specify the maximum node re-registration timeout (should be larger than _register-node-period_ from +the adapter configuration). You can also manually add and remove cluster nodes in through the Adminstration Console, which is useful if you don't want to rely +on the automatic registration feature or if you want to remove stale application nodes in the event your not using the automatic unregistration feature. [[_refresh_token_each_req]] ==== Refresh token in each request -By default, application adapter tries to refresh access token when it's expired (period can be specified as <<_token_timeouts,Access Token Lifespan>>) . However if you don't want to rely on the fact, that Keycloak is able to successfully propagate admin events like logout to your application nodes, then you have possibility to configure adapter to refresh access token in each HTTP request. +By default the application adapter will only refresh the access token when it's expired. However, you can also configure the adapter to refresh the token on every +request. This may have a performance impact as your application will send more requests to the {{book.project.name}} server. -In `WEB-INF/keycloak.json` you can configure: +To enable the feature edit the `WEB-INF/keycloak.json` file for your application and add: [source] ---- "always-refresh-token": true ---- -Note that this has big performance impact. -It's useful just if performance is not priority, but security is critical and you can't rely on logout and push notBefore propagation from Keycloak to applications. +NOTE: This may have a significant impact on performance. Only enable this feature if you can't rely on backchannel messages to propagate logout and not before + policies. Another thing to consider is that by default access tokens has a short expiration so even if logout is not propagated the token will expire within + minutes of the logout. \ No newline at end of file diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc index 8145355ce3..fec56b196f 100755 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -1,27 +1,30 @@ - [[_jaas_adapter]] === JAAS plugin -It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, but directly choose one of our adapters. -However some applications and systems may still rely on pure legacy JAAS solution. -Keycloak provides couple of login modules to help with such use cases. -Some login modules provided by Keycloak are: - +It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters. +However, some applications and systems may still rely on pure legacy JAAS solution. +{{book.project.title}} provides two login modules to help in these situations. +The provided login modules are: org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule:: - This login module allows to authenticate with username/password from Keycloak database. - It's using <<_direct_access_grants,Direct Access Grants>> Keycloak endpoint to validate on Keycloak side if provided username/password is valid. - It's useful especially for non-web based systems, which need to rely on JAAS and want to use Keycloak credentials, but can't use classic browser based authentication flow due to their non-web nature. - Example of such application could be messaging application or SSH system. + This login module allows to authenticate with username/password from {{book.project.title}}. + It's using <> flow to validate if the provided + username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use Keycloak, but can't use the standard browser + based flows due to their non-web nature. Example of such application could be messaging or SSH. org.keycloak.adapters.jaas.BearerTokenLoginModule:: - This login module allows to authenticate with Keycloak access token passed to it through CallbackHandler as password. - It may be useful for example in case, when you have Keycloak access token from classic web based authentication flow and your web application then needs to talk to external non-web based system, which rely on JAAS. - For example to JMS/messaging system. + This login module allows to authenticate with {{book.project.title}} access token passed to it through CallbackHandler as password. + It may be useful for example in case, when you have {{book.project.title}} access token from standard based authentication flow and your web application then + needs to talk to external non-web based system, which rely on JAAS. For example a messaging system. -Both login modules have configuration property `keycloak-config-file` where you need to provide location of keycloak.json configuration file. -It could be either provided from filesystem or from classpath (in that case you may need value like `classpath:/folder-on-classpath/keycloak.json` ). +Both modules use the following configuration properties: -Second property `role-principal-class` allows to specify alternative class for Role principals attached to JAAS Subject. -Default value for Role principal is `org.keycloak.adapters.jaas.RolePrincipal` . Note that class should have constructor with single String argument. \ No newline at end of file +keycloak-config-file:: + The location of the `keycloak.json` configuration file. The configuration file can either be located on the filesystem or on the classpath. If it's located + on the classpath you need to prefix the location with `classpath:` (for example `classpath:/path/keycloak.json`). + This is _REQUIRED._ + +`role-principal-class`:: + Configure alternative class for Role principals attached to JAAS Subject. + Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument. \ No newline at end of file diff --git a/topics/oidc/java/logout.adoc b/topics/oidc/java/logout.adoc index 3a9c42dac4..b56c9b4c90 100755 --- a/topics/oidc/java/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -1,6 +1,5 @@ - === Logout There are multiple ways you can logout from a web application. -For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point the browser at the url `http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri`. -This will log you out if you have an SSO session with your browser. \ No newline at end of file +For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can redirect the browser to +`http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri`. This will log you out if you have a SSO session with your browser. \ No newline at end of file diff --git a/topics/oidc/java/multi-tenancy.adoc b/topics/oidc/java/multi-tenancy.adoc index 5f339f16be..5992d2578f 100755 --- a/topics/oidc/java/multi-tenancy.adoc +++ b/topics/oidc/java/multi-tenancy.adoc @@ -1,28 +1,54 @@ === Multi Tenancy -Multi Tenancy, in our context, means that one single target application (WAR) can be secured by a single (or clustered) Keycloak server, authenticating its users against different realms. -In practice, this means that one application needs to use different `keycloak.json` files. -For this case, there are two possible solutions: +Multi Tenancy, in our context, means that a single target application (WAR) can be secured with multiple {{book.project.name}} realms. The realms can be located +one the same {{book.project.name}} instance or on different instances. -* The same WAR file deployed under two different names, each with its own Keycloak configuration (probably via the Keycloak Subsystem). - This scenario is suitable when the number of realms is known in advance or when there's a dynamic provision of application instances. - One example would be a service provider that dynamically creates servers/deployments for their clients, like a PaaS. -* client1.acme.com -+`client2.acme.com` -+`/app/client1/` -+`/app/client2/` This chapter of the reference guide focus on this second scenario. +In practice, this means that the application needs to have multiple `keycloak.json` adapter configuration files. -Keycloak provides an extension point for applications that need to evaluate the realm on a request basis. -During the authentication and authorization phase of the incoming request, Keycloak queries the application via this extension point and expects the application to return a complete representation of the realm. -With this, Keycloak then proceeds the authentication and authorization process, accepting or refusing the request based on the incoming credentials and on the returned realm. -For this scenario, an application needs to: +You could have multiple instances of your WAR with different adapter configuration files deployed to different context-paths. However, this may be inconvenient +and you may also want to select the realm based on something else than context-path. -* web.xml -+`keycloak.config.resolver` -+`org.keycloak.adapters.KeycloakConfigResolver` -* org.keycloak.adapters.KeycloakConfigResolver -+`resolve(org.keycloak.adapters.spi.HttpFacade.Request)` -+`org.keycloak.adapters.KeycloakDeployment` +{{book.project.name}} makes it possible to have a custom config resolver so you can choose what adapter config is used for each request. -An implementation of this feature can be found in the examples. \ No newline at end of file +To achieve this first you need to create an implementation of `org.keycloak.adapters.KeycloakConfigResolver`. For example: + +[source,java] +---- +package example; + +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; + +public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver { + + @Override + public KeycloakDeployment resolve(OIDCHttpFacade.Request request) { + if (path.startsWith("alternative")) { + KeycloakDeployment deployment = cache.get(realm); + if (null == deployment) { + InputStream is = getClass().getResourceAsStream("/tenant1-keycloak.json"); + return KeycloakDeploymentBuilder.build(is); + } + } else { + InputStream is = getClass().getResourceAsStream("/default-keycloak.json"); + return KeycloakDeploymentBuilder.build(is); + } + } + +} +---- + +You also need to configure which `KeycloakConfigResolver` implementation to use with the `keycloak.config.resolver` context-param in your `web.xml`: + +[source,xml] +---- + + ... + + keycloak.config.resolver + example.PathBasedKeycloakConfigResolver + + +---- \ No newline at end of file diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 035ecefd41..3f8c809b68 100755 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,25 +1,22 @@ [[_servlet_filter_adapter]] === Java Servlet Filter Adapter -If you want to use Keycloak with a Java servlet application that doesn't have an adapter for that servlet platform, you can opt to use the servlet filter adapter that Keycloak has. -This adapter works a little differently than the other adapters. -You do not define security constraints in web.xml. -Instead you define a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure. +If you are deploying your Java Servlet application on a platform where there is no {{book.project.title}} adapter you opt to use the the servlet filter adapter. +This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml. +Instead you define a filter mapping using the {{book.project.title}} servlet filter adapter to secure the url patterns you want to secure. WARNING: Backchannel logout works a bit differently than the standard adapters. -Instead of invalidating the http session it instead marks the session id as logged out. -There's just no way of arbitrarily invalidating an http session based on a session id. +Instead of invalidating the HTTP session it marks the session id as logged out. +There's no way standard way to invalidate an HTTP session based on a session id. -[source] +[source,xml] ---- - - - customer-portal + application Keycloak Filter @@ -33,23 +30,23 @@ There's just no way of arbitrarily invalidating an http session based on a sessi ---- -If you notice above, there are two url-patterns. - `/protected/*` are just the files we want protected. `/keycloak/*` url-pattern will handle callback from the keycloak server. -Note that you should configure your client in the Keycloak Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. +In the snippet above there are two url-patterns. + `/protected/*` are the files we want protected, while the `/keycloak/*` url-pattern handles callbacks from the {{book.project.title}} server. + +Note that you should configure your client in the {{book.project.title}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. + The Admin URL will make callbacks to the Admin URL to do things like backchannel logout. So, the Admin URL in this example should be `http[s]://hostname/{context-root}/keycloak`. -There is an example of this in the distribution. -The Keycloak filter has the same configuration parameters available as the other adapters except you must define them as filter init params instead of context params. +The {{book.project.title}} filter has the same configuration parameters as the other adapters except you must define them as filter init params instead of context params. -To use this filter, include this maven artifact in your WAR poms +To use this filter, include this maven artifact in your WAR poms: -[source] +[source,xml] ---- - - - org.keycloak - keycloak-servlet-filter-adapter - &project.version; - + + org.keycloak + keycloak-servlet-filter-adapter + &project.version; + ---- \ No newline at end of file diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index a697467c86..d625f9a539 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -14,6 +14,7 @@ TODO ==== Implicit +[[_resource_owner_password_credentials_flow]] ==== Resource Owner Password Credentials ==== Client Credentials From 6cf4c85ffbf5ba37773296af1d2250f5c7ca8e36 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 3 Jun 2016 14:28:51 +0200 Subject: [PATCH 027/194] Complete JavaScript adapter chapter --- topics/oidc/javascript-adapter.adoc | 360 ++++++++++++++-------------- 1 file changed, 184 insertions(+), 176 deletions(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 3bda03a5c7..8653cbadd6 100755 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -1,28 +1,31 @@ [[_javascript_adapter]] == Javascript Adapter -The Keycloak Server comes with a Javascript library you can use to secure HTML/Javascript applications. -This library is referenceable directly from the keycloak server. -You can also download the adapter from Keycloak's download site if you want a static copy. -It works in the same way as other application adapters except that your browser is driving the OAuth redirect protocol rather than the server. +{{book.project.name}} comes with a client-side JavaScript library that can be used to secure HTML5/JavaScript applications. The JavaScript adapter has built-in +support for Cordova applications. -The disadvantage of using this approach is that you have a non-confidential, public client. -This makes it more important that you register valid redirect URLs and make sure your domain name is secured. +The library can be retrieved directly from the {{book.project.name}} server at `/auth/js/keycloak.js` and is also distributed as a ZIP archive. -To use this adapter, you must first configure an application (or client) through the `Keycloak Admin Console`. -You should select `public` for the `Access Type` field. -As public clients can't be verified with a client secret, you are required to configure one or more valid redirect uris. -Once you've configured the application, click on the `Installation` tab and download the `keycloak.json` file. -This file should be hosted on your web-server at the same root as your HTML pages. -Alternatively, you can manually configure the adapter and specify the URL for this file. +One important thing to note about using client-side applications is that the client has to be a public client as there is no secure way to store client +credentials in a client-side application. This makes it very important to make sure the redirect URIs you have configured for the client are correct and as +specific as possible. -Next, you have to initialize the adapter in your application. -An example is shown below. +To use the JavaScript adapter you must first create a client for your application in the {{book.project.name}} Administration Console. Make sure `public` +is selected for `Access Type`. + +You also need to configure valid redirect URIs and valid web origins. Be as specific as possible as failing to do so may results in a security vulnerability. + +Once the client is created click on the `Installation` tab select `Keycloak OIDC JSON` for `Format Option` then click on `Download`. The downloaded +`keycloak.json` file should be hosted on your web server at the same location as your HTML pages. + +Alternatively, you can skip the configuration file and manually configure the adapter. + +The following example shows how to initialize the JavaScript adapter: [source,html] ---- - + ---- -To specify the location of the keycloak.json file: -[source] +If the `keycloak.json` file is in a different location you can specify it: + +[source,javascript] ---- var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json')); ----- -Or finally to manually configure the adapter: - -[source] ---- +You can also skip the file altogether and manually configure the adapter: + +[source,javascript] +---- var keycloak = Keycloak({ url: 'http://keycloak-server/auth', realm: 'myrealm', clientId: 'myapp' }); ----- -You can also pass `login-required` or `check-sso` to the init function. -Login required will cause a redirect to the login form on the server, while check-sso will simply redirect to the auth server to check if the user is already logged in to the realm. -For example: +---- + +By default to authenticate you need to call the `login` function. However, there are two options available to make the adapter automatically authenticate. You +can pass `login-required` or `check-sso` to the init function. `login-required` will authenticate the client if the user is logged-in to {{book.project.name}} +or display the login page if not. `check-sso` will only authenticate the client if the user is already logged-in, if the user is not logged-in the browser will be +redirected back to the application and remain unauthenticated. + +To enable `login-required` set `onLoad` to `login-required` and pass to the init method: [source] ---- keycloak.init({ onLoad: 'login-required' }) ---- -After you login, your application will be able to make REST calls using bearer token authentication. -Here's an example pulled from the `customer-portal-js` example that comes with the distribution. +After the user is authenticated the application can make requests to RESTful services secured by {{book.project.name}} by including the bearer token in the +`Authorization` header. For example: -[source] +[source,javascript] ---- - - ----- + req.send(); +}; +---- -The `loadData()` method builds an HTTP request setting the `Authorization` header to a bearer token. -The `keycloak.token` points to the access token the browser obtained when it logged you in. -The `loadFailure()` method is invoked on a failure. -The `reloadData()` function calls `keycloak.updateToken()` passing in the `loadData()` and `loadFailure()` callbacks. -The `keycloak.updateToken()` method checks to see if the access token hasn't expired. -If it hasn't, and your oauth login returned a refresh token, this method will refresh the access token. -Finally, if successful, it will invoke the success callback, which in this case is the `loadData()` method. +One thing to keep in mind is that the access token by default has a short life expiration so you may need to refresh the access token prior to sending the +request. You can do this by the `updateToken` method. The `updateToken` method returns a promise object which makes it easy to invoke the service only if the +token was successfully refreshed and for example display an error to the user if it wasn't. For example: -To refresh the token when it is expired, call the `updateToken` method. -This method returns a promise object, which can be used to invoke a function on success or failure. -This method can be used to wrap functions that should only be called with a valid token. -For example, the following method will refresh the token if it expires within 30 seconds, and then invoke the specified function. -If the token is valid for more than 30 seconds it will just call the specified function. - -[source] +[source,javascript] ---- keycloak.updateToken(30).success(function() { - // send request with valid token + loadData(); }).error(function() { - alert('failed to refresh token'); + alert('Failed to refresh token'); ); ---- == Session status iframe -By default, the JavaScript adapter creates a non-visible iframe that is used to detect if a single-sign out has occurred. -This does not require any network traffic, instead the status is retrieved from a special status cookie. -This feature can be disabled by setting `checkLoginIframe: false` in the options passed to the `init` method. +By default, the JavaScript adapter creates a hidden iframe that is used to detect if a Single-Sign Out has occurred. +This does not require any network traffic, instead the status is retrieved by looking at a special status cookie. +This feature can be disabled by setting `checkLoginIframe: false` in the options passed to the `init` method. + +You should not rely on looking at this cookie directly. It's format can change and it's also associated with the URL of the {{book.project.name}} server, not +your application. [[_javascript_implicit_flow]] == Implicit and Hybrid Flow -By default, the JavaScript adapter uses http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[OpenID Connect standard (Authorization code) flow], which means that after authentication, the Keycloak server redirects the user back to your application, where the JavaScript adapter will exchange the `code` for an access token and a refresh token. +By default, the JavaScript adapter uses the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code] flow. +With this flow the {{book.project.name}} server returns a authorization code, not a authentication token, to the application. The JavaScript adapter exchanges +the `code` for an access token and a refresh token after the browser is redirected back to the application. -However, Keycloak also supports http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[OpenID Connect Implicit flow] where an access token is sent immediately after successful authentication with Keycloak (there is no additional request for exchange code). This could have better performance than standard flow, as there is no additional request to exchange the code for tokens. -However, sending the access token in the URL fragment could pose a security issue in some environments (access logs might expose tokens located in the URL). +{{book.project.name}} also supports the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit] flow where an access token +is sent immediately after successful authentication with {{book.project.name}}. This may have better performance than standard flow, as there is no additional +request to exchange the code for tokens, but it has implications when the access token expires. -To enable implicit flow, you need to enable the `Implicit Flow Enabled` flag for the client in the Keycloak admin console. -You also need to pass the parameter `flow` with value `implicit` to `init` method. -An example is below: +However, sending the access token in the URL fragment can be a security vulnerability. For example the token could be leaked through web server logs and or +browser history. -[source] +To enable implicit flow, you need to enable the `Implicit Flow Enabled` flag for the client in the {{book.project.name}} Administration Console. +You also need to pass the parameter `flow` with value `implicit` to `init` method: + +[source,javascript] +---- +keycloak.init({ flow: 'implicit' }) ---- -keycloak.init({ flow: 'implicit' }) ----- -Note that with implicit flow, you are not given a refresh token after authentication. +One thing to note is that only an access token is provided and there is no refresh token. This means that once the access token has expired the application +has to do the redirect to the {{book.project.name}} again to obtain a new access token. -This makes it harder for your application to periodically update the access token in background (without browser redirection). It's recommended that you implement an `onTokenExpired` callback method on the keycloak object, so you are notified after the token is expired (For example you can call keycloak.login, which will redirect browser to Keycloak login screen and it will immediately redirect you back if the SSO session is still valid and the user is still logged. -However, make sure to save the application state before performing a redirect.) - -Keycloak also has support for http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth[OpenID Connect Hybrid flow]. +{{book.project.name}} also supports the http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth[Hybrid] flow. This requires the client to have both the `Standard Flow Enabled` and `Implicit Flow Enabled` flags enabled in the admin console. -The Keycloak server will then send both the code and tokens to your application. +The {{book.project.name}} server will then send both the code and tokens to your application. The access token can be used immediately while the code can be exchanged for access and refresh tokens. Similar to the implicit flow, the hybrid flow is good for performance because the access token is available immediately. -But, the token is still sent in the URL, and security risks might still apply. -However, one advantage over the implicit flow is that a refresh token is made available to the application (after the code-to-token request is finished). +But, the token is still sent in the URL, and the security vulnerability mentioned earlier may still apply. -For hybrid flow, you need to pass the parameter `flow` with value `hybrid` to `init` method. +One advantage in the Hybrid flow is that the refresh token is made available to the application. + +For the Hybrid flow, you need to pass the parameter `flow` with value `hybrid` to the `init` method: + +[source,javascript] +---- +keycloak.init({ flow: 'hybrid' }) +---- == Older browsers The JavaScript adapter depends on Base64 (window.btoa and window.atob) and HTML5 History API. -If you need to support browsers that don't provide those (for example IE9) you'll need to add polyfillers. +If you need to support browsers that don't have these available (for example IE9) you need to add polyfillers. + Example polyfill libraries: * https://github.com/davidchambers/Base64.js @@ -190,20 +184,48 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' === Properties -* authenticated - true if the user is authenticated -* Authorization -* tokenParsed - the parsed token -* subject - the user id -* idToken - the id token if claims is enabled for the application, null otherwise -* idTokenParsed - the parsed id token -* realmAccess - the realm roles associated with the token -* resourceAccess - the resource roles assocaited with the token -* refreshToken - the base64 encoded token that can be used to retrieve a new token -* refreshTokenParsed - the parsed refresh token -* timeSkew - estimated skew between local time and Keycloak server in seconds -* fragment -* Implicit flow -* flow +authenticated:: + Is `true` if the user is authenticated, `false` otherwise. + +token:: + The base64 encoded token that can be sent in the `Authorization` header in requests to services. + +tokenParsed:: + The parsed token as a JavaScript object. + +subject:: + The user id. + +idToken:: + The base64 encoded ID token. + +idTokenParsed:: + The parsed id token as a JavaScript object. + +realmAccess:: + The realm roles associated with the token. + +resourceAccess:: + The resource roles assocaited with the token. + +refreshToken:: + The base64 encoded refresh token that can be used to retrieve a new token. + +refreshTokenParsed:: + The parsed refresh token as a JavaScript object. + +timeSkew:: + The estimated time difference between the browser time and the {{book.project.name}} server in seconds. This value is just an estimation, but is accurate + enough when determining if a token is expired or not. + +responseMode:: + Response mode passed in init (default value is fragment). + +flow:: + Flow passed in init. + +responseType:: + Response type sent to {{book.project.name}} with login requests. This is determined based on the flow value used during initialization, but can be overridden by setting this value. === Methods @@ -213,100 +235,92 @@ Called to initialize the adapter. Options is an Object, where: -* onLoad - specifies an action to do on load, can be either 'login-required' or 'check-sso' -* token - set an initial value for the token -* refreshToken - set an initial value for the refresh token -* idToken - set an initial value for the id token (only together with token or refreshToken) -* timeSkew - set an initial value for skew between local time and Keycloak server in seconds (only together with token or refreshToken) -* checkLoginIframe - set to enable/disable monitoring login state (default is true) -* checkLoginIframeInterval - set the interval to check login state (default is 5 seconds) -* query -+`fragment` -+`fragment` -+`query` -* standard -+`implicit` -+`hybrid`<<_javascript_implicit_flow,+Implicit flow>> - +* onLoad - Specifies an action to do on load. Supported values are 'login-required' or 'check-sso'. +* token - Set an initial value for the token. +* refreshToken - Set an initial value for the refresh token. +* idToken - Set an initial value for the id token (only together with token or refreshToken). +* timeSkew - Set an initial value for skew between local time and Keycloak server in seconds (only together with token or refreshToken). +* checkLoginIframe - Set to enable/disable monitoring login state (default is true). +* checkLoginIframeInterval - Set the interval to check login state (default is 5 seconds). +* responseMode - Set the OpenID Connect response mode send to Keycloak server at login request. Valid values are query or fragment . Default value is fragment, which means that after successful authentication will Keycloak redirect to javascript application with OpenID Connect parameters added in URL fragment. This is generally safer and recommended over query. +* flow - Set the OpenID Connect flow. Valid values are standard, implicit or hybrid. Returns promise to set functions to be invoked on success or error. ==== login(options) -Redirects to login form on (options is an optional object with redirectUri and/or prompt fields) +Redirects to login form on (options is an optional object with redirectUri and/or prompt fields). Options is an Object, where: -* redirectUri - specifies the uri to redirect to after login -* prompt - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed) -* loginHint - used to pre-fill the username/email field on the login form -* action - if value is 'register' then user is redirected to registration page, otherwise to login page -* locale - specifies the desired locale for the UI +* redirectUri - Specifies the uri to redirect to after login. +* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed). +* loginHint - Used to pre-fill the username/email field on the login form. +* action - If value is 'register' then user is redirected to registration page, otherwise to login page. +* locale - Specifies the desired locale for the UI. ==== createLoginUrl(options) -Returns the url to login form on (options is an optional object with redirectUri and/or prompt fields) +Returns the URL to login form on (options is an optional object with redirectUri and/or prompt fields). Options is an Object, where: -* redirectUri - specifies the uri to redirect to after login -* prompt - can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed) +* redirectUri - Specifies the uri to redirect to after login. +* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed). ==== logout(options) -Redirects to logout +Redirects to logout. Options is an Object, where: -* redirectUri - specifies the uri to redirect to after logout +* redirectUri - Specifies the uri to redirect to after logout. ==== createLogoutUrl(options) -Returns logout out +Returns the URL to logout the user. Options is an Object, where: -* redirectUri - specifies the uri to redirect to after logout +* redirectUri - Specifies the uri to redirect to after logout. ==== register(options) -Redirects to registration form. -It's a shortcut for doing login with option action = 'register' +Redirects to registration form. Shortcut for login with option action = 'register' -Options are same as login method but 'action' is overwritten to 'register' +Options are same as login method but 'action' is set to 'register' ==== createRegisterUrl(options) -Returns the url to registration page. -It's a shortcut for doing createRegisterUrl with option action = 'register' +Returns the url to registration page. Shortcut for createLoginUrl with option action = 'register' -Options are same as createLoginUrl method but 'action' is overwritten to 'register' +Options are same as createLoginUrl method but 'action' is set to 'register' ==== accountManagement() -Redirects to account management +Redirects to the Account Management Console. ==== createAccountUrl() -Returns the url to account management +Returns the URL to the Account Management Console. ==== hasRealmRole(role) -Returns true if the token has the given realm role +Returns true if the token has the given realm role. ==== hasResourceRole(role, resource) -Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used) +Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used). ==== loadUserProfile() -Loads the users profile +Loads the users profile. Returns promise to set functions to be invoked on success or error. ==== isTokenExpired(minValidity) -Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used) +Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used). ==== updateToken(minValidity) @@ -316,48 +330,42 @@ If the session status iframe is enabled, the session status is also checked. Returns promise to set functions that can be invoked if the token is still valid, or if the token is no longer valid. For example: -[source] +[source,javascript] ---- - keycloak.updateToken(5).success(function(refreshed) { if (refreshed) { - alert('token was successfully refreshed'); + alert('Token was successfully refreshed'); } else { - alert('token is still valid'); + alert('Token is still valid'); } }).error(function() { - alert('failed to refresh the token, or the session has expired'); + alert('Failed to refresh the token, or the session has expired'); }); ---- ==== clearToken() Clear authentication state, including tokens. -This can be useful if application has detected the session has expired, for example if updating token fails. +This can be useful if application has detected the session was expired, for example if updating token fails. + Invoking this results in onAuthLogout callback listener being invoked. -[source] ----- - -keycloak.updateToken(5).error(function() { - keycloak.clearToken(); -}); ----- - === Callback Events The adapter supports setting callback listeners for certain events. + For example: [source] ---- - keycloak.onAuthSuccess = function() { alert('authenticated'); } ----- +---- -* onReady(authenticated) - called when the adapter is initialized -* onAuthSuccess - called when a user is successfully authenticated -* onAuthError - called if there was an error during authentication -* onAuthRefreshSuccess - called when the token is refreshed -* onAuthRefreshError - called if there was an error while trying to refresh the token -* onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode) -* onTokenExpired - called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen +The available events are: + +* onReady(authenticated) - Called when the adapter is initialized. +* onAuthSuccess - Called when a user is successfully authenticated. +* onAuthError - Called if there was an error during authentication. +* onAuthRefreshSuccess - Called when the token is refreshed. +* onAuthRefreshError - Called if there was an error while trying to refresh the token. +* onAuthLogout - Called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode). +* onTokenExpired - Called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen. From a25aed13ed39ddd937aca0a52a5ceab68ceda95b Mon Sep 17 00:00:00 2001 From: --add Date: Fri, 3 Jun 2016 18:16:22 +0530 Subject: [PATCH 028/194] resolving compilation errors for the downstream build system --- topics/oidc/java/fuse-adapter.adoc | 6 ++++-- topics/oidc/java/jboss-adapter.adoc | 4 ++-- topics/oidc/java/jetty9-adapter.adoc | 4 ++-- topics/oidc/java/servlet-filter-adapter.adoc | 4 ++-- topics/oidc/java/tomcat-adapter.adoc | 4 ++-- topics/saml/java/jetty-adapter.adoc | 2 +- topics/saml/java/jetty-adapter/jetty9_installation.adoc | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index c8833f4154..2eb700964b 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -80,7 +80,9 @@ It's recommended to use your own Jetty engine for your apps (similarly like `cxf Keycloak mainly addresses usecases for authentication of web applications, however if your admin services (like fuse admin console) are protected with Keycloak, it may be good to protect non-web services like SSH with Keycloak credentials too. It's possible to do it by using JAAS login module, which -allows to remotely connect to Keycloak and verify credentials based on <<_direct_access_grants,Direct Access Grants>> . +allows to remotely connect to Keycloak and verify credentials based on + +// <<_direct_access_grants,Direct Access Grants>> . Example steps for enable SSH authentication require changing the configuration of `sshRealm` in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`, then adding file `$FUSE_HOME/etc/keycloak-direct-access.json` (this is default location, which can be changed) and install the needed feature `keycloak-jaas`. It's described in details @@ -99,4 +101,4 @@ It's described in details in the README file of Fuse example, which in example d ===== Secure Fuse admin console -Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak. \ No newline at end of file +Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak. diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index f4d861fe13..cd1d7b0ce3 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -174,7 +174,7 @@ public class CustomerService { This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. @@ -259,7 +259,7 @@ This metadata is instead defined within server configuration (i.e. `standalone.x The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. -The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_adapter_config,general adapter configuration>>. +The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_saml-general-config,general adapter configuration>>. The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index d1972e4da9..27021f3f0b 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -58,7 +58,7 @@ This is a Jetty specific config file and you must define a Keycloak specific aut ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. You will have to define all adapter settings within the `jetty-web.xml` file as described below. @@ -145,4 +145,4 @@ Here's an example: user ----- \ No newline at end of file +---- diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 3f8c809b68..d3f161eca1 100755 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -31,7 +31,7 @@ There's no way standard way to invalidate an HTTP session based on a session id. ---- In the snippet above there are two url-patterns. - `/protected/*` are the files we want protected, while the `/keycloak/*` url-pattern handles callbacks from the {{book.project.title}} server. + */protected/** are the files we want protected, while the */keycloak/** url-pattern handles callbacks from the {{book.project.title}} server. Note that you should configure your client in the {{book.project.title}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. @@ -49,4 +49,4 @@ To use this filter, include this maven artifact in your WAR poms: keycloak-servlet-filter-adapter &project.version; ----- \ No newline at end of file +---- diff --git a/topics/oidc/java/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc index 06e93f794e..6b51783b52 100755 --- a/topics/oidc/java/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -45,7 +45,7 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: @@ -84,4 +84,4 @@ Here's an example: user ----- \ No newline at end of file +---- diff --git a/topics/saml/java/jetty-adapter.adoc b/topics/saml/java/jetty-adapter.adoc index 6a85ed2798..78ca40cc3f 100644 --- a/topics/saml/java/jetty-adapter.adoc +++ b/topics/saml/java/jetty-adapter.adoc @@ -1,4 +1,4 @@ - +[[_jetty_adapter]] ==== Jetty SAML Adapters To be able to secure WAR apps deployed on Jetty you must install the {{book.project.name}} Jetty 9.x or 8.x SAML adapter into your Jetty installation. diff --git a/topics/saml/java/jetty-adapter/jetty9_installation.adoc b/topics/saml/java/jetty-adapter/jetty9_installation.adoc index 2554091131..515c489aa7 100644 --- a/topics/saml/java/jetty-adapter/jetty9_installation.adoc +++ b/topics/saml/java/jetty-adapter/jetty9_installation.adoc @@ -1,4 +1,4 @@ -[[_jetty9_adapter_installation]] +[[_jetty_adapter_installation]] ===== Jetty 9 Adapter Installation From bda4e117f990a4a4aa4b5d6600678f7b857c1d28 Mon Sep 17 00:00:00 2001 From: --add Date: Fri, 3 Jun 2016 18:55:19 +0530 Subject: [PATCH 029/194] corrections as per review comments --- topics/oidc/java/jboss-adapter.adoc | 4 ++-- topics/oidc/java/jetty9-adapter.adoc | 5 ++--- topics/oidc/java/servlet-filter-adapter.adoc | 2 +- topics/oidc/java/tomcat-adapter.adoc | 2 +- topics/oidc/oidc-overview.adoc | 4 +++- topics/saml/java/jetty-adapter/jetty9_installation.adoc | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index cd1d7b0ce3..00bcc5c3a5 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -174,7 +174,7 @@ public class CustomerService { This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. +The format of this config file is describe in the <<_java_adapter_config,general adapter configuration>> section. Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. @@ -259,7 +259,7 @@ This metadata is instead defined within server configuration (i.e. `standalone.x The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. -The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_saml-general-config,general adapter configuration>>. +The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_java_adapter_config,general adapter configuration>>. The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index 27021f3f0b..1f3017e726 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -1,4 +1,3 @@ - [[_jetty9_adapter]] === Jetty 9.x Adapters @@ -6,7 +5,7 @@ Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will ha You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. -[[_jetty9_adapter_installation]] +[[_jetty-9_adapter_installation]] ==== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. @@ -58,7 +57,7 @@ This is a Jetty specific config file and you must define a Keycloak specific aut ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. +The format of this config file is describe in the <<_java_adapter_config,general adapter configuration>> section. WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. You will have to define all adapter settings within the `jetty-web.xml` file as described below. diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index d3f161eca1..69c52a1daf 100755 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -31,7 +31,7 @@ There's no way standard way to invalidate an HTTP session based on a session id. ---- In the snippet above there are two url-patterns. - */protected/** are the files we want protected, while the */keycloak/** url-pattern handles callbacks from the {{book.project.title}} server. + _/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {{book.project.title}} server. Note that you should configure your client in the {{book.project.title}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. diff --git a/topics/oidc/java/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc index 6b51783b52..1b7f6ca7de 100755 --- a/topics/oidc/java/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -45,7 +45,7 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_saml-general-config,general adapter configuration>> section. +The format of this config file is describe in the <<_java_adapter_config,general adapter configuration>> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: diff --git a/topics/oidc/oidc-overview.adoc b/topics/oidc/oidc-overview.adoc index 8436712c35..bb5fdda906 100644 --- a/topics/oidc/oidc-overview.adoc +++ b/topics/oidc/oidc-overview.adoc @@ -1,4 +1,6 @@ == OpenID Connect This section describes how you can secure applications and services with OpenID Connect using either {{book.project.name}} adapters or generic OpenID Connect -Resource Provider libraries. \ No newline at end of file +Resource Provider libraries. + +// TODO: Update the cross-reference <<_direct_access_grants,Direct Access Grants>> in the topic /oidc/java/fuse-adapter.adoc diff --git a/topics/saml/java/jetty-adapter/jetty9_installation.adoc b/topics/saml/java/jetty-adapter/jetty9_installation.adoc index 515c489aa7..2554091131 100644 --- a/topics/saml/java/jetty-adapter/jetty9_installation.adoc +++ b/topics/saml/java/jetty-adapter/jetty9_installation.adoc @@ -1,4 +1,4 @@ -[[_jetty_adapter_installation]] +[[_jetty9_adapter_installation]] ===== Jetty 9 Adapter Installation From 515f3c9002a2ce2342d7cd7d1bbdb65c34d5ba37 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 3 Jun 2016 15:09:26 +0200 Subject: [PATCH 030/194] More Fuse adapter documentation. Remove references to Apache Karaf --- SUMMARY.adoc | 8 +- topics/oidc/java/fuse-adapter.adoc | 98 ++-------------- topics/oidc/java/fuse/camel.adoc | 95 +++++++++++++++ topics/oidc/java/fuse/classic-war.adoc | 82 +++++++++++++ topics/oidc/java/fuse/cxf-builtin.adoc | 98 ++++++++++++++++ topics/oidc/java/fuse/cxf-separate.adoc | 111 ++++++++++++++++++ topics/oidc/java/fuse/fuse-admin.adoc | 87 ++++++++++++++ topics/oidc/java/fuse/servlet-whiteboard.adoc | 76 ++++++++++++ 8 files changed, 568 insertions(+), 87 deletions(-) create mode 100644 topics/oidc/java/fuse/camel.adoc create mode 100644 topics/oidc/java/fuse/classic-war.adoc create mode 100644 topics/oidc/java/fuse/cxf-builtin.adoc create mode 100644 topics/oidc/java/fuse/cxf-separate.adoc create mode 100644 topics/oidc/java/fuse/fuse-admin.adoc create mode 100644 topics/oidc/java/fuse/servlet-whiteboard.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index cc60f2abca..1ef570a8d9 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -10,7 +10,13 @@ .. link:topics/oidc/java/java-adapters.adoc[Java Adapters] ... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] ... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] - ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse and Apache Karaf Adapter] + ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] + .... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] + .... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] + .... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] + .... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] + .... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] + .... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] {% if book.community %} ... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] ... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index c8833f4154..f00dcefb33 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -1,17 +1,17 @@ [[_fuse_adapter]] -=== JBoss Fuse and Apache Karaf Adapter +=== JBoss Fuse Adapter NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported -Currently Keycloak supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] or http://karaf.apache.org/[Apache Karaf] . -It leverages <<_jetty8_adapter,Jetty 8 adapter>> as both JBoss Fuse 6.2 and Apache Karaf 3 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] +Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] . +It leverages <> as both JBoss Fuse 6.2 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. -What is supported for Fuse/Karaf is: +What is supported for Fuse is: -* Security for classic WAR applications deployed on Fuse/Karaf with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender]. -* Security for servlets deployed on Fuse/Karaf as OSGI services with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender]. +* Security for classic WAR applications deployed on Fuse with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender]. +* Security for servlets deployed on Fuse as OSGI services with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender]. * Security for http://camel.apache.org/[Apache Camel] Jetty endpoints running with http://camel.apache.org/jetty.html[Camel Jetty] component. * Security for http://cxf.apache.org/[Apache CXF] endpoints running on their own separate http://cxf.apache.org/docs/jetty-configuration.html[Jetty engine]. * Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet. @@ -19,84 +19,10 @@ What is supported for Fuse/Karaf is: ==== How to secure your web applications inside Fuse -The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory `fuse` . Most of the steps should be understandable from testing and +Basically all mentioned web applications require to inject {{book.project.name}} Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different +according to application type. The details are described in individual sub-chapters. + +{% if book.community %} +The best place to start is look at Fuse demo bundled as part of {{book.project.name}} examples in directory `fuse` . Most of the steps should be understandable from testing and understanding the demo. - -Basically all mentioned web applications require to inject Keycloak Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different -according to application type. - - -===== Classic WAR application - -The needed steps are: - -* Declare needed constraints in `/WEB-INF/web.xml` -* Add `jetty-web.xml` file with the authenticator to `/WEB-INF/jetty-web.xml` and add `/WEB-INF/keycloak.json` with your Keycloak configuration -* Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in MANIFEST.MF file in header `Import-Package`. It's -recommended to use maven-bundle-plugin similarly like Fuse examples are doing, but note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package -as it's not used by application or Blueprint or Spring descriptor, but it's used just in jetty-web.xml file. - -Take a look at `customer-portal-app` from fuse example for inspiration. - -===== Servlet web application deployed by pax-whiteboard-extender - -The needed steps are: - -* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. -Example `product-portal-app` declares this in `OSGI-INF/blueprint/blueprint.xml` . Note that your servlet needs to depend on it. -* Steps 2,3 are same like for classic WAR - -Take a look at `product-portal-app` for inspiration. - -===== Apache camel application - -You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] endpoint by adding securityHandler with `KeycloakJettyAuthenticator` and -proper security constraints injected. Take a look at `OSGI-INF/blueprint/blueprint.xml` configuration in `camel` application on example of how it can be done in details. - -===== Apache CXF endpoint - -It's recommended to run your CXF endpoints secured by Keycloak on separate Jetty engine. You need to add `META-INF/spring/beans.xml` to your application -and then declare `httpj:engine-factory` with Jetty SecurityHandler with injected `KeycloakJettyAuthenticator` inside. - -Fore more details, take a look at example application `cxf-ws` from Keycloak Fuse demo, which is using separate endpoint on -http://localhost:8282 . All the important configuration inside this application is declared in `META-INF/spring/beans.xml` . - -===== Builtin CXF web applications - -Some services automatically come with deployed servlets on startup. One of such examples is CXF servlet running on -http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which Keycloak is currently using, -is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by Keycloak. -You can see the `OSGI-INF/blueprint/blueprint.xml` inside `cxf-jaxrs` example, which adds JAX-RS `customerservice` endpoint and more importantly, it secures whole `/cxf` context. - -As a side effect, all other CXF services running on default CXF HTTP destination will be secured too. Once you uninstall feature `keycloak-fuse-6.2-example`, the -original unsecured servlet on `/cxf` context is deployed back and hence context will become unsecured again. - -It's recommended to use your own Jetty engine for your apps (similarly like `cxf-jaxws` application is doing). - - -==== How to secure Fuse admin services - -===== SSH authentication to Fuse terminal with Keycloak credentials - -Keycloak mainly addresses usecases for authentication of web applications, however if your admin services (like fuse admin console) are protected -with Keycloak, it may be good to protect non-web services like SSH with Keycloak credentials too. It's possible to do it by using JAAS login module, which -allows to remotely connect to Keycloak and verify credentials based on <<_direct_access_grants,Direct Access Grants>> . - -Example steps for enable SSH authentication require changing the configuration of `sshRealm` in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`, then adding -file `$FUSE_HOME/etc/keycloak-direct-access.json` (this is default location, which can be changed) and install the needed feature `keycloak-jaas`. It's described in details -in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . - - -===== JMX authentication with Keycloak credentials - -This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may -be better to use just hawt.io/jolokia as jolokia agent is installed in http://hawt.io by default. - -You need to configure `jmxRealm` in `$FUSE_HOME/etc/org.apache.karaf.management.cfg`, then adding file `$FUSE_HOME/etc/keycloak-direct-access.json` -(this is default location, which can be changed) and install the needed feature `keycloak-jaas`. -It's described in details in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . - - -===== Secure Fuse admin console - -Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak. \ No newline at end of file +{% endif %} \ No newline at end of file diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc new file mode 100644 index 0000000000..b79229de6c --- /dev/null +++ b/topics/oidc/java/fuse/camel.adoc @@ -0,0 +1,95 @@ + +[[_fuse_adapter_camel]] +==== Apache Camel Application + +* You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] component by adding securityHandler with `KeycloakJettyAuthenticator` and +proper security constraints injected. You can add file `OSGI-INF/blueprint/blueprint.xml` into your camel application with the configuration similar to below. +The roles, security constraint mappings and {{book.project.name}} adapter configuration may be a bit different according to your environment and needs: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + + +* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: +[source] +---- +javax.servlet;version="[3,4)", +javax.servlet.http;version="[3,4)", +org.apache.camel.*, +org.apache.camel;version="[2.13,3)", +org.eclipse.jetty.security;version="[8,10)", +org.eclipse.jetty.server.nio;version="[8,10)", +org.eclipse.jetty.util.security;version="[8,10)", +org.keycloak.*;version="{{book.project.version}}", +org.osgi.service.blueprint, +org.osgi.service.blueprint.container, +org.osgi.service.event, +---- + diff --git a/topics/oidc/java/fuse/classic-war.adoc b/topics/oidc/java/fuse/classic-war.adoc new file mode 100644 index 0000000000..7ff0bfc98e --- /dev/null +++ b/topics/oidc/java/fuse/classic-war.adoc @@ -0,0 +1,82 @@ + +[[_fuse_adapter_classic_war]] +==== Secure Classic WAR application + +The needed steps to secure your WAR are: + +* Declare needed security constraints in `/WEB-INF/web.xml` . You also need to declare login-config and all the roles inside security-role. +The example configuration can look like this: + +[source,xml] +---- + + + + customer-portal + + + index.html + + + + + Customers + /customers/* + + + user + + + + + BASIC + does-not-matter + + + + admin + + + user + + +---- + +* Add `jetty-web.xml` file with the authenticator to `/WEB-INF/jetty-web.xml` . Typically it will look like this: + +[source,xml] +---- + + + + + + + + + + +---- + +* Add `/WEB-INF/keycloak.json` with your {{book.project.name}} configuration. The format of this config file is described +in the <> section. + +* Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in `META-INF/MANIFEST.MF` file in header `Import-Package`. It's +recommended to use `maven-bundle-plugin` in your project to properly generate OSGI headers in manifest. +Note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package +as it's not used by application or Blueprint or Spring descriptor, but it's used just in `jetty-web.xml` file. So list of the packages to import may look like this: + +[source] +---- +org.keycloak.adapters.jetty;version="{{book.project.version}}", +org.keycloak.adapters;version="{{book.project.version}}", +org.keycloak.constants;version="{{book.project.version}}", +org.keycloak.util;version="{{book.project.version}}", +org.keycloak.*;version="{{book.project.version}}", +*;resolution:=optional +---- + diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/topics/oidc/java/fuse/cxf-builtin.adoc new file mode 100644 index 0000000000..ff4211a8c5 --- /dev/null +++ b/topics/oidc/java/fuse/cxf-builtin.adoc @@ -0,0 +1,98 @@ + +[[_fuse_adapter_cxf_builtin]] +==== Secure Apache CXF Endpoint on default Jetty Engine + +Some services automatically come with deployed servlets on startup. One of such services is CXF servlet running on +http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which {{book.project.name}} is currently using, +is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by {{book.project.name}}. +This is how configuration file `OSGI-INF/blueprint/blueprint.xml` inside your application may look like. Note it adds JAX-RS `customerservice` endpoint, +which is endpoint specific to your application, but more importantly, it secures whole `/cxf` context. + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + user + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +As a side effect, all other CXF services running on default CXF HTTP destination will be secured too. Similarly when the application is undeployed, then +whole `/cxf` context will become unsecured too. For this reason, it's recommended to use your own Jetty engine for your apps like +described in <> as then you have more +control over security for each application individually. + +* You may need to have directory `WEB-INF` inside your project (even if your project is not web application) and create files `/WEB-INF/jetty-web.xml` and + `/WEB-INF/keycloak.json` in similar way like it's in <>. + Note you don't need `web.xml` as the security-constrains are declared in blueprint configuration file. + + +* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: +[source] +---- +META-INF.cxf;version="[2.7,3.2)", +META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional, +org.apache.cxf.transport.http;version="[2.7,3.2)", +org.apache.cxf.*;version="[2.7,3.2)", +com.fasterxml.jackson.jaxrs.json;version="[2.5,3)", +org.eclipse.jetty.security;version="[8,10)", +org.eclipse.jetty.util.security;version="[8,10)", +org.keycloak.*;version="{{book.project.version}}", +org.keycloak.adapters.jetty;version="{{book.project.version}}", +*;resolution:=optional +---- diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/topics/oidc/java/fuse/cxf-separate.adoc new file mode 100644 index 0000000000..86e12b76a1 --- /dev/null +++ b/topics/oidc/java/fuse/cxf-separate.adoc @@ -0,0 +1,111 @@ + +[[_fuse_adapter_cxf_separate]] +==== Secure Apache CXF Endpoint on separate Jetty + +It's recommended to run your CXF endpoints secured by {{book.project.name}} on separate Jetty engine. This is the setup described in this section. + +* You need to add `META-INF/spring/beans.xml` to your application and then declare `httpj:engine-factory` with Jetty SecurityHandler with +injected `KeycloakJettyAuthenticator` inside. The configuration may look like this for CXF JAX-WS application: + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + user + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + +---- + +* For the CXF JAX-RS application, the only difference might be in the configuration of the endpoint dependent on engine-factory: + +[source,xml] +---- + + + + + +---- + + +* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: +[source] +---- +META-INF.cxf;version="[2.7,3.2)", +META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional, +org.apache.cxf.bus;version="[2.7,3.2)", +org.apache.cxf.bus.spring;version="[2.7,3.2)", +org.apache.cxf.bus.resource;version="[2.7,3.2)", +org.apache.cxf.transport.http;version="[2.7,3.2)", +org.apache.cxf.*;version="[2.7,3.2)", +org.springframework.beans.factory.config, +org.eclipse.jetty.security;version="[8,10)", +org.eclipse.jetty.util.security;version="[8,10)", +org.keycloak.*;version="{{book.project.version}}" +---- \ No newline at end of file diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc new file mode 100644 index 0000000000..33223d587a --- /dev/null +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -0,0 +1,87 @@ + +[[_fuse_adapter_admin]] +==== Secure Fuse Admin Services + +===== SSH authentication to Fuse terminal + +{{book.project.name}} mainly addresses usecases for authentication of web applications, however if your other web services and applications are protected +with {{book.project.name}}, it may be good to protect non-web admin services like SSH with {{book.project.name}} credentials too. It's possible to do it +by using JAAS login module, which allows to remotely connect to {{book.project.name}} and verify credentials based on +<> . + +Example steps for enable SSH authentication: + +* In {{book.project.name}} you need to create client (assume it's called `ssh-jmx-admin-client`), which will be used for SSH authentication. +This client needs to have switch `Direct grant enabled` to true. + +* You need to update/specify this property in file `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`: + +[source] +---- +sshRealm=keycloak +---- + +* Add file `$FUSE_HOME/etc/keycloak-direct-access.json` with the content similar to this (change based on your environment and {{book.project.name}} client settings): +[source,json] +---- +{ + "realm": "demo", + "resource": "ssh-jmx-admin-client", + "realm-public-key": "MIGfMA...", + "ssl-required" : "external", + "auth-server-url" : "http://localhost:8080/auth", + "credentials": { + "secret": "password" + } +} +---- +This file contains configuration of the client application, which is used by JAAS DirectAccessGrantsLoginModule from `keycloak` JAAS realm for SSH authentication. + +* Start Fuse and install `keycloak` JAAS realm into Fuse. This could be done easily by installing `keycloak-jaas` feature, which has JAAS realm predefined +(you are able to override it by using your own `keycloak` JAAS realm with higher ranking). Use those commands in Fuse terminal: + +``` +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features +features:install keycloak-jaas +``` + +* Now let's type this from your terminal to login via SSH as `admin` user: + +``` +ssh -o PubkeyAuthentication=no -p 8101 admin@localhost +``` + +And login with password `password` . Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` + + +===== JMX authentication + +This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may +be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default. + +* In file `$FUSE_HOME/etc/org.apache.karaf.management.cfg` you can change this property: + +[source] +---- +jmxRealm=keycloak +---- + +* You need `keycloak-jaas` feature and file `$FUSE_HOME/etc/keycloak-direct-access.json` as described in SSH section above. + +* In jconsole you can fill URL like: + +[source] +---- +service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root +---- + +and credentials: admin/password (based on the user with admin privileges according to your environment) + +Note again that users without `admin` role are not able to login as they are not authorized. However users with access to Hawt.io admin console +may be still able to access MBeans remotely via HTTP (Hawtio). So make sure to protect Hawt.io web console with same roles like JMX through RMI to +really protect JMX mbeans. + + +===== Secure Fuse admin console + +Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with {{book.project.name}}. \ No newline at end of file diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc new file mode 100644 index 0000000000..37c0893803 --- /dev/null +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -0,0 +1,76 @@ + +[[_fuse_adapter_servlet_whiteboard]] +==== Secure Servlet deployed as OSGI service + +This is useful for the case, when you have sevlet class inside your OSGI bundle project, which is not deployed as classic WAR. Fuse uses +https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender] for deploy such servlet as web application. + +The needed steps to secure your servlet with {{book.project.name}} are: + +* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. + You need to declare such service in `OSGI-INF/blueprint/blueprint.xml` inside your application. Note that your servlet needs to depend on it. + The example configuration can look like this: +[source,xml] +---- + + + + + + + + + + + user + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +* You may need to have directory `WEB-INF` inside your project (even if your project is not web application) and create files `/WEB-INF/jetty-web.xml` and +`/WEB-INF/keycloak.json` in similar way like it's in <>. +Note you don't need `web.xml` as the security-constrains are declared in blueprint configuration file. + +* The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain at least those imports: +[source] +---- +org.keycloak.adapters.jetty;version="{{book.project.version}}", +org.keycloak.adapters;version="{{book.project.version}}", +org.keycloak.constants;version="{{book.project.version}}", +org.keycloak.util;version="{{book.project.version}}", +org.keycloak.*;version="{{book.project.version}}", +*;resolution:=optional +---- From e9241f8c1dd86bf8a59edfe515694b01e74caa1e Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 6 Jun 2016 10:22:23 +0200 Subject: [PATCH 031/194] Fix #11: Document integration with Apache module mod_auth_openidc --- SUMMARY.adoc | 1 + topics/oidc/mod-auth-openidc.adoc | 49 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 topics/oidc/mod-auth-openidc.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 1ef570a8d9..d230ec7d6b 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -35,6 +35,7 @@ .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] + ... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] . link:topics/saml/saml-overview.adoc[SAML] .. link:topics/saml/java/java-adapters.adoc[Java Adapters] diff --git a/topics/oidc/mod-auth-openidc.adoc b/topics/oidc/mod-auth-openidc.adoc new file mode 100644 index 0000000000..25e03be59a --- /dev/null +++ b/topics/oidc/mod-auth-openidc.adoc @@ -0,0 +1,49 @@ +[[_mod_auth_openidc]] +=== mod_auth_openidc Apache HTTPD Module + +The https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc] is an Apache HTTP plugin for OpenID Connect. If your language/environment supports using Apache HTTPD +as a proxy, then you can use _mod_auth_openidc_ to secure your web application with OpenID Connect. Configuration of this module +is beyond the scope of this document. Please see the _mod_auth_openidc_ Github repo for more details on configuration. + +To configure _mod_auth_openidc_ you'll need + +* The client_id. +* The client_secret. +* The redirect_uri to your application. +* The Keycloak openid-configuration url +* _mod_auth_openidc_ specific Apache HTTPD module config. + +An example configuration would look like the following. + +[source,xml] +---- +LoadModule auth_openidc_module modules/mod_auth_openidc.so + +ServerName ${HOSTIP} + + + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + #this is required by mod_auth_openidc + OIDCCryptoPassphrase a-random-secret-used-by-apache-oidc-and-balancer + + OIDCProviderMetadataURL ${KC_ADDR}/auth/realms/${KC_REALM}/.well-known/openid-configuration + + OIDCClientID ${CLIENT_ID} + OIDCClientSecret ${CLIENT_SECRET} + OIDCRedirectURI http://${HOSTIP}/${CLIENT_APP_NAME}/redirect_uri + + # maps the prefered_username claim to the REMOTE_USER environment variable + OIDCRemoteUserClaim preferred_username + + + AuthType openid-connect + Require valid-user + + +---- + +Further information on how to configure mod_auth_openidc can be found on the https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc] +project page. \ No newline at end of file From d16bf618465415989e0e12c539bdd426e3994b90 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 11:00:44 +0200 Subject: [PATCH 032/194] Added generic OIDC chapter --- topics/client-registration.adoc | 1 + topics/oidc/oidc-generic.adoc | 175 +++++++++++++++++++++++++++----- 2 files changed, 152 insertions(+), 24 deletions(-) diff --git a/topics/client-registration.adoc b/topics/client-registration.adoc index 304436ebd3..cd28d31058 100644 --- a/topics/client-registration.adoc +++ b/topics/client-registration.adoc @@ -1,3 +1,4 @@ +[[_client_registration]] == Client Registration In order for an application or service to utilize {{book.project.name}} it has to register a client in {{book.project.name}}. diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index d625f9a539..4c71cdade3 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -1,47 +1,174 @@ == Other OpenID Connect libraries -OAuth2 https://tools.ietf.org/html/rfc6749 -OpenID Connect http://openid.net/connect/ - +{{book.project.name}} can be secured by supplied adapters that usually are easier to use and provide better integration with {{book.project.name}}. However, +if there is no adapter available for your programming language, framework or platform you may opt to use a generic OpenID Connect Resource Provider (RP) library +instead. This chapter describes details specific to {{book.project.name}} and doesn't go into low-level details of the protocols. For more details refer to the +http://openid.net/connect/[OpenID Connect specifications] and https://tools.ietf.org/html/rfc6749[OAuth2 specification]. === Endpoints -TODO +The most important endpoint to know is the `well-known` configuration endpoint. It lists endpoints and other configuration options relevant to the OpenID +Connect implementation in {{book.project.name}}. The endpoint is: + +.... +/realms/REALM-NAME/.well-known/openid-configuration +.... + +To get the full URL add the base URL for {{book.project.name}} and replace `REALM-NAME` with the name of your realm. For example: + +http://localhost:8080/auth/realms/master/.well-known/openid-configuration + +Some RP libraries will retrieve all required endpoints from this endpoint, but for others you may need to list the endpoints individually. + +==== Authorization Endpoint +.... +/realms/master/protocol/openid-connect/auth +.... + +Performs authentication of the end-user. This is done by redirecting user agent to this endpoint. + +For more details see http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[Authorization Endpoint] section in OpenID Connect specification. + +==== Token Endpoint +.... +/realms/master/protocol/openid-connect/token +.... + +Used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on what flow is used. +The token endpoint is also used to obtain new access tokens when they expire. + +For more details see http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint[Token Endpoint] section in OpenID Connect specification. + +==== Userinfo Endpoint +.... +/realms/master/protocol/openid-connect/userinfo +.... + +Returns standard claims about the authenticated user. Protected by a bearer token. + +For more details see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[Userinfo Endpoint] section in OpenID Connect specification. + +==== Logout Endpoint +.... +/realms/master/protocol/openid-connect/logout +.... + +Logs out the authenticated user. + +User agent can be redirected to the endpoint in which case the active user session will be logged out. Afterwards the user agent is redirected back to the application. + +The endpoint can also be invoked directly by the application. To invoke this endpoint directly the refresh token needs to be included as well as credentials +required to authenticate the client. + +==== Certificate Endpoint +.... +/realms/master/protocol/openid-connect/certs +.... + +Public key used by realm encoded as a JSON Web Key (JWK). This key can be used to verify tokens issued by {{book.project.name}} without making invocations to +the server. + +For more details see https://tools.ietf.org/html/rfc7517[JSON Web Key specification]. + +==== Introspection Endpoint +.... +/realms/master/protocol/openid-connect/token/introspect +.... + +Used to retrieve the active state of a token. Protected by a bearer token and can only be invoked by confidential clients. + +For more details see https://tools.ietf.org/html/rfc7662[OAuth 2.0 Token Introspection specification]. + +==== Dynamic Client Registration Endpoint +.... +/realms/master/clients-registrations/openid-connect +.... + +Used to dynamically register clients. + +For more details see <> and the +https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration specification]. + === Flows -==== Authorization Grant +==== Authorization Code + +The Authorization Code flow redirects the user agent to {{book.project.name}}. Once the user has successfully authenticated with {{book.project.name}} an +Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code to along with its +credentials to obtain an Access Roken, Refresh Token and ID Token from {{book.project.name}}. + +The flow is targeted towards web applications, but is also recommended for native applications, including mobile applications, where it is possible to embed +a user agent. + +For more details refer to the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow] in the OpenID Connect specification. ==== Implicit -[[_resource_owner_password_credentials_flow]] +The Implicit flow redirects works similarly to the Authorization Code flow, but instead of returning a Authorization Code the Access Token and ID Token is +returned. This reduces the need for the extra invocation to exchange the Authorization Code for an Access Token. However, it does not include a Refresh +Token. This results in the need to either permit Access Tokens with a long expiration, which is problematic as it's very hard to invalidate these. Or +requires a new redirect to obtain new Access Token once the initial Access Token has expired. The Implicit flow is useful if the application only wants to +authenticate the user and deals with logout itself. + +There's also a Hybrid flow where both the Access Token and an Authorization Code is returned. + +One thing to note is that both the Implicit flow and Hybrid flow has potential security risks as the Access Token may be leaked through web server logs and +browser history. This is somewhat mitigated by using short expiration for Access Tokens. + +For more details refer to the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit Flow] in the OpenID Connect specification. + ==== Resource Owner Password Credentials +Resource Owner Password Credentials, referred to as Direct Grant in {{book.project.name}}, allows exchanging user credentials for tokens. It's not recommended +to use this flow unless you absolutely need to. Examples where this could be useful are legacy applications and command-line interfaces. + +There are a number of limitations of using this flow, including: + +* User credentials are exposed to the application +* Applications need login pages +* Application needs to be aware of the authentication scheme +* Changes to authentication flow requires changes to application +* No support for identity brokering or social login +* Flows are not supported (user self-registration, required actions, etc.) + +This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification. + +For more details refer to the https://tools.ietf.org/html/rfc6749#section-4.3[Resource Owner Password Credentials Grant] chapter in the OAuth 2.0 specification. + ==== Client Credentials +Client Credentials is used when clients (applications and services) wants to obtain access on behalf of themselves rather than on behalf of a user. This can +for example be useful for background services that applies changes to the system in general rather than for a specific user. + +{{book.project.name}} provides support for clients to authenticate either with a secret or with public/private keys. + +This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification. + +For more details refer to the https://tools.ietf.org/html/rfc6749#section-4.4[Client Credentials Grant] chapter in the OAuth 2.0 specification. + === Redirect URIs -Keycloak provides two special redirect uris for installed applications. +When using the redirect based flows it's important to use valid redirect uris for your clients. The redirect uris should be as specific as possible. This +especially applies to client-side (public clients) applications. Failing to do so could result in: + +* Open redirects - this can allow attackers to create spoof links that looks like they are coming from your domain +* Unauthorized entry - when users are already authenticated with {{book.project.name}} an attacker can use a public client where redirect uris have not be configured correctly to gain access by redirecting the user without the users knowledge + +In production for web applications always use `https` for all redirect URIs. Do not allow redirects to http. + +There's also a few special redirect URIs: [[_installed_applications_url]] -==== Installed Applications url +`http://localhost`:: -http://localhost - -This returns the code to a web server on the client as a query parameter. -Any port number is allowed. -This makes it possible to start a web server for the installed application on any free port number without requiring changes in the `Admin Console`. + This redirect URI is useful for native applications and allows the native application to create a web server on a random port that can be used to obtain the + authorization code. This redirect uri allows any port. [[_installed_applications_urn]] -==== Installed Applications urn +`urn:ietf:wg:oauth:2.0:oob`:: -`urn:ietf:wg:oauth:2.0:oob` - -If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. -When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. -The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. -With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. - -=== Session Management - -=== Dynamic Client Registration \ No newline at end of file + If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. + When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. + The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. + With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. \ No newline at end of file From 3784f8423bc00f9607cc03f5eff6004bb968b95e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 11:03:21 +0200 Subject: [PATCH 033/194] Remove mod_auth_openidc in prod docs --- SUMMARY.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index d230ec7d6b..f5ed52c0e0 100755 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -35,7 +35,9 @@ .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] + {% if book.community %} ... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] + {% endif %} . link:topics/saml/saml-overview.adoc[SAML] .. link:topics/saml/java/java-adapters.adoc[Java Adapters] From 1321ee91a46415ac83bd90f39dcf70ec24d7ca22 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 11:13:45 +0200 Subject: [PATCH 034/194] Fix links --- topics/oidc/java/jboss-adapter.adoc | 4 ++-- topics/oidc/java/jetty8-adapter.adoc | 2 +- topics/oidc/java/jetty9-adapter.adoc | 2 +- topics/oidc/java/spring-security-adapter.adoc | 2 +- topics/oidc/java/tomcat-adapter.adoc | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index f4d861fe13..737724b9ef 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -174,7 +174,7 @@ public class CustomerService { This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <> section. Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. @@ -259,7 +259,7 @@ This metadata is instead defined within server configuration (i.e. `standalone.x The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. -The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <<_adapter_config,general adapter configuration>>. +The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <>. The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. diff --git a/topics/oidc/java/jetty8-adapter.adoc b/topics/oidc/java/jetty8-adapter.adoc index 2ccd38bd0d..1c18438e2f 100755 --- a/topics/oidc/java/jetty8-adapter.adoc +++ b/topics/oidc/java/jetty8-adapter.adoc @@ -44,4 +44,4 @@ OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations,keycloak Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. Our 8.1.x adapter supports both keycloak.json and the jboss-web.xml advanced configuration. -See <<_jetty9_per_war,Required Per WAR Configuration>> \ No newline at end of file +See <> \ No newline at end of file diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index d1972e4da9..9aaf122886 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -58,7 +58,7 @@ This is a Jetty specific config file and you must define a Keycloak specific aut ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <> section. WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. You will have to define all adapter settings within the `jetty-web.xml` file as described below. diff --git a/topics/oidc/java/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc index 84e8b6c3ad..d0a895caca 100755 --- a/topics/oidc/java/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -152,7 +152,7 @@ While Spring Security's XML namespace simplifies configuration, customizing the The Keycloak Spring Security adapter also supports multi tenancy. Instead of injecting `AdapterDeploymentContextFactoryBean` with the path to `keycloak.json` you can inject an implementation of the `KeycloakConfigResolver` interface. -More details on how to implement the `KeycloakConfigResolver` can be found in <<_multi_tenancy>>. +More details on how to implement the `KeycloakConfigResolver` can be found in <>. ==== Naming Security Roles diff --git a/topics/oidc/java/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc index 06e93f794e..f07a135e09 100755 --- a/topics/oidc/java/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -45,7 +45,7 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <<_adapter_config,general adapter configuration>> section. +The format of this config file is describe in the <> section. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: From 28a356b0eedc94c1404ec302cb53c1cdb5fc02eb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 12:39:57 +0200 Subject: [PATCH 035/194] Fix duplicated _jetty9_adapter_installation id --- topics/saml/java/jetty-adapter/jetty9_installation.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/jetty-adapter/jetty9_installation.adoc b/topics/saml/java/jetty-adapter/jetty9_installation.adoc index 2554091131..48a96d52e6 100644 --- a/topics/saml/java/jetty-adapter/jetty9_installation.adoc +++ b/topics/saml/java/jetty-adapter/jetty9_installation.adoc @@ -1,4 +1,4 @@ -[[_jetty9_adapter_installation]] +[[_jetty9_saml_adapter_installation]] ===== Jetty 9 Adapter Installation From 849692a5df35490a768f1d2a68deda8d430b3a2f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 13:17:34 +0200 Subject: [PATCH 036/194] Fix compilation issues --- topics/oidc/java/servlet-filter-adapter.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 3f8c809b68..69c52a1daf 100755 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -31,7 +31,7 @@ There's no way standard way to invalidate an HTTP session based on a session id. ---- In the snippet above there are two url-patterns. - `/protected/*` are the files we want protected, while the `/keycloak/*` url-pattern handles callbacks from the {{book.project.title}} server. + _/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {{book.project.title}} server. Note that you should configure your client in the {{book.project.title}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. @@ -49,4 +49,4 @@ To use this filter, include this maven artifact in your WAR poms: keycloak-servlet-filter-adapter &project.version; ----- \ No newline at end of file +---- From fea02c4c7c934423baa8ffced2bde62fafac461a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 6 Jun 2016 19:25:58 +0200 Subject: [PATCH 037/194] Added missing IDs --- topics/oidc/oidc-generic.adoc | 1 + topics/overview/supported-platforms.adoc | 2 +- topics/saml/java/jetty-adapter.adoc | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 4c71cdade3..9c9fd6c89b 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -118,6 +118,7 @@ browser history. This is somewhat mitigated by using short expiration for Access For more details refer to the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit Flow] in the OpenID Connect specification. +[[_resource_owner_password_credentials_flow]] ==== Resource Owner Password Credentials Resource Owner Password Credentials, referred to as Direct Grant in {{book.project.name}}, allows exchanging user credentials for tokens. It's not recommended diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 1a630d16dc..379d20a71c 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -65,7 +65,7 @@ {% if book.community %} * <> * <> -* <> +* <> {% endif %} ==== Apache HTTP Server diff --git a/topics/saml/java/jetty-adapter.adoc b/topics/saml/java/jetty-adapter.adoc index 6a85ed2798..bb342de974 100644 --- a/topics/saml/java/jetty-adapter.adoc +++ b/topics/saml/java/jetty-adapter.adoc @@ -1,4 +1,4 @@ - +[[_jetty_saml_adapter]] ==== Jetty SAML Adapters To be able to secure WAR apps deployed on Jetty you must install the {{book.project.name}} Jetty 9.x or 8.x SAML adapter into your Jetty installation. From 76ef0ff856bfd7b5d6113bcfcd5ca98cab4644a1 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 6 Jun 2016 15:42:25 -0400 Subject: [PATCH 038/194] small fix --- topics/overview/supported-protocols.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 250dc09b4e..c4cac293f6 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -23,7 +23,7 @@ the request. === SAML 2.0 -link://https://saml.org/fill/this/in[SAML 2.0] is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora +link:http://saml.xml.org/saml-specifications[SAML 2.0] is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora of WS-* specifications so it tends to be a bit more verbose than OIDC. SAML 2.0 is primarily an authentication protocol that works by exchanging XML documents between the authentication server and the application. XML signatures and encryption is used to verify requests and responses. From cc4654922ed1d9a9bcb0309c96ec3e629b5eaed8 Mon Sep 17 00:00:00 2001 From: --add Date: Tue, 7 Jun 2016 11:44:38 +0530 Subject: [PATCH 039/194] sync with latest changes --- topics/oidc/java/fuse-adapter.adoc | 46 +--------------------------- topics/oidc/java/jboss-adapter.adoc | 4 --- topics/oidc/java/jetty9-adapter.adoc | 7 ++--- topics/oidc/java/tomcat-adapter.adoc | 4 +-- topics/oidc/oidc-generic.adoc | 3 +- topics/oidc/oidc-overview.adoc | 4 +-- 6 files changed, 8 insertions(+), 60 deletions(-) diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 5513281432..f00dcefb33 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -22,51 +22,7 @@ What is supported for Fuse is: Basically all mentioned web applications require to inject {{book.project.name}} Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different according to application type. The details are described in individual sub-chapters. -<<<<<<< HEAD -===== Builtin CXF web applications - -Some services automatically come with deployed servlets on startup. One of such examples is CXF servlet running on -http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which Keycloak is currently using, -is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by Keycloak. -You can see the `OSGI-INF/blueprint/blueprint.xml` inside `cxf-jaxrs` example, which adds JAX-RS `customerservice` endpoint and more importantly, it secures whole `/cxf` context. - -As a side effect, all other CXF services running on default CXF HTTP destination will be secured too. Once you uninstall feature `keycloak-fuse-6.2-example`, the -original unsecured servlet on `/cxf` context is deployed back and hence context will become unsecured again. - -It's recommended to use your own Jetty engine for your apps (similarly like `cxf-jaxws` application is doing). - - -==== How to secure Fuse admin services - -===== SSH authentication to Fuse terminal with Keycloak credentials - -Keycloak mainly addresses usecases for authentication of web applications, however if your admin services (like fuse admin console) are protected -with Keycloak, it may be good to protect non-web services like SSH with Keycloak credentials too. It's possible to do it by using JAAS login module, which -allows to remotely connect to Keycloak and verify credentials based on - -// <<_direct_access_grants,Direct Access Grants>> . - -Example steps for enable SSH authentication require changing the configuration of `sshRealm` in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`, then adding -file `$FUSE_HOME/etc/keycloak-direct-access.json` (this is default location, which can be changed) and install the needed feature `keycloak-jaas`. It's described in details -in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . - - -===== JMX authentication with Keycloak credentials - -This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may -be better to use just hawt.io/jolokia as jolokia agent is installed in http://hawt.io by default. - -You need to configure `jmxRealm` in `$FUSE_HOME/etc/org.apache.karaf.management.cfg`, then adding file `$FUSE_HOME/etc/keycloak-direct-access.json` -(this is default location, which can be changed) and install the needed feature `keycloak-jaas`. -It's described in details in the README file of Fuse example, which in example distribution is inside `fuse/fuse-admin/README.md` . - - -===== Secure Fuse admin console - -Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with Keycloak. -======= {% if book.community %} The best place to start is look at Fuse demo bundled as part of {{book.project.name}} examples in directory `fuse` . Most of the steps should be understandable from testing and understanding the demo. -{% endif %} ->>>>>>> 163974a212546af0df02970a57466a056b83ba5a +{% endif %} \ No newline at end of file diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index ddc6a4fa03..737724b9ef 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -174,10 +174,8 @@ public class CustomerService { This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. - The format of this config file is describe in the <> section. - Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. @@ -261,9 +259,7 @@ This metadata is instead defined within server configuration (i.e. `standalone.x The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. - The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <>. - The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index ca21ecf29e..9aaf122886 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -1,3 +1,4 @@ + [[_jetty9_adapter]] === Jetty 9.x Adapters @@ -5,7 +6,7 @@ Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will ha You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. -[[_jetty-9_adapter_installation]] +[[_jetty9_adapter_installation]] ==== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. @@ -57,10 +58,8 @@ This is a Jetty specific config file and you must define a Keycloak specific aut ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. - The format of this config file is describe in the <> section. - WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. You will have to define all adapter settings within the `jetty-web.xml` file as described below. @@ -146,4 +145,4 @@ Here's an example: user ----- +---- \ No newline at end of file diff --git a/topics/oidc/java/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc index d978d4901b..f07a135e09 100755 --- a/topics/oidc/java/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -45,10 +45,8 @@ This is a Tomcat specific config file and you must define a Keycloak specific Va ---- Next you must create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. - The format of this config file is describe in the <> section. - Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. Here's an example: @@ -86,4 +84,4 @@ Here's an example: user ----- +---- \ No newline at end of file diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 4c71cdade3..04fac12202 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -118,6 +118,7 @@ browser history. This is somewhat mitigated by using short expiration for Access For more details refer to the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit Flow] in the OpenID Connect specification. +[[_resource_owner_password_credentials_flow]] ==== Resource Owner Password Credentials Resource Owner Password Credentials, referred to as Direct Grant in {{book.project.name}}, allows exchanging user credentials for tokens. It's not recommended @@ -171,4 +172,4 @@ There's also a few special redirect URIs: If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. - With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. \ No newline at end of file + With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. diff --git a/topics/oidc/oidc-overview.adoc b/topics/oidc/oidc-overview.adoc index bb5fdda906..8436712c35 100644 --- a/topics/oidc/oidc-overview.adoc +++ b/topics/oidc/oidc-overview.adoc @@ -1,6 +1,4 @@ == OpenID Connect This section describes how you can secure applications and services with OpenID Connect using either {{book.project.name}} adapters or generic OpenID Connect -Resource Provider libraries. - -// TODO: Update the cross-reference <<_direct_access_grants,Direct Access Grants>> in the topic /oidc/java/fuse-adapter.adoc +Resource Provider libraries. \ No newline at end of file From e472eededabdca322827859731ef0bd922de73aa Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 8 Jun 2016 06:10:13 +0200 Subject: [PATCH 040/194] Added example to use direct grant --- topics/oidc/oidc-generic.adoc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 9c9fd6c89b..98bc456da6 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -133,10 +133,28 @@ There are a number of limitations of using this flow, including: * No support for identity brokering or social login * Flows are not supported (user self-registration, required actions, etc.) +For a client to be permitted to use the Resource Owner Password Credentials grant the client has to have `Direct Access Grants Enabled` enabled. + This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification. For more details refer to the https://tools.ietf.org/html/rfc6749#section-4.3[Resource Owner Password Credentials Grant] chapter in the OAuth 2.0 specification. +===== Example using CURL + +The following example shows how to obtain an access token for a user in the realm `master` with username `user` and password `password`. The example is using +the confidential client `myclient`: + +[source,bash] +---- +curl \ + -d "client_id=myclient" \ + -d "client_secret=40cc097b-2a57-4c17-b36a-8fdf3fc2d578" \ + -d "username=user" \ + -d "password=password" \ + -d "grant_type=password" \ + "http://localhost:8080/auth/realms/master/protocol/openid-connect/token" +---- + ==== Client Credentials Client Credentials is used when clients (applications and services) wants to obtain access on behalf of themselves rather than on behalf of a user. This can From c41a00c608c110f5b0a6dd016fc9b2b48cc6eef0 Mon Sep 17 00:00:00 2001 From: --add Date: Thu, 9 Jun 2016 16:01:11 +0530 Subject: [PATCH 041/194] added master-docinfo.xml and metadata.ini --- master-docinfo.xml | 12 ++++++++++++ metadata.ini | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100755 master-docinfo.xml create mode 100644 metadata.ini diff --git a/master-docinfo.xml b/master-docinfo.xml new file mode 100755 index 0000000000..819b70d24c --- /dev/null +++ b/master-docinfo.xml @@ -0,0 +1,12 @@ +Red Hat Single Sign-On +7.0.0 +Securing Applications and Services Guide +Securing Applications and Services Guide +7.0.0 + + This guide consist of information for securing applications and services using Red Hat Single Sign-On 7.0.0 + + + Red Hat Customer Content Services + + diff --git a/metadata.ini b/metadata.ini new file mode 100644 index 0000000000..fbb306cc3f --- /dev/null +++ b/metadata.ini @@ -0,0 +1,20 @@ +[source] +language = en-US +type = book +markup = asciidoc + +[metadata] +title = Securing Applications and Services Guide +product = Red Hat Single Sign-On +version = 7.0.0 +edition = +subtitle = +keywords = +abstract = + +[bugs] +reporting_url = +type = +product = +component = Documentation + From 8e3fc3616bdecb7322e046b3acd1062a4568365a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 08:14:45 +0200 Subject: [PATCH 042/194] Set version to 1.9.7 --- book.json | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/book.json b/book.json index 39fcf1969b..56e8fe14f5 100755 --- a/book.json +++ b/book.json @@ -21,19 +21,9 @@ "name": "Keycloak Administration Guide", "link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/" }, - "web": { - "docs": { - "name": "keycloak.org/docs", - "link": "http://keycloak.org/docs" - }, - "downloads": { - "name": "keycloak.org/downloads", - "link": "http://keycloak.org/downloads" - } - }, "project": { "name": "Keycloak", - "version": "1.9.5.Final" + "version": "1.9.7.Final" } } } From 9b1a2da589670f714aaea2e0f672e6ddfa61351a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:09:54 +0200 Subject: [PATCH 043/194] Added book-product.json --- README.adoc | 5 ++--- book-product.json | 25 +++++++++++++++++++++++++ book.json | 18 +++++++----------- build.sh | 2 -- gitlab-conversion.py | 7 +++++-- 5 files changed, 39 insertions(+), 18 deletions(-) create mode 100755 book-product.json diff --git a/README.adoc b/README.adoc index 08ecb3da6d..9701180643 100755 --- a/README.adoc +++ b/README.adoc @@ -1,10 +1,9 @@ -{{book.title}} -=========================================== += Securing Applications and Services Guide image:images/keycloak_logo.png[alt="Keycloak"] -*Keycloak* _Documentation_ for {{book.project.version}} +{{book.project.name}} {{book.project.version}} http://www.keycloak.org diff --git a/book-product.json b/book-product.json new file mode 100755 index 0000000000..3a5a3448d7 --- /dev/null +++ b/book-product.json @@ -0,0 +1,25 @@ +{ + "gitbook": "2.x.x", + "structure": { + "readme": "README.adoc" + }, + "plugins": [ + "toggle-chapters", + "ungrey", + "splitter" + ], + "variables": { + "title": "Securing Applications and Services Guide", + "project": { + "name": "Red Hat Single Sign-On", + "version": "7.0.0" + }, + "community": false, + "product": true, + "images": "rhsso-images", + "adminguide": { + "name": "Server Administration Guide", + "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.0.0/server-administration-guide/" + } + } +} diff --git a/book.json b/book.json index 56e8fe14f5..d3dcf33ef5 100755 --- a/book.json +++ b/book.json @@ -10,20 +10,16 @@ ], "variables": { "title": "Securing Applications and Services Guide", - "community": true, - "product": false, - "images": "keycloak-images", - "appserver": { - "name": "Wildfly", - "version": "10" - }, - "adminguide": { - "name": "Keycloak Administration Guide", - "link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/" - }, "project": { "name": "Keycloak", "version": "1.9.7.Final" + }, + "community": true, + "product": false, + "images": "keycloak-images", + "adminguide": { + "name": "Server Administration Guide", + "link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/" } } } diff --git a/build.sh b/build.sh index 4befdf8200..fc1e19f7fb 100755 --- a/build.sh +++ b/build.sh @@ -5,5 +5,3 @@ cd $(readlink -f `dirname $0`) python gitlab-conversion.py cd target asciidoctor master.adoc - -xdg-open master.html diff --git a/gitlab-conversion.py b/gitlab-conversion.py index 630665f150..9144f04d72 100755 --- a/gitlab-conversion.py +++ b/gitlab-conversion.py @@ -42,6 +42,9 @@ if os.path.isdir('keycloak-images'): if os.path.isdir('rhsso-images'): shutil.copytree('rhsso-images',os.path.join(targetdir, 'rhsso-images')) +shutil.copyfile('metadata.ini', os.path.join(targetdir, 'metadata.ini')); +shutil.copyfile('master-docinfo.xml', os.path.join(targetdir, 'master-docinfo.xml')); + tmp = os.path.join(targetdir, 'topics') if not os.path.exists(tmp): os.makedirs(tmp) @@ -67,8 +70,8 @@ input = re.sub(r"[ ]*\.+\s*link:(.*)\[(.*)\]", "include::\g<1>[]", input) input = applyTransformation(input) output.write(input) -# parse book.json file and create document attributes -with open('book.json') as data_file: +# parse book-product.json file and create document attributes +with open('book-product.json') as data_file: data = json.load(data_file) variables = data['variables'] From 479c75be271a07b076362bb1cf22d0b4894a0457 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:10:47 +0200 Subject: [PATCH 044/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index 50a8d591bb..32a3fff863 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -55,7 +55,7 @@ For example the way backchannel logout works is: . User sends logout request from one application . The application sends logout request to {{book.project.name}} . The {{book.project.name}} server invalidates the user session -. The {{book.project.name}} server then sends a backchannel request to application with a admin url that are associated with the session +. The {{book.project.name}} server then sends a backchannel request to application with an admin url that are associated with the session . When an application receives the logout request it invalidates the corresponding HTTP session Some examples of possible values of admin URL are: From 5b85f7d68b6949a25ee8eedb3a69522cadc4c68c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:11:22 +0200 Subject: [PATCH 045/194] Update topics/oidc/java/servlet-filter-adapter.adoc --- topics/oidc/java/servlet-filter-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 topics/oidc/java/servlet-filter-adapter.adoc diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc old mode 100755 new mode 100644 index 69c52a1daf..b4ca795950 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,7 +1,7 @@ [[_servlet_filter_adapter]] === Java Servlet Filter Adapter -If you are deploying your Java Servlet application on a platform where there is no {{book.project.title}} adapter you opt to use the the servlet filter adapter. +If you are deploying your Java Servlet application on a platform where there is no {{book.project.title}} adapter you opt to use the servlet filter adapter. This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml. Instead you define a filter mapping using the {{book.project.title}} servlet filter adapter to secure the url patterns you want to secure. From e42eb436c8d43335437af34628e4e68db8881d2a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:16:55 +0200 Subject: [PATCH 046/194] Update topics/oidc/java/adapter_error_handling.adoc --- topics/oidc/java/adapter_error_handling.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 topics/oidc/java/adapter_error_handling.adoc diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc old mode 100755 new mode 100644 index 6e6beadfa9..0aaf30816d --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -16,7 +16,7 @@ You can set up an error-page within your `web.xml` file to handle the error howe ---- {{book.project.name}} also sets a `HttpServletRequest` attribute that you can retrieve. -The attribute name is `org.keycloak.adapters.spi.AuthenticationError`, which should be casted to to `org.keycloak.adapters.OIDCAuthenticationError`. +The attribute name is `org.keycloak.adapters.spi.AuthenticationError`, which should be casted to `org.keycloak.adapters.OIDCAuthenticationError`. For example: From be9cea7e476656e7992602dfd84b6d4ca484e595 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:17:57 +0200 Subject: [PATCH 047/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index 32a3fff863..e65da6007e 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -66,7 +66,7 @@ http://${application.session.host}:8080/myapp:: [[_registration_app_nodes]] ==== Registration of application nodes -The previous describes how {{book.project.name}} can send logout request to node associated with a specific HTTP session. +The previous section describes how {{book.project.name}} can send logout request to node associated with a specific HTTP session. However, in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. For example to push a new not before policy to the application or to logout all users from the application. From eb173bccc891820bb9e5c4c337882f547b6660ba Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:19:48 +0200 Subject: [PATCH 048/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index e65da6007e..b24d3be47b 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -58,10 +58,7 @@ For example the way backchannel logout works is: . The {{book.project.name}} server then sends a backchannel request to application with an admin url that are associated with the session . When an application receives the logout request it invalidates the corresponding HTTP session -Some examples of possible values of admin URL are: - -http://${application.session.host}:8080/myapp:: - {{book.project.name}} tracks hosts associated with the HTTP Session and will send session invalidation message to the associated node. +If admin URL contains `${application.session.host}` it will be replaced with the URL to the node associated with the HTTP session. [[_registration_app_nodes]] ==== Registration of application nodes From 944d8d0b0a4b0168221b0cb07e76a836fef92f51 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:20:20 +0200 Subject: [PATCH 049/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index b24d3be47b..20d8651cae 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -47,7 +47,7 @@ the redirect-uri `/myapp` instead of `https://acme.org/myapp`. ==== Admin URL configuration -Admin URL for a particular application can be configured in the {{book.project.name}} Administration Console. +Admin URL for a particular client can be configured in the {{book.project.name}} Administration Console. It's used by the {{book.project.name}} server to send backend requests to the application for various tasks, like logout users or push revocation policies. For example the way backchannel logout works is: From 70db85b88c850b42969c5ccc83c3700b807a182f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:23:02 +0200 Subject: [PATCH 050/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index 20d8651cae..c775005fd2 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -3,7 +3,7 @@ This chapter is related to supporting clustered applications deployed to JBoss EAP{% if book.community %}, WildFly and JBoss AS{% endif %}. -There are several options depending on if your application is: +There are a few options available depending on whether your application is: * Stateless or stateful * Distributable (replicated http session) or non-distributable From 1fe502f345f7f347678dc52baa7b955d9c4a803f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:25:17 +0200 Subject: [PATCH 051/194] Update topics/oidc/java/application-clustering.adoc --- topics/oidc/java/application-clustering.adoc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index c775005fd2..515d68e14f 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -1,7 +1,12 @@ [[_applicationclustering]] === Application Clustering -This chapter is related to supporting clustered applications deployed to JBoss EAP{% if book.community %}, WildFly and JBoss AS{% endif %}. +{% if book.community %} +This chapter is related to supporting clustered applications deployed to JBoss EAP, WildFly and JBoss AS. +{% endif %} +{% if book.product %} +This chapter is related to supporting clustered applications deployed to JBoss EAP. +{% endif %} There are a few options available depending on whether your application is: From 0aac192e52b130ea76dba42d0ea9d73f004a2675 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:28:28 +0200 Subject: [PATCH 052/194] Update topics/oidc/java/jaas.adoc --- topics/oidc/java/jaas.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 topics/oidc/java/jaas.adoc diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc old mode 100755 new mode 100644 index fec56b196f..d42d21cd10 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -27,4 +27,4 @@ keycloak-config-file:: `role-principal-class`:: Configure alternative class for Role principals attached to JAAS Subject. - Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument. \ No newline at end of file + Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument. From 6e9a29b27556b0a1e31b2862201c5b7d06668cee Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:28:35 +0200 Subject: [PATCH 053/194] Update topics/oidc/java/adapter_error_handling.adoc --- topics/oidc/java/adapter_error_handling.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc index 0aaf30816d..ec6f59fbc9 100644 --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -31,4 +31,4 @@ OIDCAuthenticationError error = (OIDCAuthenticationError) httpServletRequest Reason reason = error.getReason(); System.out.println(reason.name()); ----- \ No newline at end of file +---- From 608f49f7541efff6bd85cd50a07b0c424284125e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:29:01 +0200 Subject: [PATCH 054/194] Update topics/oidc/java/jaas.adoc --- topics/oidc/java/jaas.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc index d42d21cd10..94a795f651 100644 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -28,3 +28,4 @@ keycloak-config-file:: `role-principal-class`:: Configure alternative class for Role principals attached to JAAS Subject. Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument. + From 2e0fb0268946404d57538471688f3ef6202ba0bb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:29:14 +0200 Subject: [PATCH 055/194] Update topics/oidc/java/adapter_error_handling.adoc --- topics/oidc/java/adapter_error_handling.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc index ec6f59fbc9..501c9fd6f8 100644 --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -32,3 +32,4 @@ OIDCAuthenticationError error = (OIDCAuthenticationError) httpServletRequest Reason reason = error.getReason(); System.out.println(reason.name()); ---- + From 1d0d9b1b0659e196050671396b1e3122c80165ed Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:29:21 +0200 Subject: [PATCH 056/194] Update topics/oidc/java/adapter-context.adoc --- topics/oidc/java/adapter-context.adoc | 1 + 1 file changed, 1 insertion(+) mode change 100755 => 100644 topics/oidc/java/adapter-context.adoc diff --git a/topics/oidc/java/adapter-context.adoc b/topics/oidc/java/adapter-context.adoc old mode 100755 new mode 100644 index 41319b0c76..31ccdc5892 --- a/topics/oidc/java/adapter-context.adoc +++ b/topics/oidc/java/adapter-context.adoc @@ -17,3 +17,4 @@ Or, it is available in secure and insecure requests in the HttpSession: httpServletRequest.getSession() .getAttribute(KeycloakSecurityContext.class.getName()); ---- + From ee62068dcfd8ddfadb98d363f2f8674fa1690a0d Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:30:08 +0200 Subject: [PATCH 057/194] Update topics/oidc/java/jaas.adoc --- topics/oidc/java/jaas.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc index 94a795f651..b1cb7cf13d 100644 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -10,7 +10,7 @@ The provided login modules are: org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule:: This login module allows to authenticate with username/password from {{book.project.title}}. It's using <> flow to validate if the provided - username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use Keycloak, but can't use the standard browser + username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use {{book.project.title}}, but can't use the standard browser based flows due to their non-web nature. Example of such application could be messaging or SSH. org.keycloak.adapters.jaas.BearerTokenLoginModule:: From 4f2490cb930455f09a297b9bd64b15f34c54340f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:31:57 +0200 Subject: [PATCH 058/194] Update topics/oidc/java/jaas.adoc --- topics/oidc/java/jaas.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc index b1cb7cf13d..d95e5ccfbb 100644 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -3,19 +3,19 @@ It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters. However, some applications and systems may still rely on pure legacy JAAS solution. -{{book.project.title}} provides two login modules to help in these situations. +{{book.project.name}} provides two login modules to help in these situations. The provided login modules are: org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule:: - This login module allows to authenticate with username/password from {{book.project.title}}. + This login module allows to authenticate with username/password from {{book.project.name}}. It's using <> flow to validate if the provided - username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use {{book.project.title}}, but can't use the standard browser + username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use {{book.project.name}}, but can't use the standard browser based flows due to their non-web nature. Example of such application could be messaging or SSH. org.keycloak.adapters.jaas.BearerTokenLoginModule:: - This login module allows to authenticate with {{book.project.title}} access token passed to it through CallbackHandler as password. - It may be useful for example in case, when you have {{book.project.title}} access token from standard based authentication flow and your web application then + This login module allows to authenticate with {{book.project.name}} access token passed to it through CallbackHandler as password. + It may be useful for example in case, when you have {{book.project.name}} access token from standard based authentication flow and your web application then needs to talk to external non-web based system, which rely on JAAS. For example a messaging system. Both modules use the following configuration properties: From 7911e5fcf00ac5a2ddbf29bbbab3f3123b526082 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:33:42 +0200 Subject: [PATCH 059/194] Fix invalid book.project.title --- topics/oidc/java/adapter-context.adoc | 2 +- topics/oidc/java/adapter_error_handling.adoc | 4 ++-- topics/oidc/java/jboss-adapter.adoc | 6 +++--- topics/oidc/java/servlet-filter-adapter.adoc | 10 +++++----- topics/overview/supported-platforms.adoc | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/topics/oidc/java/adapter-context.adoc b/topics/oidc/java/adapter-context.adoc index 31ccdc5892..eaa2835338 100644 --- a/topics/oidc/java/adapter-context.adoc +++ b/topics/oidc/java/adapter-context.adoc @@ -1,7 +1,7 @@ === Security Context The `KeycloakSecurityContext` interface is available if you need to access to the tokens directly. This could be useful if you want to retrieve additional -details from the token (such as user profile information) or you want to invoke a RESTful service that is protected by {{book.project.title}}. +details from the token (such as user profile information) or you want to invoke a RESTful service that is protected by {{book.project.name}}. In servlet environments it is available in secured invocations as an attribute in HttpServletRequest: [source,java] diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc index 501c9fd6f8..04a7ca115d 100644 --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -3,9 +3,9 @@ === Error Handling {{book.project.name}} has some error handling facilities for servlet based client adapters. -When an error is encountered in authentication, {{book.project.title}} will call `HttpServletResponse.sendError()`. +When an error is encountered in authentication, {{book.project.name}} will call `HttpServletResponse.sendError()`. You can set up an error-page within your `web.xml` file to handle the error however you want. -{{book.project.title}} may throw 400, 401, 403, and 500 errors. +{{book.project.name}} may throw 400, 401, 403, and 500 errors. [source,xml] ---- diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index d7c83276d0..5ce51c35ca 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -233,10 +233,10 @@ Here's an example: ==== Securing WARs via Keycloak Subsystem -You do not have to modify your WAR to secure it with {{book.project.title}}. Instead you can externally secure it via the {{book.project.title}} Adapter Subsystem. +You do not have to modify your WAR to secure it with {{book.project.name}}. Instead you can externally secure it via the {{book.project.name}} Adapter Subsystem. While you don't have to specify KEYCLOAK as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. You do not, however, have to create a `WEB-INF/keycloak.json` file. -This metadata is instead defined within server configuration (i.e. `standalone.xml`) in the {{book.project.title}} subsystem definition. +This metadata is instead defined within server configuration (i.e. `standalone.xml`) in the {{book.project.name}} subsystem definition. [source,xml] ---- @@ -265,7 +265,7 @@ The rest of the configuration corresponds pretty much one to one with the `keycl The exception is the `credential` element. -To make it easier for you, you can go to the {{book.project.title}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. +To make it easier for you, you can go to the {{book.project.name}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. It provides an example XML file you can cut and paste. There is an additional convenience format for this XML if you have multiple WARs you are deployment that are secured by the same domain. diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index b4ca795950..fe4d7f0081 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,9 +1,9 @@ [[_servlet_filter_adapter]] === Java Servlet Filter Adapter -If you are deploying your Java Servlet application on a platform where there is no {{book.project.title}} adapter you opt to use the servlet filter adapter. +If you are deploying your Java Servlet application on a platform where there is no {{book.project.name}} adapter you opt to use the servlet filter adapter. This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml. -Instead you define a filter mapping using the {{book.project.title}} servlet filter adapter to secure the url patterns you want to secure. +Instead you define a filter mapping using the {{book.project.name}} servlet filter adapter to secure the url patterns you want to secure. WARNING: Backchannel logout works a bit differently than the standard adapters. Instead of invalidating the HTTP session it marks the session id as logged out. @@ -31,14 +31,14 @@ There's no way standard way to invalidate an HTTP session based on a session id. ---- In the snippet above there are two url-patterns. - _/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {{book.project.title}} server. + _/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {{book.project.name}} server. -Note that you should configure your client in the {{book.project.title}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. +Note that you should configure your client in the {{book.project.name}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. The Admin URL will make callbacks to the Admin URL to do things like backchannel logout. So, the Admin URL in this example should be `http[s]://hostname/{context-root}/keycloak`. -The {{book.project.title}} filter has the same configuration parameters as the other adapters except you must define them as filter init params instead of context params. +The {{book.project.name}} filter has the same configuration parameters as the other adapters except you must define them as filter init params instead of context params. To use this filter, include this maven artifact in your WAR poms: diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 379d20a71c..6d7be449ff 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -26,8 +26,8 @@ {% if book.community %} ==== Node.js -* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.title}} Connect] (community) -* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.title}} Auth Utils] (community) +* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Connect] (community) +* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Auth Utils] (community) {% endif %} {% if book.community %} From cab9b0a21c0e043219dd6ed88ae54e0ad04af239 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:41:13 +0200 Subject: [PATCH 060/194] Fix project name --- topics/oidc/java/fuse/servlet-whiteboard.adoc | 2 +- topics/oidc/java/jboss-adapter.adoc | 6 +++--- topics/oidc/javascript-adapter.adoc | 4 ++-- topics/oidc/oidc-generic.adoc | 2 +- .../saml/java/jboss-adapter/jboss_adapter_installation.adoc | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc index 37c0893803..2b979a76f0 100644 --- a/topics/oidc/java/fuse/servlet-whiteboard.adoc +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -7,7 +7,7 @@ https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web The needed steps to secure your servlet with {{book.project.name}} are: -* Keycloak provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. +* {{book.project.name}} provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. You need to declare such service in `OSGI-INF/blueprint/blueprint.xml` inside your application. Note that your servlet needs to depend on it. The example configuration can look like this: [source,xml] diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 5ce51c35ca..12882d5daa 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -68,7 +68,7 @@ Install on JBoss EAP 7: [source] ---- $ cd $EAP_HOME -$ unzip RH-SSO-{{book.project.version}}-eap7-adapter.zip +$ unzip rh-sso-{{book.project.version}}-eap7-adapter.zip ---- Install on JBoss EAP 6: @@ -76,7 +76,7 @@ Install on JBoss EAP 6: [source] ---- $ cd $EAP_HOME -$ unzip RH-SSO-{{book.project.version}}-eap6-adapter.zip +$ unzip rh-sso-{{book.project.version}}-eap6-adapter.zip ---- {% endif %} @@ -231,7 +231,7 @@ Here's an example: ---- -==== Securing WARs via Keycloak Subsystem +==== Securing WARs via Adapter Subsystem You do not have to modify your WAR to secure it with {{book.project.name}}. Instead you can externally secure it via the {{book.project.name}} Adapter Subsystem. While you don't have to specify KEYCLOAK as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 8653cbadd6..206d1cd138 100755 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -239,10 +239,10 @@ Options is an Object, where: * token - Set an initial value for the token. * refreshToken - Set an initial value for the refresh token. * idToken - Set an initial value for the id token (only together with token or refreshToken). -* timeSkew - Set an initial value for skew between local time and Keycloak server in seconds (only together with token or refreshToken). +* timeSkew - Set an initial value for skew between local time and {{book.project.name}} server in seconds (only together with token or refreshToken). * checkLoginIframe - Set to enable/disable monitoring login state (default is true). * checkLoginIframeInterval - Set the interval to check login state (default is 5 seconds). -* responseMode - Set the OpenID Connect response mode send to Keycloak server at login request. Valid values are query or fragment . Default value is fragment, which means that after successful authentication will Keycloak redirect to javascript application with OpenID Connect parameters added in URL fragment. This is generally safer and recommended over query. +* responseMode - Set the OpenID Connect response mode send to {{book.project.name}} server at login request. Valid values are query or fragment . Default value is fragment, which means that after successful authentication will {{book.project.name}} redirect to javascript application with OpenID Connect parameters added in URL fragment. This is generally safer and recommended over query. * flow - Set the OpenID Connect flow. Valid values are standard, implicit or hybrid. Returns promise to set functions to be invoked on success or error. diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 46fc8c2397..b16fb3783f 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -188,6 +188,6 @@ There's also a few special redirect URIs: `urn:ietf:wg:oauth:2.0:oob`:: If its not possible to start a web server in the client (or a browser is not available) it is possible to use the special `urn:ietf:wg:oauth:2.0:oob` redirect uri. - When this redirect uri is used Keycloak displays a page with the code in the title and in a box on the page. + When this redirect uri is used {{book.project.name}} displays a page with the code in the title and in a box on the page. The application can either detect that the browser title has changed, or the user can copy/paste the code manually to the application. With this redirect uri it is also possible for a user to use a different device to obtain a code to paste back to the application. diff --git a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc index 2a3e0e38c2..3e9fbd5e1c 100644 --- a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc +++ b/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc @@ -32,7 +32,7 @@ Install on JBoss EAP 6.x: ---- $ cd $JBOSS_HOME -$ unzip RH-SSO-saml-eap6-adapter.zip +$ unzip rh-sso-saml-eap6-adapter.zip ---- Install on JBoss EAP 7.x: @@ -40,12 +40,12 @@ Install on JBoss EAP 7.x: ---- $ cd $JBOSS_HOME -$ unzip RH-SSO-saml-eap7-adapter.zip +$ unzip rh-sso-saml-eap7-adapter.zip ---- {% endif %} -These zip files create new JBoss Modules specific to the Wildfly/JBoss EPKeycloak SAML Adapter within your Wildfly or JBoss EAP distro. +These zip files create new JBoss Modules specific to the Wildfly/JBoss EAP SAML Adapter within your Wildfly or JBoss EAP distro. After adding the modules, you must then enable the {{book.project.name}} SAML Subsystem within your app server's server configuration: `domain.xml` or `standalone.xml`. From dff47728beadb82082aaa091691d5e901ae7080f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:43:22 +0200 Subject: [PATCH 061/194] Update topics/oidc/java/servlet-filter-adapter.adoc --- topics/oidc/java/servlet-filter-adapter.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index fe4d7f0081..140c3ef460 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -42,11 +42,11 @@ The {{book.project.name}} filter has the same configuration parameters as the ot To use this filter, include this maven artifact in your WAR poms: -[source,xml] +[source,xml, subs="attributes"] ---- org.keycloak keycloak-servlet-filter-adapter - &project.version; + {{book.project.version}} ---- From 4339eba1f2be5199739032b8ec47d29fee60962e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:45:20 +0200 Subject: [PATCH 062/194] Fix --- topics/oidc/java/servlet-filter-adapter.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 140c3ef460..d23023b867 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -44,9 +44,9 @@ To use this filter, include this maven artifact in your WAR poms: [source,xml, subs="attributes"] ---- - - org.keycloak - keycloak-servlet-filter-adapter - {{book.project.version}} - +<dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-servlet-filter-adapter</artifactId> + <version>{{book.project.version}}</version> +</dependency> ---- From 8dd3a065398c8b5b1dabcd55dcf8775a0a096bd4 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:46:35 +0200 Subject: [PATCH 063/194] Update topics/oidc/java/fuse/fuse-admin.adoc --- topics/oidc/java/fuse/fuse-admin.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index 33223d587a..a3af44346b 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -51,7 +51,7 @@ features:install keycloak-jaas ssh -o PubkeyAuthentication=no -p 8101 admin@localhost ``` -And login with password `password` . Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` +And login with password `password`. Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` ===== JMX authentication From 148e6a5bed7b4875d0fe0caf3cc419b5b0ad7ebe Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:54:13 +0200 Subject: [PATCH 064/194] Fix attr substitution in source blocks --- topics/oidc/java/fuse/camel.adoc | 3 ++- topics/oidc/java/fuse/classic-war.adoc | 2 +- topics/oidc/java/fuse/cxf-builtin.adoc | 3 ++- topics/oidc/java/fuse/cxf-separate.adoc | 3 ++- topics/oidc/java/fuse/fuse-admin.adoc | 5 +++-- topics/oidc/java/fuse/servlet-whiteboard.adoc | 3 ++- topics/oidc/java/jboss-adapter.adoc | 14 +++++++------- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index b79229de6c..092f1deef0 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -78,7 +78,8 @@ The roles, security constraint mappings and {{book.project.name}} adapter config * The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: -[source] + +[source, subs="attributes"] ---- javax.servlet;version="[3,4)", javax.servlet.http;version="[3,4)", diff --git a/topics/oidc/java/fuse/classic-war.adoc b/topics/oidc/java/fuse/classic-war.adoc index 7ff0bfc98e..29f72a02d5 100644 --- a/topics/oidc/java/fuse/classic-war.adoc +++ b/topics/oidc/java/fuse/classic-war.adoc @@ -70,7 +70,7 @@ recommended to use `maven-bundle-plugin` in your project to properly generate OS Note that "*" resolution for package doesn't import `org.keycloak.adapters.jetty` package as it's not used by application or Blueprint or Spring descriptor, but it's used just in `jetty-web.xml` file. So list of the packages to import may look like this: -[source] +[source, subs="attributes"] ---- org.keycloak.adapters.jetty;version="{{book.project.version}}", org.keycloak.adapters;version="{{book.project.version}}", diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/topics/oidc/java/fuse/cxf-builtin.adoc index ff4211a8c5..3ad7164805 100644 --- a/topics/oidc/java/fuse/cxf-builtin.adoc +++ b/topics/oidc/java/fuse/cxf-builtin.adoc @@ -83,7 +83,8 @@ control over security for each application individually. * The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: -[source] + +[source, subs="attributes"] ---- META-INF.cxf;version="[2.7,3.2)", META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional, diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/topics/oidc/java/fuse/cxf-separate.adoc index 86e12b76a1..b2d84e97a0 100644 --- a/topics/oidc/java/fuse/cxf-separate.adoc +++ b/topics/oidc/java/fuse/cxf-separate.adoc @@ -95,7 +95,8 @@ injected `KeycloakJettyAuthenticator` inside. The configuration may look like th * The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain those imports: -[source] + +[source, subs="attributes"] ---- META-INF.cxf;version="[2.7,3.2)", META-INF.cxf.osgi;version="[2.7,3.2)";resolution:=optional, diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index a3af44346b..363b737bac 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -40,10 +40,11 @@ This file contains configuration of the client application, which is used by JAA * Start Fuse and install `keycloak` JAAS realm into Fuse. This could be done easily by installing `keycloak-jaas` feature, which has JAAS realm predefined (you are able to override it by using your own `keycloak` JAAS realm with higher ranking). Use those commands in Fuse terminal: -``` +[source, subs="attributes"] +---- features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features features:install keycloak-jaas -``` +---- * Now let's type this from your terminal to login via SSH as `admin` user: diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc index 2b979a76f0..8452f73105 100644 --- a/topics/oidc/java/fuse/servlet-whiteboard.adoc +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -65,7 +65,8 @@ The needed steps to secure your servlet with {{book.project.name}} are: Note you don't need `web.xml` as the security-constrains are declared in blueprint configuration file. * The `Import-Package` in `META-INF/MANIFEST.MF` needs to contain at least those imports: -[source] + +[source, subs="attributes"] ---- org.keycloak.adapters.jetty;version="{{book.project.version}}", org.keycloak.adapters;version="{{book.project.version}}", diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 12882d5daa..295bbe1d35 100755 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -23,7 +23,7 @@ Adapters are available as a separate archive and are also available as Maven art {% if book.community %} Install on Wildfly 9 or 10: -[source,bash] +[source, subs="attributes"] ---- $ cd $WILDFLY_HOME $ unzip keycloak-wildfly-adapter-dist-{{book.project.version}}.zip @@ -31,7 +31,7 @@ $ unzip keycloak-wildfly-adapter-dist-{{book.project.version}}.zip Install on Wildfly 8: -[source,bash] +[source, subs="attributes"] ---- $ cd $WILDFLY_HOME $ unzip keycloak-wf8-adapter-dist-{{book.project.version}}.zip @@ -39,7 +39,7 @@ $ unzip keycloak-wf8-adapter-dist-{{book.project.version}}.zip Install on JBoss EAP 7: -[source] +[source, subs="attributes"] ---- $ cd $EAP_HOME $ unzip keycloak-eap7-adapter-dist-{{book.project.version}}.zip @@ -47,7 +47,7 @@ $ unzip keycloak-eap7-adapter-dist-{{book.project.version}}.zip Install on JBoss EAP 6: -[source] +[source, subs="attributes"] ---- $ cd $EAP_HOME $ unzip keycloak-eap6-adapter-dist-{{book.project.version}}.zip @@ -55,7 +55,7 @@ $ unzip keycloak-eap6-adapter-dist-{{book.project.version}}.zip Install on JBoss AS 7.1: -[source] +[source, subs="attributes"] ---- $ cd $JBOSS_HOME $ unzip keycloak-as7-adapter-dist-{{book.project.version}}.zip @@ -65,7 +65,7 @@ $ unzip keycloak-as7-adapter-dist-{{book.project.version}}.zip {% if book.product %} Install on JBoss EAP 7: -[source] +[source, subs="attributes"] ---- $ cd $EAP_HOME $ unzip rh-sso-{{book.project.version}}-eap7-adapter.zip @@ -73,7 +73,7 @@ $ unzip rh-sso-{{book.project.version}}-eap7-adapter.zip Install on JBoss EAP 6: -[source] +[source, subs="attributes"] ---- $ cd $EAP_HOME $ unzip rh-sso-{{book.project.version}}-eap6-adapter.zip From cc8f590ffffac1f77df9d3ec26312b6c30d4a61b Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:55:05 +0200 Subject: [PATCH 065/194] Update topics/oidc/java/fuse/fuse-admin.adoc --- topics/oidc/java/fuse/fuse-admin.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index 363b737bac..78b7c9983d 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -7,7 +7,7 @@ {{book.project.name}} mainly addresses usecases for authentication of web applications, however if your other web services and applications are protected with {{book.project.name}}, it may be good to protect non-web admin services like SSH with {{book.project.name}} credentials too. It's possible to do it by using JAAS login module, which allows to remotely connect to {{book.project.name}} and verify credentials based on -<> . +<>. Example steps for enable SSH authentication: From ba16aeee5f34a5071c07dc9f11e562083a0cc66f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:57:50 +0200 Subject: [PATCH 066/194] Update topics/oidc/java/jboss-adapter.adoc --- topics/oidc/java/jboss-adapter.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) mode change 100755 => 100644 topics/oidc/java/jboss-adapter.adoc diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc old mode 100755 new mode 100644 index 295bbe1d35..79669df63e --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -268,8 +268,7 @@ The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.name}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. It provides an example XML file you can cut and paste. -There is an additional convenience format for this XML if you have multiple WARs you are deployment that are secured by the same domain. -This format allows you to define common configuration items in one place under the `realm` element. +If you have multiple deployments secured by the same realm you can share the realm configuration in a separate element. For example: [source,xml] ---- From 1d81707ad9e90dc7ce5f70211e0b43371ffe311c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 14:58:36 +0200 Subject: [PATCH 067/194] Update topics/oidc/java/jboss-adapter.adoc --- topics/oidc/java/jboss-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 79669df63e..e85f8000e3 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -265,7 +265,7 @@ The rest of the configuration corresponds pretty much one to one with the `keycl The exception is the `credential` element. -To make it easier for you, you can go to the {{book.project.name}} Administration Console and go to the Application/Installation tab of the application this WAR is aligned with. +To make it easier for you, you can go to the {{book.project.name}} Administration Console and go to the Client/Installation tab of the application this WAR is aligned with. It provides an example XML file you can cut and paste. If you have multiple deployments secured by the same realm you can share the realm configuration in a separate element. For example: From 060ccb64ad75d7d5b0875cb92199a869b4ded951 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 15:03:16 +0200 Subject: [PATCH 068/194] Update topics/oidc/java/jboss-adapter.adoc --- topics/oidc/java/jboss-adapter.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index e85f8000e3..0d8e5577bd 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -259,9 +259,7 @@ This metadata is instead defined within server configuration (i.e. `standalone.x ---- The `secure-deployment` `name` attribute identifies the WAR you want to secure. -Its value is the `module-name` defined in `web.xml` with `.war` appended. - -The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <>. +Its value is the `module-name` defined in `web.xml` with `.war` appended. The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <>. The exception is the `credential` element. From fba771b1e3bfe62909b12b08a5d06c9dc1c12d21 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 9 Jun 2016 15:12:10 +0200 Subject: [PATCH 069/194] Fix headers in oidc chapter --- topics/oidc/java/adapter-context.adoc | 2 +- topics/oidc/java/adapter_error_handling.adoc | 2 +- topics/oidc/java/application-clustering.adoc | 12 ++--- topics/oidc/java/fuse-adapter.adoc | 4 +- topics/oidc/java/fuse/camel.adoc | 2 +- topics/oidc/java/fuse/classic-war.adoc | 2 +- topics/oidc/java/fuse/cxf-builtin.adoc | 2 +- topics/oidc/java/fuse/cxf-separate.adoc | 2 +- topics/oidc/java/fuse/fuse-admin.adoc | 8 ++-- topics/oidc/java/fuse/servlet-whiteboard.adoc | 2 +- topics/oidc/java/jaas.adoc | 2 +- topics/oidc/java/java-adapter-config.adoc | 2 +- topics/oidc/java/java-adapters.adoc | 2 +- topics/oidc/java/jboss-adapter.adoc | 10 ++-- topics/oidc/java/jetty8-adapter.adoc | 6 +-- topics/oidc/java/jetty9-adapter.adoc | 6 +-- topics/oidc/java/logout.adoc | 2 +- topics/oidc/java/multi-tenancy.adoc | 2 +- topics/oidc/java/servlet-filter-adapter.adoc | 2 +- topics/oidc/java/spring-boot-adapter.adoc | 6 +-- topics/oidc/java/spring-security-adapter.adoc | 18 +++---- topics/oidc/java/tomcat-adapter.adoc | 6 +-- topics/oidc/javascript-adapter.adoc | 48 +++++++++---------- topics/oidc/mod-auth-openidc.adoc | 2 +- topics/oidc/oidc-generic.adoc | 32 ++++++------- 25 files changed, 92 insertions(+), 92 deletions(-) diff --git a/topics/oidc/java/adapter-context.adoc b/topics/oidc/java/adapter-context.adoc index eaa2835338..92f7c8b834 100644 --- a/topics/oidc/java/adapter-context.adoc +++ b/topics/oidc/java/adapter-context.adoc @@ -1,4 +1,4 @@ -=== Security Context +==== Security Context The `KeycloakSecurityContext` interface is available if you need to access to the tokens directly. This could be useful if you want to retrieve additional details from the token (such as user profile information) or you want to invoke a RESTful service that is protected by {{book.project.name}}. diff --git a/topics/oidc/java/adapter_error_handling.adoc b/topics/oidc/java/adapter_error_handling.adoc index 04a7ca115d..594384b322 100644 --- a/topics/oidc/java/adapter_error_handling.adoc +++ b/topics/oidc/java/adapter_error_handling.adoc @@ -1,6 +1,6 @@ [[_adapter_error_handling]] -=== Error Handling +==== Error Handling {{book.project.name}} has some error handling facilities for servlet based client adapters. When an error is encountered in authentication, {{book.project.name}} will call `HttpServletResponse.sendError()`. diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index 515d68e14f..4f43429c68 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -1,5 +1,5 @@ [[_applicationclustering]] -=== Application Clustering +==== Application Clustering {% if book.community %} This chapter is related to supporting clustered applications deployed to JBoss EAP, WildFly and JBoss AS. @@ -18,7 +18,7 @@ There are a few options available depending on whether your application is: Dealing with clustering is not quite as simple as for a regular application. Mainly due to the fact that both the browser and the server-side application sends requests to {{book.project.name}}, so it's not as simple as enabling sticky sessions on your load balancer. -==== Stateless token store +===== Stateless token store By default, the web application secured by {{book.project.name}} uses the HTTP session to store security context. This means that you either have to enable sticky sessions or replicate the HTTP session. @@ -40,7 +40,7 @@ Another small limitation is limited support for Single-Sign Out. It works withou application itself as the adapter will delete the KEYCLOAK_ADAPTER_STATE cookie. However, back-channel logout initialized from a different application isn't propagated by {{book.project.name}} to applications using cookie store. Hence it's recommended to use a short value for the access token timeout (for example 1 minute). -==== Relative URI optimization +===== Relative URI optimization In deployment scenarios where {{book.project.name}} and the application is hosted on the same domain (through a reverse proxy or load balancer) it can be convenient to use relative URI options in your client configuration. @@ -50,7 +50,7 @@ With relative URIs the URI is resolved as relative to the URL of the URL used to For example if the URL to your application is `https://acme.org/myapp` and the URL to {{book.project.name}} is `https://acme.org/auth`, then you can use the redirect-uri `/myapp` instead of `https://acme.org/myapp`. -==== Admin URL configuration +===== Admin URL configuration Admin URL for a particular client can be configured in the {{book.project.name}} Administration Console. It's used by the {{book.project.name}} server to send backend requests to the application for various tasks, like logout users or push revocation policies. @@ -66,7 +66,7 @@ For example the way backchannel logout works is: If admin URL contains `${application.session.host}` it will be replaced with the URL to the node associated with the HTTP session. [[_registration_app_nodes]] -==== Registration of application nodes +===== Registration of application nodes The previous section describes how {{book.project.name}} can send logout request to node associated with a specific HTTP session. However, in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them. @@ -98,7 +98,7 @@ the adapter configuration). You can also manually add and remove cluster nodes i on the automatic registration feature or if you want to remove stale application nodes in the event your not using the automatic unregistration feature. [[_refresh_token_each_req]] -==== Refresh token in each request +===== Refresh token in each request By default the application adapter will only refresh the access token when it's expired. However, you can also configure the adapter to refresh the token on every request. This may have a performance impact as your application will send more requests to the {{book.project.name}} server. diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index f00dcefb33..b8e8ca6a4b 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter]] -=== JBoss Fuse Adapter +==== JBoss Fuse Adapter NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported @@ -17,7 +17,7 @@ What is supported for Fuse is: * Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet. * Security for SSH and JMX admin access. -==== How to secure your web applications inside Fuse +===== How to secure your web applications inside Fuse Basically all mentioned web applications require to inject {{book.project.name}} Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different according to application type. The details are described in individual sub-chapters. diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index 092f1deef0..c9de37ab4c 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter_camel]] -==== Apache Camel Application +===== Apache Camel Application * You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] component by adding securityHandler with `KeycloakJettyAuthenticator` and proper security constraints injected. You can add file `OSGI-INF/blueprint/blueprint.xml` into your camel application with the configuration similar to below. diff --git a/topics/oidc/java/fuse/classic-war.adoc b/topics/oidc/java/fuse/classic-war.adoc index 29f72a02d5..980c791425 100644 --- a/topics/oidc/java/fuse/classic-war.adoc +++ b/topics/oidc/java/fuse/classic-war.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter_classic_war]] -==== Secure Classic WAR application +===== Secure Classic WAR application The needed steps to secure your WAR are: diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/topics/oidc/java/fuse/cxf-builtin.adoc index 3ad7164805..99a7f1761e 100644 --- a/topics/oidc/java/fuse/cxf-builtin.adoc +++ b/topics/oidc/java/fuse/cxf-builtin.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter_cxf_builtin]] -==== Secure Apache CXF Endpoint on default Jetty Engine +===== Secure Apache CXF Endpoint on default Jetty Engine Some services automatically come with deployed servlets on startup. One of such services is CXF servlet running on http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which {{book.project.name}} is currently using, diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/topics/oidc/java/fuse/cxf-separate.adoc index b2d84e97a0..925bec9c77 100644 --- a/topics/oidc/java/fuse/cxf-separate.adoc +++ b/topics/oidc/java/fuse/cxf-separate.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter_cxf_separate]] -==== Secure Apache CXF Endpoint on separate Jetty +===== Secure Apache CXF Endpoint on separate Jetty It's recommended to run your CXF endpoints secured by {{book.project.name}} on separate Jetty engine. This is the setup described in this section. diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index 78b7c9983d..48410203c4 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -1,8 +1,8 @@ [[_fuse_adapter_admin]] -==== Secure Fuse Admin Services +===== Secure Fuse Admin Services -===== SSH authentication to Fuse terminal +====== SSH authentication to Fuse terminal {{book.project.name}} mainly addresses usecases for authentication of web applications, however if your other web services and applications are protected with {{book.project.name}}, it may be good to protect non-web admin services like SSH with {{book.project.name}} credentials too. It's possible to do it @@ -55,7 +55,7 @@ ssh -o PubkeyAuthentication=no -p 8101 admin@localhost And login with password `password`. Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` -===== JMX authentication +====== JMX authentication This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default. @@ -83,6 +83,6 @@ may be still able to access MBeans remotely via HTTP (Hawtio). So make sure to p really protect JMX mbeans. -===== Secure Fuse admin console +====== Secure Fuse admin console Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with {{book.project.name}}. \ No newline at end of file diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc index 8452f73105..cf7e122784 100644 --- a/topics/oidc/java/fuse/servlet-whiteboard.adoc +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -1,6 +1,6 @@ [[_fuse_adapter_servlet_whiteboard]] -==== Secure Servlet deployed as OSGI service +===== Secure Servlet deployed as OSGI service This is useful for the case, when you have sevlet class inside your OSGI bundle project, which is not deployed as classic WAR. Fuse uses https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+Whiteboard[Pax Web Whiteboard Extender] for deploy such servlet as web application. diff --git a/topics/oidc/java/jaas.adoc b/topics/oidc/java/jaas.adoc index d95e5ccfbb..19e21dd118 100644 --- a/topics/oidc/java/jaas.adoc +++ b/topics/oidc/java/jaas.adoc @@ -1,5 +1,5 @@ [[_jaas_adapter]] -=== JAAS plugin +==== JAAS plugin It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters. However, some applications and systems may still rely on pure legacy JAAS solution. diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 03000d5a58..d30347cf66 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -1,6 +1,6 @@ [[_java_adapter_config]] -=== Java Adapter Config +==== Java Adapter Config Each Java adapter supported by {{book.project.name}} can be configured by a simple JSON file. This is what one might look like: diff --git a/topics/oidc/java/java-adapters.adoc b/topics/oidc/java/java-adapters.adoc index 4aea42c559..091505c8a4 100644 --- a/topics/oidc/java/java-adapters.adoc +++ b/topics/oidc/java/java-adapters.adoc @@ -1,4 +1,4 @@ -== Java Adapters +=== Java Adapters {{book.project.name}} comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform. diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 0d8e5577bd..a8284859fb 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -1,10 +1,10 @@ [[_jboss_adapter]] {% if book.community %} -=== JBoss EAP/Wildfly Adapter +==== JBoss EAP/Wildfly Adapter {% endif %} {% if book.product %} -=== JBoss EAP Adapter +==== JBoss EAP Adapter {% endif %} To be able to secure WAR apps deployed on JBoss EAP {% if book.community %}, WildFly or JBoss AS{% endif %}, you must install and configure the @@ -16,7 +16,7 @@ Alternatively, you don't have to modify your WAR at all and you can secure it vi Both methods are described in this section. [[_jboss_adapter_installation]] -==== Adapter Installation +===== Adapter Installation Adapters are available as a separate archive and are also available as Maven artifacts. @@ -169,7 +169,7 @@ public class CustomerService { } ---- -==== Required Per WAR Configuration +===== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. @@ -231,7 +231,7 @@ Here's an example: ---- -==== Securing WARs via Adapter Subsystem +===== Securing WARs via Adapter Subsystem You do not have to modify your WAR to secure it with {{book.project.name}}. Instead you can externally secure it via the {{book.project.name}} Adapter Subsystem. While you don't have to specify KEYCLOAK as an `auth-method`, you still have to define the `security-constraints` in `web.xml`. diff --git a/topics/oidc/java/jetty8-adapter.adoc b/topics/oidc/java/jetty8-adapter.adoc index 1c18438e2f..9aacb75473 100755 --- a/topics/oidc/java/jetty8-adapter.adoc +++ b/topics/oidc/java/jetty8-adapter.adoc @@ -1,13 +1,13 @@ [[_jetty8_adapter]] -=== Jetty 8.1.x Adapter +==== Jetty 8.1.x Adapter Keycloak has a separate adapter for Jetty 8.1.x that you will have to install into your Jetty installation. You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. [[_jetty8_adapter_installation]] -==== Adapter Installation +===== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. They are also available as a maven artifact. @@ -40,7 +40,7 @@ Edit start.ini and add keycloak to the options OPTIONS=Server,jsp,jmx,resources,websocket,ext,plus,annotations,keycloak ---- -==== Required Per WAR Configuration +===== Required Per WAR Configuration Enabling Keycloak for your WARs is the same as the Jetty 9.x adapter. Our 8.1.x adapter supports both keycloak.json and the jboss-web.xml advanced configuration. diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index bcad9713c5..70946313a6 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -1,13 +1,13 @@ [[_jetty9_adapter]] -=== Jetty 9.x Adapters +==== Jetty 9.x Adapters Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will have to install into your Jetty installation. You then have to provide some extra configuration in each WAR you deploy to Jetty. Let's go over these steps. [[_jetty9_adapter_installation]] -==== Adapter Installation +===== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. They are also available as a maven artifact. @@ -34,7 +34,7 @@ $ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak ---- [[_jetty9_per_war]] -==== Required Per WAR Configuration +===== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. diff --git a/topics/oidc/java/logout.adoc b/topics/oidc/java/logout.adoc index b56c9b4c90..5ab7487c24 100755 --- a/topics/oidc/java/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -1,4 +1,4 @@ -=== Logout +==== Logout There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can redirect the browser to diff --git a/topics/oidc/java/multi-tenancy.adoc b/topics/oidc/java/multi-tenancy.adoc index 5992d2578f..120218e5b0 100755 --- a/topics/oidc/java/multi-tenancy.adoc +++ b/topics/oidc/java/multi-tenancy.adoc @@ -1,5 +1,5 @@ -=== Multi Tenancy +==== Multi Tenancy Multi Tenancy, in our context, means that a single target application (WAR) can be secured with multiple {{book.project.name}} realms. The realms can be located one the same {{book.project.name}} instance or on different instances. diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index d23023b867..2ddd90f00a 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -1,5 +1,5 @@ [[_servlet_filter_adapter]] -=== Java Servlet Filter Adapter +==== Java Servlet Filter Adapter If you are deploying your Java Servlet application on a platform where there is no {{book.project.name}} adapter you opt to use the servlet filter adapter. This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml. diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index 87f59a036d..508f52cee1 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -1,11 +1,11 @@ [[_spring_boot_adapter]] -=== Spring Boot Adapter +==== Spring Boot Adapter To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app. You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`). Let's go over these steps. [[_spring_boot_adapter_installation]] -==== Adapter Installation +===== Adapter Installation The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all you need to do is add the Keycloak Spring Boot adapter JAR to your project. Depending on what container you are using with Spring Boot, you also need to add the appropriate Keycloak container adapter. @@ -29,7 +29,7 @@ If you are using Maven, add the following to your pom.xml (using Tomcat as an ex ---- [[_spring_boot_adapter_configuration]] -==== Required Spring Boot Adapter Configuration +===== Required Spring Boot Adapter Configuration This section describes how to configure your Spring Boot app to use Keycloak. diff --git a/topics/oidc/java/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc index d0a895caca..5c8c34db13 100755 --- a/topics/oidc/java/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -1,5 +1,5 @@ [[_spring_security_adapter]] -=== Spring Security Adapter +==== Spring Security Adapter To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project. You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline. @@ -7,7 +7,7 @@ You then have to provide some extra beans in your Spring Security configuration Unlike the other Keycloak Adapters, you should not configure your security in web.xml. However, keycloak.json is still required. -==== Adapter Installation +===== Adapter Installation Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. @@ -23,11 +23,11 @@ Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle ---- -==== Spring Security Configuration +===== Spring Security Configuration The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax. -===== Java Configuration +====== Java Configuration Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a http://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/web/WebSecurityConfigurer.html[WebSecurityConfigurer] instance. The implementation allows customization by overriding methods. @@ -78,7 +78,7 @@ You must provide a session authentication strategy bean which should be of type Spring Security's `SessionFixationProtectionStrategy` is currently not supported because it changes the session identifier after login via Keycloak. If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier. -===== XML Configuration +====== XML Configuration While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose. @@ -148,13 +148,13 @@ While Spring Security's XML namespace simplifies configuration, customizing the ---- -==== Multi Tenancy +===== Multi Tenancy The Keycloak Spring Security adapter also supports multi tenancy. Instead of injecting `AdapterDeploymentContextFactoryBean` with the path to `keycloak.json` you can inject an implementation of the `KeycloakConfigResolver` interface. More details on how to implement the `KeycloakConfigResolver` can be found in <>. -==== Naming Security Roles +===== Naming Security Roles Spring Security, when using role-based authentication, requires that role names start with `ROLE_`. For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`. @@ -163,7 +163,7 @@ The class `org.keycloak.adapters.springsecurity.authentication.KeycloakAuthentic Use, for example, `org.springframework.security.core.authority.mapping.SimpleAuthorityMapper` to insert the `ROLE_` prefix and convert the role name to upper case. The class is part of Spring Security Core module. -==== Client to Client Support +===== Client to Client Support To simplify communication between clients, Keycloak provides an extension of Spring's `RestTemplate` that handles bearer token authentication for you. To enable this feature your security configuration must add the `KeycloakRestTemplate` bean. @@ -227,7 +227,7 @@ public class RemoteProductService implements ProductService { } ---- -==== Spring Boot Configuration +===== Spring Boot Configuration Spring Boot attempts to eagerly register filter beans with the web application context. Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add two ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice. diff --git a/topics/oidc/java/tomcat-adapter.adoc b/topics/oidc/java/tomcat-adapter.adoc index 096dcc3824..4d646ca7be 100755 --- a/topics/oidc/java/tomcat-adapter.adoc +++ b/topics/oidc/java/tomcat-adapter.adoc @@ -1,13 +1,13 @@ [[_tomcat_adapter]] -=== Tomcat 6, 7 and 8 Adapters +==== Tomcat 6, 7 and 8 Adapters To be able to secure WAR apps deployed on Tomcat 6, 7 and 8 you must install the Keycloak Tomcat 6, 7 or 8 adapter into your Tomcat installation. You then have to provide some extra configuration in each WAR you deploy to Tomcat. Let's go over these steps. [[_tomcat_adapter_installation]] -==== Adapter Installation +===== Adapter Installation Adapters are no longer included with the appliance or war distribution. Each adapter is a separate download on the Keycloak download site. @@ -28,7 +28,7 @@ $ unzip keycloak-tomcat7-adapter-dist.zip $ unzip keycloak-tomcat8-adapter-dist.zip ---- -==== Required Per WAR Configuration +===== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 206d1cd138..cd8665d6aa 100755 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -1,5 +1,5 @@ [[_javascript_adapter]] -== Javascript Adapter +=== Javascript Adapter {{book.project.name}} comes with a client-side JavaScript library that can be used to secure HTML5/JavaScript applications. The JavaScript adapter has built-in support for Cordova applications. @@ -109,7 +109,7 @@ keycloak.updateToken(30).success(function() { ); ---- -== Session status iframe +==== Session status iframe By default, the JavaScript adapter creates a hidden iframe that is used to detect if a Single-Sign Out has occurred. This does not require any network traffic, instead the status is retrieved by looking at a special status cookie. @@ -119,7 +119,7 @@ You should not rely on looking at this cookie directly. It's format can change a your application. [[_javascript_implicit_flow]] -== Implicit and Hybrid Flow +==== Implicit and Hybrid Flow By default, the JavaScript adapter uses the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code] flow. With this flow the {{book.project.name}} server returns a authorization code, not a authentication token, to the application. The JavaScript adapter exchanges @@ -160,7 +160,7 @@ For the Hybrid flow, you need to pass the parameter `flow` with value `hybrid` t keycloak.init({ flow: 'hybrid' }) ---- -== Older browsers +==== Older browsers The JavaScript adapter depends on Base64 (window.btoa and window.atob) and HTML5 History API. If you need to support browsers that don't have these available (for example IE9) you need to add polyfillers. @@ -170,9 +170,9 @@ Example polyfill libraries: * https://github.com/davidchambers/Base64.js * https://github.com/devote/HTML5-History-API -== JavaScript Adapter reference +==== JavaScript Adapter reference -=== Constructor +===== Constructor [source] ---- @@ -182,7 +182,7 @@ new Keycloak('http://localhost/keycloak.json'); new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' }); ---- -=== Properties +===== Properties authenticated:: Is `true` if the user is authenticated, `false` otherwise. @@ -227,9 +227,9 @@ flow:: responseType:: Response type sent to {{book.project.name}} with login requests. This is determined based on the flow value used during initialization, but can be overridden by setting this value. -=== Methods +===== Methods -==== init(options) +====== init(options) Called to initialize the adapter. @@ -247,7 +247,7 @@ Options is an Object, where: Returns promise to set functions to be invoked on success or error. -==== login(options) +====== login(options) Redirects to login form on (options is an optional object with redirectUri and/or prompt fields). @@ -259,7 +259,7 @@ Options is an Object, where: * action - If value is 'register' then user is redirected to registration page, otherwise to login page. * locale - Specifies the desired locale for the UI. -==== createLoginUrl(options) +====== createLoginUrl(options) Returns the URL to login form on (options is an optional object with redirectUri and/or prompt fields). @@ -268,7 +268,7 @@ Options is an Object, where: * redirectUri - Specifies the uri to redirect to after login. * prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed). -==== logout(options) +====== logout(options) Redirects to logout. @@ -276,7 +276,7 @@ Options is an Object, where: * redirectUri - Specifies the uri to redirect to after logout. -==== createLogoutUrl(options) +====== createLogoutUrl(options) Returns the URL to logout the user. @@ -284,45 +284,45 @@ Options is an Object, where: * redirectUri - Specifies the uri to redirect to after logout. -==== register(options) +====== register(options) Redirects to registration form. Shortcut for login with option action = 'register' Options are same as login method but 'action' is set to 'register' -==== createRegisterUrl(options) +====== createRegisterUrl(options) Returns the url to registration page. Shortcut for createLoginUrl with option action = 'register' Options are same as createLoginUrl method but 'action' is set to 'register' -==== accountManagement() +====== accountManagement() Redirects to the Account Management Console. -==== createAccountUrl() +====== createAccountUrl() Returns the URL to the Account Management Console. -==== hasRealmRole(role) +====== hasRealmRole(role) Returns true if the token has the given realm role. -==== hasResourceRole(role, resource) +====== hasResourceRole(role, resource) Returns true if the token has the given role for the resource (resource is optional, if not specified clientId is used). -==== loadUserProfile() +====== loadUserProfile() Loads the users profile. Returns promise to set functions to be invoked on success or error. -==== isTokenExpired(minValidity) +====== isTokenExpired(minValidity) Returns true if the token has less than minValidity seconds left before it expires (minValidity is optional, if not specified 0 is used). -==== updateToken(minValidity) +====== updateToken(minValidity) If the token expires within minValidity seconds (minValidity is optional, if not specified 0 is used) the token is refreshed. If the session status iframe is enabled, the session status is also checked. @@ -343,14 +343,14 @@ keycloak.updateToken(5).success(function(refreshed) { }); ---- -==== clearToken() +====== clearToken() Clear authentication state, including tokens. This can be useful if application has detected the session was expired, for example if updating token fails. Invoking this results in onAuthLogout callback listener being invoked. -=== Callback Events +===== Callback Events The adapter supports setting callback listeners for certain events. diff --git a/topics/oidc/mod-auth-openidc.adoc b/topics/oidc/mod-auth-openidc.adoc index 25e03be59a..f80d67b0e5 100644 --- a/topics/oidc/mod-auth-openidc.adoc +++ b/topics/oidc/mod-auth-openidc.adoc @@ -1,5 +1,5 @@ [[_mod_auth_openidc]] -=== mod_auth_openidc Apache HTTPD Module +==== mod_auth_openidc Apache HTTPD Module The https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc] is an Apache HTTP plugin for OpenID Connect. If your language/environment supports using Apache HTTPD as a proxy, then you can use _mod_auth_openidc_ to secure your web application with OpenID Connect. Configuration of this module diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index b16fb3783f..b1fb818162 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -1,11 +1,11 @@ -== Other OpenID Connect libraries +=== Other OpenID Connect libraries {{book.project.name}} can be secured by supplied adapters that usually are easier to use and provide better integration with {{book.project.name}}. However, if there is no adapter available for your programming language, framework or platform you may opt to use a generic OpenID Connect Resource Provider (RP) library instead. This chapter describes details specific to {{book.project.name}} and doesn't go into low-level details of the protocols. For more details refer to the http://openid.net/connect/[OpenID Connect specifications] and https://tools.ietf.org/html/rfc6749[OAuth2 specification]. -=== Endpoints +==== Endpoints The most important endpoint to know is the `well-known` configuration endpoint. It lists endpoints and other configuration options relevant to the OpenID Connect implementation in {{book.project.name}}. The endpoint is: @@ -20,7 +20,7 @@ http://localhost:8080/auth/realms/master/.well-known/openid-configuration Some RP libraries will retrieve all required endpoints from this endpoint, but for others you may need to list the endpoints individually. -==== Authorization Endpoint +===== Authorization Endpoint .... /realms/master/protocol/openid-connect/auth .... @@ -29,7 +29,7 @@ Performs authentication of the end-user. This is done by redirecting user agent For more details see http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[Authorization Endpoint] section in OpenID Connect specification. -==== Token Endpoint +===== Token Endpoint .... /realms/master/protocol/openid-connect/token .... @@ -39,7 +39,7 @@ The token endpoint is also used to obtain new access tokens when they expire. For more details see http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint[Token Endpoint] section in OpenID Connect specification. -==== Userinfo Endpoint +===== Userinfo Endpoint .... /realms/master/protocol/openid-connect/userinfo .... @@ -48,7 +48,7 @@ Returns standard claims about the authenticated user. Protected by a bearer toke For more details see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[Userinfo Endpoint] section in OpenID Connect specification. -==== Logout Endpoint +===== Logout Endpoint .... /realms/master/protocol/openid-connect/logout .... @@ -60,7 +60,7 @@ User agent can be redirected to the endpoint in which case the active user sessi The endpoint can also be invoked directly by the application. To invoke this endpoint directly the refresh token needs to be included as well as credentials required to authenticate the client. -==== Certificate Endpoint +===== Certificate Endpoint .... /realms/master/protocol/openid-connect/certs .... @@ -70,7 +70,7 @@ the server. For more details see https://tools.ietf.org/html/rfc7517[JSON Web Key specification]. -==== Introspection Endpoint +===== Introspection Endpoint .... /realms/master/protocol/openid-connect/token/introspect .... @@ -79,7 +79,7 @@ Used to retrieve the active state of a token. Protected by a bearer token and ca For more details see https://tools.ietf.org/html/rfc7662[OAuth 2.0 Token Introspection specification]. -==== Dynamic Client Registration Endpoint +===== Dynamic Client Registration Endpoint .... /realms/master/clients-registrations/openid-connect .... @@ -90,9 +90,9 @@ For more details see < Date: Thu, 9 Jun 2016 19:38:33 +0200 Subject: [PATCH 070/194] KEYCLOAK-2028 : Docs for new adapter option minimum-token-time-to-live --- topics/oidc/java/java-adapter-config.adoc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index d30347cf66..8fcad0d57b 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -31,7 +31,8 @@ This is what one might look like: "truststore-password" : "geheim", "client-keystore" : "path/to/client-keystore.jks", "client-keystore-password" : "geheim", - "client-key-password" : "geheim" + "client-key-password" : "geheim", + "token-minimum-time-to-live" : 10 } ---- @@ -190,3 +191,9 @@ principal-attribute:: turn-off-change-session-id-on-login:: The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true if you want to turn this off This is _OPTIONAL_. The default value is _false_. + +token-minimum-time-to-live:: + Amount of time, in seconds, to preemptively refresh an active access token with the {{book.project.name}} server before it expires. + This is especially useful when the access token is sent to another REST client where it could expire before being evaluated. + This value should never exceed the realm's access token lifespan. + This is _OPTIONAL_. The default value is `0` seconds, so adapter will refresh access token just if it's expired. From f91042b31c551de9e80c1c81bd89d0d569afe395 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:03:41 +0200 Subject: [PATCH 071/194] Update topics/oidc/java/jboss-adapter.adoc --- topics/oidc/java/jboss-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index a8284859fb..bd3ac4a377 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -99,7 +99,7 @@ is not running: [source] ---- -$ ./bin/jboss-cli.sh -c --file=adapter-install-offline.cli +$ ./bin/jboss-cli.sh --file=adapter-install-offline.cli ---- If you are planning to add it manually you need to add the extension and subsystem definition to the server configuration: From 2f7c465f5d8f17eb97196376f0edc7969f620f91 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:09:58 +0200 Subject: [PATCH 072/194] Update topics/oidc/java/jboss-adapter.adoc --- topics/oidc/java/jboss-adapter.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index bd3ac4a377..048643b843 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -94,8 +94,14 @@ $ ./bin/jboss-cli.sh -c --file=adapter-install.cli The script will add the required configuration to the server configuration file. -For JBoss EAP 7 {% if book.community %} and WildFly 9+{% endif %} there is also an offline CLI script that can be used to install the adapter while the server +{% if book.community %} +For JBoss EAP 7 and WildFly 9+ there is also an offline CLI script that can be used to install the adapter while the server is not running: +{% endif %} +{% if book.product %} +For JBoss EAP 7 there is also an offline CLI script that can be used to install the adapter while the server +is not running: +{% endif %} [source] ---- From 01c7318621d409674efa5f201d78b19e1a941ebc Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:13:14 +0200 Subject: [PATCH 073/194] Fix conditional in jboss adapter chapter --- topics/oidc/java/jboss-adapter.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 048643b843..e9036004d0 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -7,8 +7,14 @@ ==== JBoss EAP Adapter {% endif %} -To be able to secure WAR apps deployed on JBoss EAP {% if book.community %}, WildFly or JBoss AS{% endif %}, you must install and configure the +{% if book.community %} +To be able to secure WAR apps deployed on JBoss EAP, WildFly or JBoss AS, you must install and configure the {{book.project.name}} adapter subsystem. You then have two options to secure your WARs. +{% endif %} +{% if book.product %} +To be able to secure WAR apps deployed on JBoss EAP, you must install and configure the +{{book.project.name}} adapter subsystem. You then have two options to secure your WARs. +{% endif %} You can provide an adapter config file in your WAR and change the auth-method to KEYCLOAK within web.xml. From 9d1430e98ba62603d807c1d9cfa8efda261d0a6c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:17:30 +0200 Subject: [PATCH 074/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index d30347cf66..ba091c4fad 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -143,8 +143,8 @@ truststore:: Client making HTTPS requests need a way to verify the host of the server they are talking to. This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. - You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. - This is _OPTIONAL_ if `ssl-required` is `none` or `disable-trust-manager` is `true`. + You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. + This is _REQUIRED_ unless `ssl-required` is set to `none` or `disable-trust-manager` is `true`. truststore-password:: Password for the truststore keystore. From 26baf1076c30de8396147c5819363fdb8d91069a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:17:48 +0200 Subject: [PATCH 075/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index ba091c4fad..3f2a009daf 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -144,7 +144,7 @@ truststore:: This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. - This is _REQUIRED_ unless `ssl-required` is set to `none` or `disable-trust-manager` is `true`. + This is _REQUIRED_ unless `ssl-required` is `none` or `disable-trust-manager` is `true`. truststore-password:: Password for the truststore keystore. From 89ed2a3a7bfb28d5d60198368733d52df6362aef Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:19:07 +0200 Subject: [PATCH 076/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 3f2a009daf..0c375c9476 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -52,7 +52,7 @@ resource:: realm-public-key:: PEM format of the realm public key. You can obtain this from the administration console. - This is OPTION._ + This is _OPTIONAL_. If not set the adapter will download this from {{book.project.name}} on startup. auth-server-url:: The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `https://host:port/auth`. From 1b6e940f44a47fc492bd6381e2536ab1c4d9ffdd Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:19:12 +0200 Subject: [PATCH 077/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 0c375c9476..0a9d097cf2 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -52,7 +52,7 @@ resource:: realm-public-key:: PEM format of the realm public key. You can obtain this from the administration console. - This is _OPTIONAL_. If not set the adapter will download this from {{book.project.name}} on startup. + This is _OPTIONAL_. If not set the adapter will download this from {{book.project.name}}. auth-server-url:: The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `https://host:port/auth`. From b7d5704db9cdea284fc687a8b19f7515b3ed707d Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:20:34 +0200 Subject: [PATCH 078/194] Update topics/oidc/java/java-adapters.adoc --- topics/oidc/java/java-adapters.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/topics/oidc/java/java-adapters.adoc b/topics/oidc/java/java-adapters.adoc index 091505c8a4..697c559fad 100644 --- a/topics/oidc/java/java-adapters.adoc +++ b/topics/oidc/java/java-adapters.adoc @@ -2,5 +2,4 @@ {{book.project.name}} comes with a range of different adapters for Java application. Selecting the correct adapter depends on the target platform. -All Java adapters share a set of common configuration options described in the <> chapter. There are also -a few more chapters that are relevant to all Java adapters. \ No newline at end of file +All Java adapters share a set of common configuration options described in the <> chapter. \ No newline at end of file From 8d850ce0cfc250db53020cef1abcb76ac660d6ab Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:37:45 +0200 Subject: [PATCH 079/194] Update topics/overview/supported-protocols.adoc --- topics/overview/supported-protocols.adoc | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index c4cac293f6..91bcec83a8 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -39,20 +39,17 @@ is allowed to access on the application. The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks {{book.project.name}} to obtain an SAML assertion it can use to invoke on other remote services on behalf of the user. -=== OIDC vs. SAML +=== OpenID Connect vs. SAML + +Choosing between OpenID Connect and SAML is not just a matter of using a newer protocol (OIDC) instead of the older more mature protocol (SAML). + +In most cases {{book.project.name}} recommends using OIDC. -Choosing between OIDC and SAML is not just a matter of using a newer, sexier protocol (OIDC) instead of the old, mature, dinosaur (SAML). -{{book.project.name}} has chosen OIDC as the protocol we use to both recommend and write all our extensions on top of. SAML tends to be a bit more verbose than OIDC. -Beyond verbosity of exchanged data, if you compare the specifications you'll find that OIDC was designed to work with the -web while SAML was retrofitted to work on top of the web. For example, -OIDC is also much better suited for HTML5/JavaScript applications because it is -much much simpler to implement on the client side than SAML. Since tokens are in the JSON format, -they can be directly consumed by JavaScript. Also, you'll find many nice little switches and features that -make implementing security in your web applications easier. For example, check out the iframe trick that the specification -uses to easily determine if a user is still logged in or not. +Beyond verbosity of exchanged data, if you compare the specifications you'll find that OIDC was designed to work with the web while SAML was retrofitted to work on top of the web. For example, OIDC is also more suited for HTML5/JavaScript applications because it is +easier to implement on the client side than SAML. As tokens are in the JSON format, +they are easier to consume by JavaScript. You will also find several nice features that +make implementing security in your web applications easier. For example, check out the iframe trick that the specification uses to easily determine if a user is still logged in or not. -SAML has its uses though. As you see the OIDC specifications evolve you see they implement more and more features that -SAML has had for years. What we often see is that people pick SAML over OIDC because of the perception that it is more mature -and also because they already have existing applications that are secured by it. +SAML has its uses though. As you see the OIDC specifications evolve you see they implement more and more features that SAML has had for years. What we often see is that people pick SAML over OIDC because of the perception that it is more mature and also because they already have existing applications that are secured with it. From adbfed0f9a280fd259eae158a89b198f8b26d5a9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:39:27 +0200 Subject: [PATCH 080/194] Update topics/overview/supported-protocols.adoc --- topics/overview/supported-protocols.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 91bcec83a8..e97ba919b1 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -37,7 +37,7 @@ the realm and contains access information (like user role mappings) that the app is allowed to access on the application. The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks {{book.project.name}} -to obtain an SAML assertion it can use to invoke on other remote services on behalf of the user. +to obtain a SAML assertion it can use to invoke on other remote services on behalf of the user. === OpenID Connect vs. SAML From 2e1d42115933472c81dbcae8527cdbbada761c0a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:40:04 +0200 Subject: [PATCH 081/194] Update topics/overview/supported-protocols.adoc --- topics/overview/supported-protocols.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index e97ba919b1..67c473d070 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -32,7 +32,7 @@ In {{book.project.name}} SAML serves two types of use cases: browser application There is really two types of use cases when using SAML. The first is an application that asks the {{book.project.name}} server to authenticate a user for them. After a successful login, the application will receive an XML document that contains -something called a SAML assertion that specify various attributes about the user. This XML document is digitally signed by +something called a SAML assertion that specifies various attributes about the user. This XML document is digitally signed by the realm and contains access information (like user role mappings) that the application can use to determine what resources the user is allowed to access on the application. From 16ee626fe32f6b652c836d402404507580415a32 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:40:58 +0200 Subject: [PATCH 082/194] Update topics/overview/supported-protocols.adoc --- topics/overview/supported-protocols.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 67c473d070..5487750a00 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -25,8 +25,7 @@ the request. link:http://saml.xml.org/saml-specifications[SAML 2.0] is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora of WS-* specifications so it tends to be a bit more verbose than OIDC. SAML 2.0 is primarily an authentication protocol -that works by exchanging XML documents between the authentication server and the application. XML signatures and encryption -is used to verify requests and responses. +that works by exchanging XML documents between the authentication server and the application. XML signatures and encryption are used to verify requests and responses. In {{book.project.name}} SAML serves two types of use cases: browser applications and REST invocations. From 39c17f3449574a0331b7c57dcd80ae1e13e34dbc Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 06:44:48 +0200 Subject: [PATCH 083/194] Update topics/overview/supported-protocols.adoc --- topics/overview/supported-protocols.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 5487750a00..bcca21aa3d 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -5,7 +5,7 @@ link:http://openid.net/connect/[Open ID Connect] (OIDC) is an authentication protocol that is an extension of link:https://tools.ietf.org/html/rfc6749[OAuth 2.0]. While OAuth 2.0 is only a framework for building authorization protocols and is mainly incomplete, OIDC is a full-fledged authentication and authorization -protocol. OIDC also makes heavy use of the link:https://jwt.io[Json Web Token] (JWT) set of standards. These standards define a +protocol. OIDC also makes heavy use of the link:https://jwt.io[Json Web Token] (JWT) set of standards. These standards define an identity token JSON format and ways to digitally sign and encrypt that data in a compact and web-friendly way. There is really two types of use cases when using OIDC. The first is an application that asks the {{book.project.name}} server to authenticate From c3bc77206adba90f262666e2bd9802ead5c3c105 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 07:37:09 +0200 Subject: [PATCH 084/194] Fixes --- check.sh | 41 ++++++++++++++++++++ topics/oidc/java/application-clustering.adoc | 4 +- topics/oidc/java/fuse-adapter.adoc | 2 + topics/oidc/java/fuse/cxf-builtin.adoc | 2 +- topics/oidc/java/java-adapter-config.adoc | 2 +- topics/oidc/java/logout.adoc | 2 +- topics/oidc/oidc-generic.adoc | 4 +- topics/saml/java/idp-registration.adoc | 2 +- topics/saml/java/logout.adoc | 2 +- 9 files changed, 52 insertions(+), 9 deletions(-) create mode 100755 check.sh diff --git a/check.sh b/check.sh new file mode 100755 index 0000000000..0c012cb723 --- /dev/null +++ b/check.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +if [ "$1" == "" ]; then + DOC="target/master.html" +else + DOC="$1/target/master.html" +fi + +for i in `cat $DOC | grep -o -e 'href="[^"]*"' | cut -d '"' -f 2`; do + if ( echo $i | grep '^#' &>/dev/null ); then + i=`echo $i | sed 's/#//'` + if ( ! cat $DOC | grep "id=\"$i\"" &>/dev/null ); then + echo "Missing link: $i" + ERROR=1 + fi + else + if ( echo $i | grep 'redhat.com' &>/dev/null ); then + if ( ! curl --insecure -s $i | grep "attributes.set('Name'" &>/dev/null ); then + echo "Invalid link: $i" + ERROR=1 + fi + elif ( ! curl --output /dev/null --silent --head --fail "$i" --connect-timeout 2 ); then + echo "Invalid link: $i" + ERROR=1 + fi + fi +done + +if ( cat $DOC | grep ifeval &>/dev/null ); then + echo "Found ifeval in text" + ERROR=1 +fi + +for i in `cat $DOC | grep -o -e '{book_[^}]*}' | sed 's/{//' | sed 's/}//'`; do + echo "Invalid attribute: $i" + ERROR=1 +done + +if [ $ERROR ]; then + exit 1 +fi diff --git a/topics/oidc/java/application-clustering.adoc b/topics/oidc/java/application-clustering.adoc index 4f43429c68..798d7347e8 100644 --- a/topics/oidc/java/application-clustering.adoc +++ b/topics/oidc/java/application-clustering.adoc @@ -47,8 +47,8 @@ convenient to use relative URI options in your client configuration. With relative URIs the URI is resolved as relative to the URL of the URL used to access {{book.project.name}}. -For example if the URL to your application is `https://acme.org/myapp` and the URL to {{book.project.name}} is `https://acme.org/auth`, then you can use -the redirect-uri `/myapp` instead of `https://acme.org/myapp`. +For example if the URL to your application is `$$https://acme.org/myapp$$` and the URL to {{book.project.name}} is `$$https://acme.org/auth$$`, then you can use +the redirect-uri `/myapp` instead of `$$https://acme.org/myapp$$`. ===== Admin URL configuration diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index b8e8ca6a4b..53057c9282 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -5,8 +5,10 @@ NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] . +{% if book.community %} It leverages <> as both JBoss Fuse 6.2 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] under the covers and Jetty is used for running various kinds of web applications. +{% endif %} What is supported for Fuse is: diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/topics/oidc/java/fuse/cxf-builtin.adoc index 99a7f1761e..8fb2c01501 100644 --- a/topics/oidc/java/fuse/cxf-builtin.adoc +++ b/topics/oidc/java/fuse/cxf-builtin.adoc @@ -3,7 +3,7 @@ ===== Secure Apache CXF Endpoint on default Jetty Engine Some services automatically come with deployed servlets on startup. One of such services is CXF servlet running on -http://localhost:8181/cxf context. Securing such endpoints is quite tricky. The approach, which {{book.project.name}} is currently using, +$$http://localhost:8181/cxf$$ context. Securing such endpoints is quite tricky. The approach, which {{book.project.name}} is currently using, is providing ServletReregistrationService, which undeploys builtin servlet at startup, so you are able to re-deploy it again on context secured by {{book.project.name}}. This is how configuration file `OSGI-INF/blueprint/blueprint.xml` inside your application may look like. Note it adds JAX-RS `customerservice` endpoint, which is endpoint specific to your application, but more importantly, it secures whole `/cxf` context. diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 0a9d097cf2..6e700e0ed6 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -55,7 +55,7 @@ realm-public-key:: This is _OPTIONAL_. If not set the adapter will download this from {{book.project.name}}. auth-server-url:: - The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `https://host:port/auth`. + The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `$$https://host:port/auth$$`. This is _REQUIRED._ ssl-required:: diff --git a/topics/oidc/java/logout.adoc b/topics/oidc/java/logout.adoc index 5ab7487c24..ee285201ab 100755 --- a/topics/oidc/java/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -2,4 +2,4 @@ There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can redirect the browser to -`http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri`. This will log you out if you have a SSO session with your browser. \ No newline at end of file +`$$http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri$$`. This will log you out if you have a SSO session with your browser. \ No newline at end of file diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index b1fb818162..d4c589bb22 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -16,7 +16,7 @@ Connect implementation in {{book.project.name}}. The endpoint is: To get the full URL add the base URL for {{book.project.name}} and replace `REALM-NAME` with the name of your realm. For example: -http://localhost:8080/auth/realms/master/.well-known/openid-configuration +$$http://localhost:8080/auth/realms/master/.well-known/openid-configuration$$ Some RP libraries will retrieve all required endpoints from this endpoint, but for others you may need to list the endpoints individually. @@ -179,7 +179,7 @@ In production for web applications always use `https` for all redirect URIs. Do There's also a few special redirect URIs: [[_installed_applications_url]] -`http://localhost`:: +`$$http://localhost$$`:: This redirect URI is useful for native applications and allows the native application to create a web server on a random port that can be used to obtain the authorization code. This redirect uri allows any port. diff --git a/topics/saml/java/idp-registration.adoc b/topics/saml/java/idp-registration.adoc index 630a29ae32..2d59109c9b 100644 --- a/topics/saml/java/idp-registration.adoc +++ b/topics/saml/java/idp-registration.adoc @@ -2,4 +2,4 @@ ==== Registering with an IDP For each servlet based adapter, the endpoint you register for the assert consumer service url and and single logout service -must be the base url of your servlet application with `/saml` appended to it i.e. `https://example.com/contextPath/saml` +must be the base url of your servlet application with `/saml` appended to it i.e. `$$https://example.com/contextPath/saml$$` diff --git a/topics/saml/java/logout.adoc b/topics/saml/java/logout.adoc index 25528af316..b6a9ed82c2 100644 --- a/topics/saml/java/logout.adoc +++ b/topics/saml/java/logout.adoc @@ -2,5 +2,5 @@ There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call `HttpServletRequest.logout()`. For any other browser application, you can point -the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. `http://myapp?GLO=true`. +the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. `$$http://myapp?GLO=true$$`. This will log you out if you have an SSO session with your browser. From ffaf5d8bbde9d0cfaa5dfd866324a51c8ad4b0e2 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 07:40:15 +0200 Subject: [PATCH 085/194] Removed check script --- check.sh | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100755 check.sh diff --git a/check.sh b/check.sh deleted file mode 100755 index 0c012cb723..0000000000 --- a/check.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -if [ "$1" == "" ]; then - DOC="target/master.html" -else - DOC="$1/target/master.html" -fi - -for i in `cat $DOC | grep -o -e 'href="[^"]*"' | cut -d '"' -f 2`; do - if ( echo $i | grep '^#' &>/dev/null ); then - i=`echo $i | sed 's/#//'` - if ( ! cat $DOC | grep "id=\"$i\"" &>/dev/null ); then - echo "Missing link: $i" - ERROR=1 - fi - else - if ( echo $i | grep 'redhat.com' &>/dev/null ); then - if ( ! curl --insecure -s $i | grep "attributes.set('Name'" &>/dev/null ); then - echo "Invalid link: $i" - ERROR=1 - fi - elif ( ! curl --output /dev/null --silent --head --fail "$i" --connect-timeout 2 ); then - echo "Invalid link: $i" - ERROR=1 - fi - fi -done - -if ( cat $DOC | grep ifeval &>/dev/null ); then - echo "Found ifeval in text" - ERROR=1 -fi - -for i in `cat $DOC | grep -o -e '{book_[^}]*}' | sed 's/{//' | sed 's/}//'`; do - echo "Invalid attribute: $i" - ERROR=1 -done - -if [ $ERROR ]; then - exit 1 -fi From 3bd695ca7c25cc4e091c1cc392f7084d713ea2a0 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 07:45:39 +0200 Subject: [PATCH 086/194] Change version in prod doc links --- book-product.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book-product.json b/book-product.json index 3a5a3448d7..bde16b05f8 100755 --- a/book-product.json +++ b/book-product.json @@ -19,7 +19,7 @@ "images": "rhsso-images", "adminguide": { "name": "Server Administration Guide", - "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.0.0/server-administration-guide/" + "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.0/server-administration-guide/" } } } From 7e8f2afef92d53b64b390c19c0acb8c157798169 Mon Sep 17 00:00:00 2001 From: --add Date: Fri, 10 Jun 2016 12:20:59 +0530 Subject: [PATCH 087/194] updated the product version number in the master-docinfo.xml and metadata.ini --- master-docinfo.xml | 6 +++--- metadata.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/master-docinfo.xml b/master-docinfo.xml index 819b70d24c..831238c4ba 100755 --- a/master-docinfo.xml +++ b/master-docinfo.xml @@ -1,10 +1,10 @@ Red Hat Single Sign-On -7.0.0 +7.0 Securing Applications and Services Guide Securing Applications and Services Guide -7.0.0 +7.0 - This guide consist of information for securing applications and services using Red Hat Single Sign-On 7.0.0 + This guide consist of information for securing applications and services using Red Hat Single Sign-On 7.0 Red Hat Customer Content Services diff --git a/metadata.ini b/metadata.ini index fbb306cc3f..9200af5281 100644 --- a/metadata.ini +++ b/metadata.ini @@ -6,7 +6,7 @@ markup = asciidoc [metadata] title = Securing Applications and Services Guide product = Red Hat Single Sign-On -version = 7.0.0 +version = 7.0 edition = subtitle = keywords = From 53735621696e2e2913a076e5d6a975fac1a09f6a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:52:29 +0200 Subject: [PATCH 088/194] Update topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc --- .../idp_singlesignonservice_subelement.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc index c172bd3996..3566510450 100644 --- a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc @@ -17,23 +17,23 @@ to the IDP formatted via the settings within this element when it wants to login Here are the config attributes you can define on this element: signRequest:: - Should the client sign authn requests? This setting is _OPTIONAL._. + Should the client sign authn requests? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. validateResponseSignature:: Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? - This setting _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + This setting _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating with the IDP. This setting is _OPTIONAL._. + This is the SAML binding type used for communicating with the IDP. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to `REDIRECT` as well. responseBinding:: SAML allows the client to request what binding type it wants authn responses to use. - The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL._. + The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL_. The default is that the client will not request a specific binding type for responses. bindingUrl:: - This is the URL for the IDP login service that the client will send requests to. This setting is _REQUIRED._. + This is the URL for the IDP login service that the client will send requests to. This setting is _REQUIRED_. From daf1f02a6e17d87d2f6158c9c1e7cafdd008db7c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:53:23 +0200 Subject: [PATCH 089/194] Update topics/saml/java/general-config/idp_element.adoc --- topics/saml/java/general-config/idp_element.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/saml/java/general-config/idp_element.adoc b/topics/saml/java/general-config/idp_element.adoc index 2bbf03664c..25c54b56d1 100644 --- a/topics/saml/java/general-config/idp_element.adoc +++ b/topics/saml/java/general-config/idp_element.adoc @@ -16,7 +16,7 @@ Everything in the IDP element describes the settings for the identity provider ( Here are the attribute config options you can specify within the `IDP` element declaration. entityID:: - This is the issuer ID of the IDP. This setting is _REQUIRED._. + This is the issuer ID of the IDP. This setting is _REQUIRED_. signaturesRequired:: If set to `true`, the client adapter will sign every document it sends to the IDP. @@ -29,6 +29,6 @@ signatureAlgorithm:: This setting is _OPTIONAL_ and defaults to `RSA_SHA256`. signatureCanonicalizationMethod:: - This is the signature canonicalization method that the IDP expects signed documents to use. This setting is _OPTIONAL._. + This is the signature canonicalization method that the IDP expects signed documents to use. This setting is _OPTIONAL_. The default value is `http://www.w3.org/2001/10/xml-exc-c14n#` and should be good for most IDPs. From 06b74badde5817043ff65e3ef1737d8a603c8e9e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:55:05 +0200 Subject: [PATCH 090/194] Update topics/saml/java/general-config/sp-keys/keystore_element.adoc --- topics/saml/java/general-config/sp-keys/keystore_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp-keys/keystore_element.adoc b/topics/saml/java/general-config/sp-keys/keystore_element.adoc index 0569c02da5..b099c03d5f 100644 --- a/topics/saml/java/general-config/sp-keys/keystore_element.adoc +++ b/topics/saml/java/general-config/sp-keys/keystore_element.adoc @@ -28,7 +28,7 @@ resource:: This is a path used in method call to ServletContext.getResourceAsStream(). _OPTIONAL._ The file or resource attribute must be set. password:: - The password of the KeyStore _REQUIRED._ + The password of the KeyStore. This option is _REQUIRED_. If you are defining keys that the SP will use to sign document, you must also specify references to your private keys and certificates within the Java KeyStore. From cc8ea65622790577f70650a44deb2897066ac307 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:55:29 +0200 Subject: [PATCH 091/194] Update topics/saml/java/general-config/sp-keys/keystore_element.adoc --- topics/saml/java/general-config/sp-keys/keystore_element.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/saml/java/general-config/sp-keys/keystore_element.adoc b/topics/saml/java/general-config/sp-keys/keystore_element.adoc index b099c03d5f..f00f4ffd04 100644 --- a/topics/saml/java/general-config/sp-keys/keystore_element.adoc +++ b/topics/saml/java/general-config/sp-keys/keystore_element.adoc @@ -21,11 +21,11 @@ a `KeyStore` element. Here are the XML config attributes that are defined with the `KeyStore` element. file:: - File path to the key store. _OPTIONAL._ The file or resource attribute must be set. + File path to the key store. This option is _OPTIONAL_. The file or resource attribute must be set. resource:: WAR resource path to the KeyStore. - This is a path used in method call to ServletContext.getResourceAsStream(). _OPTIONAL._ The file or resource attribute must be set. + This is a path used in method call to ServletContext.getResourceAsStream(). This option is _OPTIONAL_. The file or resource attribute must be set. password:: The password of the KeyStore. This option is _REQUIRED_. From 9af76ebe016391990076d3220dc0b3dd1829e5a0 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:56:13 +0200 Subject: [PATCH 092/194] Update topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc --- .../idp_singlelogoutservice_subelement.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc index 90d983cca1..2123e7f17f 100644 --- a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -17,25 +17,25 @@ to the IDP formatted via the settings within this element when it wants to logou ---- signRequest:: - Should the client sign logout requests it makes to the IDP? This setting _OPTIONAL._. + Should the client sign logout requests it makes to the IDP? This setting _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. signResponse:: - Should the client sign logout responses it sends to the IDP requests? This setting _OPTIONAL._. + Should the client sign logout responses it sends to the IDP requests? This setting _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. validateRequestSignature:: - Should the client expect signed logout request documents from the IDP? This setting is _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + Should the client expect signed logout request documents from the IDP? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. validateResponseSignature:: - Should the client expect signed logout response documents from the IDP? This setting is _OPTIONAL._ Defaults to whatever the IDP `signaturesRequired` element value is. + Should the client expect signed logout response documents from the IDP? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL._. + This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to REDIRECT as well. responseBinding:: - This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL._. + This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to `REDIRECT` as well. postBindingUrl:: From 15ac03dae005824529fea7eb125b862b1f513414 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:56:37 +0200 Subject: [PATCH 093/194] Update topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc --- .../general-config/idp_singlelogoutservice_subelement.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc index 2123e7f17f..639cc9ea15 100644 --- a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -17,11 +17,11 @@ to the IDP formatted via the settings within this element when it wants to logou ---- signRequest:: - Should the client sign logout requests it makes to the IDP? This setting _OPTIONAL_. + Should the client sign logout requests it makes to the IDP? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. signResponse:: - Should the client sign logout responses it sends to the IDP requests? This setting _OPTIONAL_. + Should the client sign logout responses it sends to the IDP requests? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. validateRequestSignature:: From ee9670b8a04aa4bf7f4db58e4934727cdae43412 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:57:11 +0200 Subject: [PATCH 094/194] Update topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc --- .../java/general-config/idp_singlelogoutservice_subelement.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc index 639cc9ea15..23c414c07c 100644 --- a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -31,7 +31,7 @@ validateResponseSignature:: Should the client expect signed logout response documents from the IDP? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL_. + This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to REDIRECT as well. responseBinding:: From 2a236a74f577e17b571dbe6af7fd560deeb362a5 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:58:38 +0200 Subject: [PATCH 095/194] Update topics/saml/java/general-config/sp-keys.adoc --- topics/saml/java/general-config/sp-keys.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp-keys.adoc b/topics/saml/java/general-config/sp-keys.adoc index d76a1f700c..45e9c4cd16 100644 --- a/topics/saml/java/general-config/sp-keys.adoc +++ b/topics/saml/java/general-config/sp-keys.adoc @@ -7,7 +7,7 @@ For client signed documents you must define both the private and public key or c For encryption, you only have to define the private key that will be used to decrypt. There are two ways to describe your keys. -They can be stored within a Java KeyStore or you can or you can cut and paste the keys directly within `keycloak-saml.xml` in the PEM format. +They can be stored within a Java KeyStore or you can copy/paste the keys directly within `keycloak-saml.xml` in the PEM format. [source,xml] ---- From ef684d70cb89739767f608a8e650e16e74190e3a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:59:17 +0200 Subject: [PATCH 096/194] Update topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc --- .../general-config/idp_singlelogoutservice_subelement.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc index 23c414c07c..63d1e5c00a 100644 --- a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc @@ -31,11 +31,11 @@ validateResponseSignature:: Should the client expect signed logout response documents from the IDP? This setting is _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is. requestBinding:: - This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL_. + This is the SAML binding type used for communicating SAML requests to the IDP. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to REDIRECT as well. responseBinding:: - This is the SAML binding type used for communicating SAML responses to the IDP The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL_. + This is the SAML binding type used for communicating SAML responses to the IDP. The values of this can be `POST` or `REDIRECT`. This setting is _OPTIONAL_. The default value is `POST`, but you can set it to `REDIRECT` as well. postBindingUrl:: From 9c0ff6fa0fbae7b255dda881ba05ced9dfb0f3ac Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 12:59:55 +0200 Subject: [PATCH 097/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 8edc55405c..3637b75f1e 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -45,7 +45,7 @@ isPassive:: It is set to `false` by default. turnOffChangeSessionIdOnLogin:: - The session id is changed by default on a successful login on some platforms to plug a security attack vector (Tomcat 8, Jetty9, Undertow/Wildfly). + The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to `true` if you want to turn this off. It is recommended you do not turn it off. The default value is `false`. From cc2f100cab58174f22f66c16a75c6313c955af4d Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:01:17 +0200 Subject: [PATCH 098/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 3637b75f1e..05ec4bf169 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -24,7 +24,7 @@ sslPolicy:: For `ALL`, all requests must come in via HTTPS. For `EXTERNAL`, only non-private IP addresses must come over the wire via HTTPS. For `NONE`, no requests are required to come over via HTTPS. - This is _OPTIONAL._ and defaults to `EXTERNAL`. + This is _OPTIONAL._. Default value is `EXTERNAL`. nameIDPolicyFormat:: SAML clients can request a specific NameID Subject format. @@ -35,17 +35,17 @@ nameIDPolicyFormat:: forceAuthentication:: SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. - Set this to `true` if you want this. This setting is _OPTIONAL._. - Set to `false` by default. + Set this to `true` if you want this. This setting is _OPTIONAL._ and defaults to `false`. + Default value is `false`. isPassive:: SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to `true` if you want this. Do not use together with `forceAuthentication` as they are opposite. This setting is _OPTIONAL._. - It is set to `false` by default. + Default value is `false`. turnOffChangeSessionIdOnLogin:: The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to `true` if you want to turn this off. It is recommended you do not turn it off. - The default value is `false`. + Default value is `false`. From 5c66715669cddfa2dfc6477bb1f40e91ad324c30 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:02:09 +0200 Subject: [PATCH 099/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 05ec4bf169..21d3f1a818 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -16,7 +16,7 @@ Here is the explanation of the SP element attributes ---- entityID:: This is the identifier for this client. - The IDP needs this value to determine who the client is that is communicating with it. This setting _REQUIRED._ + The IDP needs this value to determine who the client is that is communicating with it. This setting is _REQUIRED_. sslPolicy:: This is the SSL policy the adapter will enforce. @@ -24,24 +24,24 @@ sslPolicy:: For `ALL`, all requests must come in via HTTPS. For `EXTERNAL`, only non-private IP addresses must come over the wire via HTTPS. For `NONE`, no requests are required to come over via HTTPS. - This is _OPTIONAL._. Default value is `EXTERNAL`. + This settings is _OPTIONAL_. Default value is `EXTERNAL`. nameIDPolicyFormat:: SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` - This setting is _OPTIONAL._. + This setting is _OPTIONAL_. By default, no special format is requested. forceAuthentication:: SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. - Set this to `true` if you want this. This setting is _OPTIONAL._ and defaults to `false`. + Set this to `true` if you want this. This setting is _OPTIONAL_ and defaults to `false`. Default value is `false`. isPassive:: SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to `true` if you want this. - Do not use together with `forceAuthentication` as they are opposite. This setting is _OPTIONAL._. + Do not use together with `forceAuthentication` as they are opposite. This setting is _OPTIONAL_. Default value is `false`. turnOffChangeSessionIdOnLogin:: From fe0fc04038c61e74a87d9f74bee8b68cb173452a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:03:02 +0200 Subject: [PATCH 100/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 21d3f1a818..1fecaccac9 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -29,7 +29,7 @@ sslPolicy:: nameIDPolicyFormat:: SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. - It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` + It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. This setting is _OPTIONAL_. By default, no special format is requested. From b1583754bd778be57be7fae6dba377e6da2915fe Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:03:30 +0200 Subject: [PATCH 101/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 1fecaccac9..8ca29e0933 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -35,7 +35,7 @@ nameIDPolicyFormat:: forceAuthentication:: SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. - Set this to `true` if you want this. This setting is _OPTIONAL_ and defaults to `false`. + Set this to `true` to enable. This setting is _OPTIONAL_ and defaults to `false`. Default value is `false`. isPassive:: From fb84142ea369310d6ac91ebc67520c6fc033325a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:03:57 +0200 Subject: [PATCH 102/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 8ca29e0933..52dc474350 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -35,7 +35,7 @@ nameIDPolicyFormat:: forceAuthentication:: SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. - Set this to `true` to enable. This setting is _OPTIONAL_ and defaults to `false`. + Set this to `true` to enable. This setting is _OPTIONAL_. Default value is `false`. isPassive:: From ef0210e757875bb4e8d456f3143d07126c3624d8 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:04:44 +0200 Subject: [PATCH 103/194] Update topics/saml/java/general-config/sp_element.adoc --- topics/saml/java/general-config/sp_element.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 52dc474350..03b8b69144 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -46,6 +46,6 @@ isPassive:: turnOffChangeSessionIdOnLogin:: The session id is changed by default on a successful login on some platforms to plug a security attack vector. - Change this to `true` if you want to turn this off. It is recommended you do not turn it off. + Change this to `true` to disable this. It is recommended you do not turn it off. Default value is `false`. From 363ff676e3948379fb39df5d4334b4eefe14d709 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:05:56 +0200 Subject: [PATCH 104/194] Update topics/saml/java/jboss-adapter.adoc --- topics/saml/java/jboss-adapter.adoc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/jboss-adapter.adoc index 56460177d2..5dfda7a9f8 100644 --- a/topics/saml/java/jboss-adapter.adoc +++ b/topics/saml/java/jboss-adapter.adoc @@ -1,6 +1,11 @@ [[_saml-jboss-adapter]] -==== JBoss/Wildfly Adapter +{% if book.community %} +==== JBoss EAP/Wildfly Adapter +{% endif %} +{% if book.product %} +==== JBoss EAP Adapter +{% endif %} To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and configure the {{book.project.name}} SAML Adapter Subsystem. You then provide a keycloak config, `/WEB-INF/keycloak-saml.xml` file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml. From 18ef9c69b533aa62ff12bf4008cb5c298e3f62f0 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:08:00 +0200 Subject: [PATCH 105/194] Update topics/saml/java/jboss-adapter.adoc --- topics/saml/java/jboss-adapter.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/jboss-adapter.adoc index 5dfda7a9f8..9091c41bf6 100644 --- a/topics/saml/java/jboss-adapter.adoc +++ b/topics/saml/java/jboss-adapter.adoc @@ -7,7 +7,13 @@ ==== JBoss EAP Adapter {% endif %} -To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and configure the {{book.project.name}} SAML Adapter Subsystem. +{% if book.community %} +To be able to secure WAR apps deployed on JBoss EAP or Wildfly, you must install and configure the {{book.project.name}} SAML Adapter Subsystem. +{% endif %} +{% if book.product %} +To be able to secure WAR apps deployed on JBoss EAP, you must install and configure the {{book.project.name}} SAML Adapter Subsystem. +{% endif %} + You then provide a keycloak config, `/WEB-INF/keycloak-saml.xml` file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml. Both methods are described in this section. From 805a74aaabed3c63471a2cc02fba6988edfa053c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:08:48 +0200 Subject: [PATCH 106/194] Update topics/saml/java/jboss-adapter/required_per_war_configuration.adoc --- .../saml/java/jboss-adapter/required_per_war_configuration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc index 47276e2598..928d10ccc5 100644 --- a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc +++ b/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc @@ -4,7 +4,7 @@ This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <> section. +The format of this config file is described in the <> section. Next you must set the `auth-method` to `KEYCLOAK-SAML` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. From b31a14bc3d3cc09557702bb107f423065da41766 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:11:17 +0200 Subject: [PATCH 107/194] Update topics/oidc/oidc-generic.adoc --- topics/oidc/oidc-generic.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index d4c589bb22..45f5e37bf9 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -133,7 +133,7 @@ There are a number of limitations of using this flow, including: * No support for identity brokering or social login * Flows are not supported (user self-registration, required actions, etc.) -For a client to be permitted to use the Resource Owner Password Credentials grant the client has to have `Direct Access Grants Enabled` enabled. +For a client to be permitted to use the Resource Owner Password Credentials grant the client has to have the `Direct Access Grants Enabled` option enabled. This flow is not included in OpenID Connect, but is a part of the OAuth 2.0 specification. From 5ffda764609dec6a94ee4a01cc83f4bc821c47b8 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:12:09 +0200 Subject: [PATCH 108/194] Update topics/oidc/oidc-generic.adoc --- topics/oidc/oidc-generic.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 45f5e37bf9..0413d70d07 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -95,7 +95,7 @@ https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dyn ===== Authorization Code The Authorization Code flow redirects the user agent to {{book.project.name}}. Once the user has successfully authenticated with {{book.project.name}} an -Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code to along with its +Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code along with its credentials to obtain an Access Roken, Refresh Token and ID Token from {{book.project.name}}. The flow is targeted towards web applications, but is also recommended for native applications, including mobile applications, where it is possible to embed From 9dab95a807802b913418da287de8491d138abcdb Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:12:37 +0200 Subject: [PATCH 109/194] Update topics/oidc/oidc-generic.adoc --- topics/oidc/oidc-generic.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 0413d70d07..212baec505 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -96,7 +96,7 @@ https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dyn The Authorization Code flow redirects the user agent to {{book.project.name}}. Once the user has successfully authenticated with {{book.project.name}} an Authorization Code is created and the user agent is redirected back to the application. The application then uses the authorization code along with its -credentials to obtain an Access Roken, Refresh Token and ID Token from {{book.project.name}}. +credentials to obtain an Access Token, Refresh Token and ID Token from {{book.project.name}}. The flow is targeted towards web applications, but is also recommended for native applications, including mobile applications, where it is possible to embed a user agent. From 41c2b0608ab17d829ab5163f5198f6f64cd8d9ec Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:13:56 +0200 Subject: [PATCH 110/194] Update topics/oidc/oidc-generic.adoc --- topics/oidc/oidc-generic.adoc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 212baec505..f79ed88de1 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -11,10 +11,10 @@ The most important endpoint to know is the `well-known` configuration endpoint. Connect implementation in {{book.project.name}}. The endpoint is: .... -/realms/REALM-NAME/.well-known/openid-configuration +/realms/{realm-name}/.well-known/openid-configuration .... -To get the full URL add the base URL for {{book.project.name}} and replace `REALM-NAME` with the name of your realm. For example: +To get the full URL add the base URL for {{book.project.name}} and replace `{realm-name}` with the name of your realm. For example: $$http://localhost:8080/auth/realms/master/.well-known/openid-configuration$$ @@ -22,7 +22,7 @@ Some RP libraries will retrieve all required endpoints from this endpoint, but f ===== Authorization Endpoint .... -/realms/master/protocol/openid-connect/auth +/realms/{realm-name}/protocol/openid-connect/auth .... Performs authentication of the end-user. This is done by redirecting user agent to this endpoint. @@ -31,7 +31,7 @@ For more details see http://openid.net/specs/openid-connect-core-1_0.html#Author ===== Token Endpoint .... -/realms/master/protocol/openid-connect/token +/realms/{realm-name}/protocol/openid-connect/token .... Used to obtain tokens. Tokens can either be obtained by exchanging an authorization code or by supplying credentials directly depending on what flow is used. @@ -41,7 +41,7 @@ For more details see http://openid.net/specs/openid-connect-core-1_0.html#TokenE ===== Userinfo Endpoint .... -/realms/master/protocol/openid-connect/userinfo +/realms/{realm-name}/protocol/openid-connect/userinfo .... Returns standard claims about the authenticated user. Protected by a bearer token. @@ -50,7 +50,7 @@ For more details see http://openid.net/specs/openid-connect-core-1_0.html#UserIn ===== Logout Endpoint .... -/realms/master/protocol/openid-connect/logout +/realms/{realm-name}/protocol/openid-connect/logout .... Logs out the authenticated user. @@ -62,7 +62,7 @@ required to authenticate the client. ===== Certificate Endpoint .... -/realms/master/protocol/openid-connect/certs +/realms/{realm-name}/protocol/openid-connect/certs .... Public key used by realm encoded as a JSON Web Key (JWK). This key can be used to verify tokens issued by {{book.project.name}} without making invocations to @@ -72,7 +72,7 @@ For more details see https://tools.ietf.org/html/rfc7517[JSON Web Key specificat ===== Introspection Endpoint .... -/realms/master/protocol/openid-connect/token/introspect +/realms/{realm-name}/protocol/openid-connect/token/introspect .... Used to retrieve the active state of a token. Protected by a bearer token and can only be invoked by confidential clients. @@ -81,7 +81,7 @@ For more details see https://tools.ietf.org/html/rfc7662[OAuth 2.0 Token Introsp ===== Dynamic Client Registration Endpoint .... -/realms/master/clients-registrations/openid-connect +/realms/{realm-name}/clients-registrations/openid-connect .... Used to dynamically register clients. From b6cbb82bda24c624b642aa6c9a2f013eb62cdd8f Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:16:22 +0200 Subject: [PATCH 111/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 topics/oidc/javascript-adapter.adoc diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc old mode 100755 new mode 100644 index cd8665d6aa..77d30725cf --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -368,4 +368,4 @@ The available events are: * onAuthRefreshSuccess - Called when the token is refreshed. * onAuthRefreshError - Called if there was an error while trying to refresh the token. * onAuthLogout - Called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode). -* onTokenExpired - Called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen. +* onTokenExpired - Called when the access token is expired. When this happens you can for refresh the token, or if refresh is not available (ie. with implicit flow) you can redirect to login screen. From 3e756875b2253e99856505b08f858e368be82d7e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:17:42 +0200 Subject: [PATCH 112/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 77d30725cf..7c064c69b5 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -288,13 +288,13 @@ Options is an Object, where: Redirects to registration form. Shortcut for login with option action = 'register' -Options are same as login method but 'action' is set to 'register' +Options are same as for the login method but 'action' is set to 'register' ====== createRegisterUrl(options) Returns the url to registration page. Shortcut for createLoginUrl with option action = 'register' -Options are same as createLoginUrl method but 'action' is set to 'register' +Options are same as for the createLoginUrl method but 'action' is set to 'register' ====== accountManagement() From a36788f62fe2b67ad483e82a51aa70704c3f3241 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:19:59 +0200 Subject: [PATCH 113/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 7c064c69b5..77b60bd57a 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -254,7 +254,7 @@ Redirects to login form on (options is an optional object with redirectUri and/o Options is an Object, where: * redirectUri - Specifies the uri to redirect to after login. -* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed). +* prompt - By default the login screen is displayed if the user is not logged-in to {{book.project.name}}. To only authenticate to the application if the user is already logged-in and not display the login page if the user is not logged-in, set this option to `none`. * loginHint - Used to pre-fill the username/email field on the login form. * action - If value is 'register' then user is redirected to registration page, otherwise to login page. * locale - Specifies the desired locale for the UI. From 56f922687559a4aae75d536d72b92d3fb4a1fd20 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:20:50 +0200 Subject: [PATCH 114/194] Update SUMMARY.adoc --- SUMMARY.adoc | 143 ++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 77 deletions(-) mode change 100755 => 100644 SUMMARY.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc old mode 100755 new mode 100644 index f5ed52c0e0..feb133dbf3 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -1,79 +1,68 @@ -= {{book.title}} += Summary - . link:topics/overview/overview.adoc[Overview] - .. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] - .. link:topics/overview/supported-platforms.adoc[Supported Platforms] - .. link:topics/overview/supported-protocols.adoc[Supported Protocols] +. link:README.adoc[Introduction] +. link:topics/overview/overview.adoc[Overview] +.. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] +.. link:topics/overview/supported-platforms.adoc[Supported Platforms] +.. link:topics/overview/supported-protocols.adoc[Supported Protocols] +. link:topics/oidc/oidc-overview.adoc[OpenID Connect] +.. link:topics/oidc/java/java-adapters.adoc[Java Adapters] +... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] +... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] +... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] +.... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] +.... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] +.... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] +.... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] +.... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] +.... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] +... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] +... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] +... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] +... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] +... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] +... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] +... link:topics/oidc/java/jaas.adoc[JAAS plugin] +... link:topics/oidc/java/adapter-context.adoc[Security Context] +... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] +... link:topics/oidc/java/logout.adoc[Logout] +... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] +... link:topics/oidc/java/application-clustering.adoc[Application Clustering] +.. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] +.. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] +... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] +. link:topics/saml/saml-overview.adoc[SAML] +.. link:topics/saml/java/java-adapters.adoc[Java Adapters] +... link:topics/saml/java/general-config.adoc[General Adapter Config] +.... link:topics/saml/java/general-config/sp_element.adoc[SP Element] +.... link:topics/saml/java/general-config/sp-keys.adoc[SP Keys and Key elements] +..... link:topics/saml/java/general-config/sp-keys/keystore_element.adoc[KeyStore Element] +..... link:topics/saml/java/general-config/sp-keys/key_pems.adoc[Key PEMS] +.... link:topics/saml/java/general-config/sp_principalname_mapping_element.adoc[SP PrincipalNameMapping element] +.... link:topics/saml/java/general-config/roleidentifiers_element.adoc[RoleIdentifiers element] +.... link:topics/saml/java/general-config/idp_element.adoc[IDP Element] +.... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] +.... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] +.... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] +... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] +.... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] +.... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] +.... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] +... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] +.... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] +.... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Per WAR Configuration] +... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] +.... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] +.... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Per WAR Configuration] +.... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] +.... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] +... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] +... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] +... link:topics/saml/java/logout.adoc[Logout] +... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] +... link:topics/saml/java/error_handling.adoc[Error Handling] +... link:topics/saml/java/debugging.adoc[Troubleshooting] +... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] +.. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] +. link:topics/client-registration.adoc[Client Registration] - . link:topics/oidc/oidc-overview.adoc[OpenID Connect] - - .. link:topics/oidc/java/java-adapters.adoc[Java Adapters] - ... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] - ... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] - ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] - .... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] - .... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] - .... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] - .... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] - .... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] - .... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] - {% if book.community %} - ... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] - ... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] - ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] - ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] - ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] - {% endif %} - ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] - ... link:topics/oidc/java/jaas.adoc[JAAS plugin] - ... link:topics/oidc/java/adapter-context.adoc[Security Context] - ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] - ... link:topics/oidc/java/logout.adoc[Logout] - ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] - ... link:topics/oidc/java/application-clustering.adoc[Application Clustering] - - .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] - - .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] - {% if book.community %} - ... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] - {% endif %} - - . link:topics/saml/saml-overview.adoc[SAML] - .. link:topics/saml/java/java-adapters.adoc[Java Adapters] - ... link:topics/saml/java/general-config.adoc[General Adapter Config] - .... link:topics/saml/java/general-config/sp_element.adoc[SP Element] - .... link:topics/saml/java/general-config/sp-keys.adoc[SP Keys and Key elements] - ..... link:topics/saml/java/general-config/sp-keys/keystore_element.adoc[KeyStore Element] - ..... link:topics/saml/java/general-config/sp-keys/key_pems.adoc[Key PEMS] - .... link:topics/saml/java/general-config/sp_principalname_mapping_element.adoc[SP PrincipalNameMapping element] - .... link:topics/saml/java/general-config/roleidentifiers_element.adoc[RoleIdentifiers element] - .... link:topics/saml/java/general-config/idp_element.adoc[IDP Element] - .... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] - .... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] - .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] - ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] - .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] - .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] - .... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] - {% if book.community %} - ... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] - .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] - .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Per WAR Configuration] - ... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] - .... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] - .... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Per WAR Configuration] - .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] - .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] - {% endif %} - ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] - ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] - ... link:topics/saml/java/logout.adoc[Logout] - ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] - ... link:topics/saml/java/error_handling.adoc[Error Handling] - ... link:topics/saml/java/debugging.adoc[Troubleshooting] - {% if book.community %} - ... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] - {% endif %} - .. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] - . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file From be8a9a63d87b82ac76602fe7342e7263b1514461 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:21:43 +0200 Subject: [PATCH 115/194] Update topics/saml/java/idp-registration.adoc --- topics/saml/java/idp-registration.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/topics/saml/java/idp-registration.adoc b/topics/saml/java/idp-registration.adoc index 2d59109c9b..285971e51f 100644 --- a/topics/saml/java/idp-registration.adoc +++ b/topics/saml/java/idp-registration.adoc @@ -3,3 +3,4 @@ For each servlet based adapter, the endpoint you register for the assert consumer service url and and single logout service must be the base url of your servlet application with `/saml` appended to it i.e. `$$https://example.com/contextPath/saml$$` + From 2e607e4ffad10f32644c9ca7f21e77310c7ef0c3 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:22:50 +0200 Subject: [PATCH 116/194] Fix mistake to SUMMARY --- SUMMARY.adoc | 143 +++++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index feb133dbf3..f5ed52c0e0 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -1,68 +1,79 @@ -= Summary += {{book.title}} -. link:README.adoc[Introduction] -. link:topics/overview/overview.adoc[Overview] -.. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] -.. link:topics/overview/supported-platforms.adoc[Supported Platforms] -.. link:topics/overview/supported-protocols.adoc[Supported Protocols] -. link:topics/oidc/oidc-overview.adoc[OpenID Connect] -.. link:topics/oidc/java/java-adapters.adoc[Java Adapters] -... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] -... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] -... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] -.... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] -.... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] -.... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] -.... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] -.... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] -.... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] -... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] -... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] -... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] -... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] -... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] -... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] -... link:topics/oidc/java/jaas.adoc[JAAS plugin] -... link:topics/oidc/java/adapter-context.adoc[Security Context] -... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] -... link:topics/oidc/java/logout.adoc[Logout] -... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] -... link:topics/oidc/java/application-clustering.adoc[Application Clustering] -.. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] -.. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] -... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] -. link:topics/saml/saml-overview.adoc[SAML] -.. link:topics/saml/java/java-adapters.adoc[Java Adapters] -... link:topics/saml/java/general-config.adoc[General Adapter Config] -.... link:topics/saml/java/general-config/sp_element.adoc[SP Element] -.... link:topics/saml/java/general-config/sp-keys.adoc[SP Keys and Key elements] -..... link:topics/saml/java/general-config/sp-keys/keystore_element.adoc[KeyStore Element] -..... link:topics/saml/java/general-config/sp-keys/key_pems.adoc[Key PEMS] -.... link:topics/saml/java/general-config/sp_principalname_mapping_element.adoc[SP PrincipalNameMapping element] -.... link:topics/saml/java/general-config/roleidentifiers_element.adoc[RoleIdentifiers element] -.... link:topics/saml/java/general-config/idp_element.adoc[IDP Element] -.... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] -.... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] -.... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] -... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] -.... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] -.... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] -.... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] -... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] -.... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] -.... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Per WAR Configuration] -... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] -.... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] -.... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Per WAR Configuration] -.... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] -.... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] -... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] -... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] -... link:topics/saml/java/logout.adoc[Logout] -... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] -... link:topics/saml/java/error_handling.adoc[Error Handling] -... link:topics/saml/java/debugging.adoc[Troubleshooting] -... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] -.. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] -. link:topics/client-registration.adoc[Client Registration] + . link:topics/overview/overview.adoc[Overview] + .. link:topics/overview/what-are-client-adapters.adoc[What are Client Adapters?] + .. link:topics/overview/supported-platforms.adoc[Supported Platforms] + .. link:topics/overview/supported-protocols.adoc[Supported Protocols] + . link:topics/oidc/oidc-overview.adoc[OpenID Connect] + + .. link:topics/oidc/java/java-adapters.adoc[Java Adapters] + ... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] + ... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] + ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] + .... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] + .... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] + .... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] + .... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] + .... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] + .... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] + {% if book.community %} + ... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] + ... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] + ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] + ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] + ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] + {% endif %} + ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + ... link:topics/oidc/java/jaas.adoc[JAAS plugin] + ... link:topics/oidc/java/adapter-context.adoc[Security Context] + ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] + ... link:topics/oidc/java/logout.adoc[Logout] + ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] + ... link:topics/oidc/java/application-clustering.adoc[Application Clustering] + + .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] + + .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] + {% if book.community %} + ... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] + {% endif %} + + . link:topics/saml/saml-overview.adoc[SAML] + .. link:topics/saml/java/java-adapters.adoc[Java Adapters] + ... link:topics/saml/java/general-config.adoc[General Adapter Config] + .... link:topics/saml/java/general-config/sp_element.adoc[SP Element] + .... link:topics/saml/java/general-config/sp-keys.adoc[SP Keys and Key elements] + ..... link:topics/saml/java/general-config/sp-keys/keystore_element.adoc[KeyStore Element] + ..... link:topics/saml/java/general-config/sp-keys/key_pems.adoc[Key PEMS] + .... link:topics/saml/java/general-config/sp_principalname_mapping_element.adoc[SP PrincipalNameMapping element] + .... link:topics/saml/java/general-config/roleidentifiers_element.adoc[RoleIdentifiers element] + .... link:topics/saml/java/general-config/idp_element.adoc[IDP Element] + .... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] + .... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] + .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] + ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] + .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] + .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] + .... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] + {% if book.community %} + ... link:topics/saml/java/tomcat-adapter.adoc[Tomcat SAML adapters] + .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc[Adapter Installation] + .... link:topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc[Per WAR Configuration] + ... link:topics/saml/java/jetty-adapter.adoc[Jetty SAML Adapters] + .... link:topics/saml/java/jetty-adapter/jetty9_installation.adoc[Jetty 9 Adapter Installation] + .... link:topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc[Jetty 9 Per WAR Configuration] + .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] + .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] + {% endif %} + ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] + ... link:topics/saml/java/logout.adoc[Logout] + ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] + ... link:topics/saml/java/error_handling.adoc[Error Handling] + ... link:topics/saml/java/debugging.adoc[Troubleshooting] + {% if book.community %} + ... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] + {% endif %} + .. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] + . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file From 2154f029d4d1af9a6870c699ada4072a6695521b Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:25:14 +0200 Subject: [PATCH 117/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 77b60bd57a..10f6eabd41 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -13,7 +13,7 @@ specific as possible. To use the JavaScript adapter you must first create a client for your application in the {{book.project.name}} Administration Console. Make sure `public` is selected for `Access Type`. -You also need to configure valid redirect URIs and valid web origins. Be as specific as possible as failing to do so may results in a security vulnerability. +You also need to configure valid redirect URIs and valid web origins. Be as specific as possible as failing to do so may result in a security vulnerability. Once the client is created click on the `Installation` tab select `Keycloak OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be hosted on your web server at the same location as your HTML pages. From 80dbee6111d384b0f86bdbb682474edfdeb0818a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:26:49 +0200 Subject: [PATCH 118/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 10f6eabd41..8e7bd0dd5e 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -122,7 +122,8 @@ your application. ==== Implicit and Hybrid Flow By default, the JavaScript adapter uses the http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code] flow. -With this flow the {{book.project.name}} server returns a authorization code, not a authentication token, to the application. The JavaScript adapter exchanges + +With this flow the {{book.project.name}} server returns an authorization code, not an authentication token, to the application. The JavaScript adapter exchanges the `code` for an access token and a refresh token after the browser is redirected back to the application. {{book.project.name}} also supports the http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth[Implicit] flow where an access token From e095fdb528a22a9b9506d9db880af8348aa8596a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 10 Jun 2016 13:29:37 +0200 Subject: [PATCH 119/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 8e7bd0dd5e..029a510afe 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -44,7 +44,7 @@ If the `keycloak.json` file is in a different location you can specify it: var keycloak = Keycloak('http://localhost:8080/myapp/keycloak.json')); ---- -You can also skip the file altogether and manually configure the adapter: +Alternatively, you can pass in a JavaScript object with the required configuration instead: [source,javascript] ---- From ea97b59da557337ef2bafbde31952f6a5333a25f Mon Sep 17 00:00:00 2001 From: --add Date: Fri, 10 Jun 2016 17:31:30 +0530 Subject: [PATCH 120/194] chapter 1 heading levels corrected --- topics/overview/overview.adoc | 4 ++- topics/overview/supported-platforms.adoc | 30 ++++++++++--------- topics/overview/supported-protocols.adoc | 10 ++++--- topics/overview/what-are-client-adapters.adoc | 6 ++-- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/topics/overview/overview.adoc b/topics/overview/overview.adoc index ea74de3034..fdb0362192 100644 --- a/topics/overview/overview.adoc +++ b/topics/overview/overview.adoc @@ -4,4 +4,6 @@ decide is which of the two you are going to use. If you want you can also choose to secure some with OpenID Connect and others with SAML. To secure clients and services you are also going to need an adapter or library for the protocol you've selected. {{book.project.name}} comes with its own -adapters for selected platforms, but it is also possible to use generic OpenID Connect Resource Provider and SAML Service Provider libraries. \ No newline at end of file +adapters for selected platforms, but it is also possible to use generic OpenID Connect Resource Provider and SAML Service Provider libraries. + + diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 6d7be449ff..206a166b14 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -1,8 +1,8 @@ -== Supported Platforms +=== Supported Platforms -=== OpenID Connect +==== OpenID Connect -==== Java +===== Java * <> {% if book.community %} * <> @@ -18,48 +18,48 @@ * <> (community) {% endif %} -==== JavaScript (client-side) +===== JavaScript (client-side) * <> -=== Apache Cordova +===== Apache Cordova * <> {% if book.community %} -==== Node.js +===== Node.js * https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Connect] (community) * https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Auth Utils] (community) {% endif %} {% if book.community %} -=== C# +==== C# * https://github.com/dylanplecki/KeycloakOwinAuthentication[OWIN] (community) {% endif %} {% if book.community %} -=== Python +==== Python * https://pypi.python.org/pypi/python-openid/[python-openid] (generic) {% endif %} {% if book.community %} -=== Android +==== Android * https://github.com/openid/AppAuth-Android[AppAuth] (generic) * https://github.com/aerogear/aerogear-android-authz[AeroGear] (generic) {% endif %} {% if book.community %} -=== iOS +==== iOS * https://github.com/openid/AppAuth-iOS[AppAuth] (generic) * https://github.com/aerogear/aerogear-ios-oauth2[AeroGear] (generic) {% endif %} {% if book.community %} -==== Apache HTTP Server +===== Apache HTTP Server * https://github.com/pingidentity/mod_auth_openidc[mod_auth_openidc] {% endif %} -=== SAML +==== SAML -==== Java +===== Java * <> {% if book.community %} @@ -68,6 +68,8 @@ * <> {% endif %} + + ==== Apache HTTP Server -* https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] \ No newline at end of file +* https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index bcca21aa3d..104de15bcd 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -1,7 +1,7 @@ [[_supported_protocols]] -== Supported Protocols +=== Supported Protocols -=== OpenID Connect +==== OpenID Connect link:http://openid.net/connect/[Open ID Connect] (OIDC) is an authentication protocol that is an extension of link:https://tools.ietf.org/html/rfc6749[OAuth 2.0]. While OAuth 2.0 is only a framework for building authorization protocols and is mainly incomplete, OIDC is a full-fledged authentication and authorization @@ -21,7 +21,7 @@ is digitally signed by the realm. The client can make REST invocations on remot extracts the _access token_, verifies the signature of the token, then decides based on access information within the token whether or not to process the request. -=== SAML 2.0 +==== SAML 2.0 link:http://saml.xml.org/saml-specifications[SAML 2.0] is a similar specification to OIDC but a lot older and more mature. It has its roots in SOAP and the plethora of WS-* specifications so it tends to be a bit more verbose than OIDC. SAML 2.0 is primarily an authentication protocol @@ -38,7 +38,7 @@ is allowed to access on the application. The second type of use cases is that of a client that wants to gain access to remote services. In this case, the client asks {{book.project.name}} to obtain a SAML assertion it can use to invoke on other remote services on behalf of the user. -=== OpenID Connect vs. SAML +==== OpenID Connect vs. SAML Choosing between OpenID Connect and SAML is not just a matter of using a newer protocol (OIDC) instead of the older more mature protocol (SAML). @@ -52,3 +52,5 @@ they are easier to consume by JavaScript. You will also find several nice featur make implementing security in your web applications easier. For example, check out the iframe trick that the specification uses to easily determine if a user is still logged in or not. SAML has its uses though. As you see the OIDC specifications evolve you see they implement more and more features that SAML has had for years. What we often see is that people pick SAML over OIDC because of the perception that it is more mature and also because they already have existing applications that are secured with it. + + diff --git a/topics/overview/what-are-client-adapters.adoc b/topics/overview/what-are-client-adapters.adoc index ba7c381349..dc643a2185 100644 --- a/topics/overview/what-are-client-adapters.adoc +++ b/topics/overview/what-are-client-adapters.adoc @@ -1,5 +1,7 @@ -== What are Client Adapters? +=== What are Client Adapters? {{book.project.name}} client adapters are libraries that makes it very easy to secure applications and services with {{book.project.name}}. We call them adapters rather than libraries as they provide a tight integration to the underlying platform and framework. This makes our adapters easy to use and they -require less boilerplate code than what is typically required by a library. \ No newline at end of file +require less boilerplate code than what is typically required by a library. + + From daf509f8aa8aa6614581c588246841d15bc1e4cc Mon Sep 17 00:00:00 2001 From: --add Date: Fri, 10 Jun 2016 17:46:34 +0530 Subject: [PATCH 121/194] chapter 1 heading levels corrected --- topics/overview/supported-platforms.adoc | 4 ++-- topics/overview/what-are-client-adapters.adoc | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 206a166b14..c75669c3c5 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -68,8 +68,8 @@ * <> {% endif %} - - ==== Apache HTTP Server * https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] + + diff --git a/topics/overview/what-are-client-adapters.adoc b/topics/overview/what-are-client-adapters.adoc index dc643a2185..47f8f83f18 100644 --- a/topics/overview/what-are-client-adapters.adoc +++ b/topics/overview/what-are-client-adapters.adoc @@ -5,3 +5,4 @@ adapters rather than libraries as they provide a tight integration to the underl require less boilerplate code than what is typically required by a library. + From bdd0019f6f972114946edddde2f3584aeee75ded Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 13:02:36 +0200 Subject: [PATCH 122/194] Added mvn version --- book-product.json | 3 ++- book.json | 3 ++- topics/oidc/java/fuse-adapter.adoc | 2 ++ topics/oidc/java/fuse/camel.adoc | 2 +- topics/oidc/java/fuse/classic-war.adoc | 10 +++++----- topics/oidc/java/fuse/cxf-builtin.adoc | 4 ++-- topics/oidc/java/fuse/cxf-separate.adoc | 2 +- topics/oidc/java/fuse/servlet-whiteboard.adoc | 10 +++++----- topics/oidc/java/servlet-filter-adapter.adoc | 2 +- topics/oidc/oidc-generic.adoc | 4 ++++ 10 files changed, 25 insertions(+), 17 deletions(-) diff --git a/book-product.json b/book-product.json index bde16b05f8..cfde542908 100755 --- a/book-product.json +++ b/book-product.json @@ -12,7 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Red Hat Single Sign-On", - "version": "7.0.0" + "version": "7.0.0", + "versionMvn": "1.9.7.Final-redhat-1" }, "community": false, "product": true, diff --git a/book.json b/book.json index d3dcf33ef5..954b01e19c 100755 --- a/book.json +++ b/book.json @@ -12,7 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "1.9.7.Final" + "version": "1.9.7.Final", + "versionMvn": "1.9.7.Final" }, "community": true, "product": false, diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 53057c9282..58f42914bd 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -2,7 +2,9 @@ [[_fuse_adapter]] ==== JBoss Fuse Adapter +{% if book.product %} NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported +{% endif %} Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] . {% if book.community %} diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index c9de37ab4c..3e5668b71a 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -88,7 +88,7 @@ org.apache.camel;version="[2.13,3)", org.eclipse.jetty.security;version="[8,10)", org.eclipse.jetty.server.nio;version="[8,10)", org.eclipse.jetty.util.security;version="[8,10)", -org.keycloak.*;version="{{book.project.version}}", +org.keycloak.*;version="{{book.project.versionMvn}}", org.osgi.service.blueprint, org.osgi.service.blueprint.container, org.osgi.service.event, diff --git a/topics/oidc/java/fuse/classic-war.adoc b/topics/oidc/java/fuse/classic-war.adoc index 980c791425..56703c1c5c 100644 --- a/topics/oidc/java/fuse/classic-war.adoc +++ b/topics/oidc/java/fuse/classic-war.adoc @@ -72,11 +72,11 @@ as it's not used by application or Blueprint or Spring descriptor, but it's used [source, subs="attributes"] ---- -org.keycloak.adapters.jetty;version="{{book.project.version}}", -org.keycloak.adapters;version="{{book.project.version}}", -org.keycloak.constants;version="{{book.project.version}}", -org.keycloak.util;version="{{book.project.version}}", -org.keycloak.*;version="{{book.project.version}}", +org.keycloak.adapters.jetty;version="{{book.project.versionMvn}}", +org.keycloak.adapters;version="{{book.project.versionMvn}}", +org.keycloak.constants;version="{{book.project.versionMvn}}", +org.keycloak.util;version="{{book.project.versionMvn}}", +org.keycloak.*;version="{{book.project.versionMvn}}", *;resolution:=optional ---- diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/topics/oidc/java/fuse/cxf-builtin.adoc index 8fb2c01501..5e930061dd 100644 --- a/topics/oidc/java/fuse/cxf-builtin.adoc +++ b/topics/oidc/java/fuse/cxf-builtin.adoc @@ -93,7 +93,7 @@ org.apache.cxf.*;version="[2.7,3.2)", com.fasterxml.jackson.jaxrs.json;version="[2.5,3)", org.eclipse.jetty.security;version="[8,10)", org.eclipse.jetty.util.security;version="[8,10)", -org.keycloak.*;version="{{book.project.version}}", -org.keycloak.adapters.jetty;version="{{book.project.version}}", +org.keycloak.*;version="{{book.project.versionMvn}}", +org.keycloak.adapters.jetty;version="{{book.project.versionMvn}}", *;resolution:=optional ---- diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/topics/oidc/java/fuse/cxf-separate.adoc index 925bec9c77..178775b56b 100644 --- a/topics/oidc/java/fuse/cxf-separate.adoc +++ b/topics/oidc/java/fuse/cxf-separate.adoc @@ -108,5 +108,5 @@ org.apache.cxf.*;version="[2.7,3.2)", org.springframework.beans.factory.config, org.eclipse.jetty.security;version="[8,10)", org.eclipse.jetty.util.security;version="[8,10)", -org.keycloak.*;version="{{book.project.version}}" +org.keycloak.*;version="{{book.project.versionMvn}}" ---- \ No newline at end of file diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc index cf7e122784..386cca93ad 100644 --- a/topics/oidc/java/fuse/servlet-whiteboard.adoc +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -68,10 +68,10 @@ Note you don't need `web.xml` as the security-constrains are declared in bluepri [source, subs="attributes"] ---- -org.keycloak.adapters.jetty;version="{{book.project.version}}", -org.keycloak.adapters;version="{{book.project.version}}", -org.keycloak.constants;version="{{book.project.version}}", -org.keycloak.util;version="{{book.project.version}}", -org.keycloak.*;version="{{book.project.version}}", +org.keycloak.adapters.jetty;version="{{book.project.versionMvn}}", +org.keycloak.adapters;version="{{book.project.versionMvn}}", +org.keycloak.constants;version="{{book.project.versionMvn}}", +org.keycloak.util;version="{{book.project.versionMvn}}", +org.keycloak.*;version="{{book.project.versionMvn}}", *;resolution:=optional ---- diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 2ddd90f00a..9acdb735eb 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -47,6 +47,6 @@ To use this filter, include this maven artifact in your WAR poms: <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-servlet-filter-adapter</artifactId> - <version>{{book.project.version}}</version> + <version>{{book.project.versionMvn}}</version> </dependency> ---- diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index f79ed88de1..434141bed9 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -1,5 +1,9 @@ === Other OpenID Connect libraries +{% if book.product %} +NOTE: Using {{book.project.name}} with generic OpenID Connect libraries is a Technology Preview feature and is not fully supported +{% endif %} + {{book.project.name}} can be secured by supplied adapters that usually are easier to use and provide better integration with {{book.project.name}}. However, if there is no adapter available for your programming language, framework or platform you may opt to use a generic OpenID Connect Resource Provider (RP) library instead. This chapter describes details specific to {{book.project.name}} and doesn't go into low-level details of the protocols. For more details refer to the From 51c8539e917e22ab15765223e6e896ea0354b850 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 19:42:11 +0200 Subject: [PATCH 123/194] Update topics/overview/supported-platforms.adoc --- topics/overview/supported-platforms.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index c75669c3c5..2d3e4d0fd2 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -68,7 +68,7 @@ * <> {% endif %} -==== Apache HTTP Server +===== Apache HTTP Server * https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] From 1a252cd90e2d13331ddd93adaa29e60bdc25109d Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 19:43:03 +0200 Subject: [PATCH 124/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 6e700e0ed6..b4ae0214e9 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -37,7 +37,7 @@ This is what one might look like: You can use `${...}` enclosure for system property replacement. For example `${jboss.server.config.dir}` would be replaced by `/path/to/{{book.project.name}}`. -The initial config file can be ontained from the the admin console. This can be done by opening the admin console, select `Clients` from the menu and clicking +The initial config file can be obtained from the the admin console. This can be done by opening the admin console, select `Clients` from the menu and clicking on the corresponding client. Once the page for the client is opened click on the `Installation` tab and select `Keycloak OIDC JSON`. Here is a description of each configuration option: From 4a1a141aae3e82ccd989610d225c51dee3b6c657 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 19:44:45 +0200 Subject: [PATCH 125/194] Update topics/oidc/java/java-adapter-config.adoc --- topics/oidc/java/java-adapter-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index b4ae0214e9..2d57fa2b32 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -43,7 +43,7 @@ on the corresponding client. Once the page for the client is opened click on the Here is a description of each configuration option: realm:: - Name of the realm representing the users of your distributed applications and services. + Name of the realm. This is _REQUIRED._ resource:: From 1c412b254c393084f5f787dcbca90fd56e76cbb9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 19:40:04 +0200 Subject: [PATCH 126/194] Set prod mvn version to 1.9.8.Final-redhat-1 --- book-product.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book-product.json b/book-product.json index cfde542908..3d404759ab 100755 --- a/book-product.json +++ b/book-product.json @@ -13,7 +13,7 @@ "project": { "name": "Red Hat Single Sign-On", "version": "7.0.0", - "versionMvn": "1.9.7.Final-redhat-1" + "versionMvn": "1.9.8.Final-redhat-1" }, "community": false, "product": true, From c12df08dee1b7b6b81121526ff87e6691d5fb584 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 19:48:25 +0200 Subject: [PATCH 127/194] Set version to 1.9.8.Final --- book.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book.json b/book.json index 954b01e19c..8485c6a2ca 100755 --- a/book.json +++ b/book.json @@ -12,8 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "1.9.7.Final", - "versionMvn": "1.9.7.Final" + "version": "1.9.8.Final", + "versionMvn": "1.9.8.Final" }, "community": true, "product": false, From 9e1f459b8ef54229fffbe3cf8f6445911acb5e22 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 20:57:10 +0200 Subject: [PATCH 128/194] Set version to 2.0.0.CR1 --- book.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book.json b/book.json index 8485c6a2ca..b8744dfa48 100755 --- a/book.json +++ b/book.json @@ -12,8 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "1.9.8.Final", - "versionMvn": "1.9.8.Final" + "version": "2.0.0.CR1", + "versionMvn": "2.0.0.CR1" }, "community": true, "product": false, From ed4571d79a53728ccec0d0447c043a43a13a0215 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 13 Jun 2016 21:07:33 +0200 Subject: [PATCH 129/194] Force build From 7f8aad2d16129cf8f74ecae5856a90d0ab4d8569 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 23 Jun 2016 20:52:58 +0200 Subject: [PATCH 130/194] Change version --- book.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book.json b/book.json index 8485c6a2ca..66067958f1 100755 --- a/book.json +++ b/book.json @@ -12,8 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "1.9.8.Final", - "versionMvn": "1.9.8.Final" + "version": "SNAPSHOT", + "versionMvn": "SNAPSHOT" }, "community": true, "product": false, From 7d2c9c0064385ccfa98fcee0c77123e63428fc6e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 23 Jun 2016 20:57:38 +0200 Subject: [PATCH 131/194] Change version --- book.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book.json b/book.json index 66067958f1..8485c6a2ca 100755 --- a/book.json +++ b/book.json @@ -12,8 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "SNAPSHOT", - "versionMvn": "SNAPSHOT" + "version": "1.9.8.Final", + "versionMvn": "1.9.8.Final" }, "community": true, "product": false, From 70357e5342602eb6952316d7804567fb9256256a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 23 Jun 2016 20:56:57 +0200 Subject: [PATCH 132/194] Change version --- book.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book.json b/book.json index b8744dfa48..66067958f1 100755 --- a/book.json +++ b/book.json @@ -12,8 +12,8 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Keycloak", - "version": "2.0.0.CR1", - "versionMvn": "2.0.0.CR1" + "version": "SNAPSHOT", + "versionMvn": "SNAPSHOT" }, "community": true, "product": false, From 5aa0ec6ec02cca8202515bdbe308624f89945efe Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 28 Jun 2016 13:22:03 +0200 Subject: [PATCH 133/194] Update topics/client-registration.adoc --- topics/client-registration.adoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/topics/client-registration.adoc b/topics/client-registration.adoc index cd28d31058..17a177ddd4 100644 --- a/topics/client-registration.adoc +++ b/topics/client-registration.adoc @@ -149,7 +149,10 @@ String token = "eyJhbGciOiJSUz..."; ClientRepresentation client = new ClientRepresentation(); client.setClientId(CLIENT_ID); -ClientRegistration reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/myrealm/clients").build(); +ClientRegistration reg = ClientRegistration.create() + .url("http://localhost:8080/auth", "myrealm") + .build(); + reg.auth(Auth.token(token)); client = reg.create(client); From 27d50aab8440ad9175c56d7d877164bf69b7c5b3 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 11 Jul 2016 22:23:26 +0200 Subject: [PATCH 134/194] Document usage of environment variables in Keycloak.json Fixed #26 --- topics/oidc/java/java-adapter-config.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index b371d7d966..e2c0e6489f 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -37,6 +37,7 @@ This is what one might look like: ---- You can use `${...}` enclosure for system property replacement. For example `${jboss.server.config.dir}` would be replaced by `/path/to/{{book.project.name}}`. +Replacement of environment variables is also supported via the `env` prefix, e.g. `${env.MY_ENVIRONMENT_VARIABLE}`. The initial config file can be obtained from the the admin console. This can be done by opening the admin console, select `Clients` from the menu and clicking on the corresponding client. Once the page for the client is opened click on the `Installation` tab and select `Keycloak OIDC JSON`. From cf7ead03aa389e9d98377e27bd52c3e5935ee813 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 21 Jul 2016 18:58:14 +0200 Subject: [PATCH 135/194] KEYCLOAK-3318 Documentation for parameters forwarding from adapter. More docs for prompt and max_age --- SUMMARY.adoc | 1 + topics/oidc/java/params_forwarding.adoc | 32 +++++++++++++++++++++++++ topics/oidc/javascript-adapter.adoc | 8 +++---- 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 topics/oidc/java/params_forwarding.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index f5ed52c0e0..9729f18fe2 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -29,6 +29,7 @@ ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] + ... link:topics/oidc/java/params_forwarding.adoc[Parameters Forwarding] ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] ... link:topics/oidc/java/application-clustering.adoc[Application Clustering] diff --git a/topics/oidc/java/params_forwarding.adoc b/topics/oidc/java/params_forwarding.adoc new file mode 100644 index 0000000000..91c8300717 --- /dev/null +++ b/topics/oidc/java/params_forwarding.adoc @@ -0,0 +1,32 @@ + +==== Parameters Forwarding + +The {{book.project.name}} initial authorization endpoint request has support for various parameters. Most of the parameters are described in +http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification] . Some parameters are added automatically by adapter based +on the adapter configuration. However there are also few parameters, which can be added on per-invocation basis. When you open the secured application URI, +the particular parameter will be forwarded to the {{book.project.name}} authorization endpoint. + +For example, if you request offline token, then you can open the secured application URI with the `scope` parameter like: + +[source] +---- +http://myappserver/mysecuredapp?scope=offline_access +---- + +and the parameter `scope=offline_access` will be automatically forwarded to the {{book.project.name}} authorization endpoint. + +The supported parameters are actually: + +* scope + +* prompt + +* max_age + +* login_hint + +* kc_idp_hint + +Most of the parameters are described in the http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification]. +The only exception is parameter `kc_idp_hint`, which is specific to {{book.project.name}} and contains the name of Identity provider to automatically use. +More info in {{book.adminguide.link}}[{{book.adminguide.name}}] in `Identity Brokering` section. diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 029a510afe..8ea168e0b8 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -255,7 +255,8 @@ Redirects to login form on (options is an optional object with redirectUri and/o Options is an Object, where: * redirectUri - Specifies the uri to redirect to after login. -* prompt - By default the login screen is displayed if the user is not logged-in to {{book.project.name}}. To only authenticate to the application if the user is already logged-in and not display the login page if the user is not logged-in, set this option to `none`. +* prompt - By default the login screen is displayed if the user is not logged-in to {{book.project.name}}. To only authenticate to the application if the user is already logged-in and not display the login page if the user is not logged-in, set this option to `none`. To always require re-authentication and ignore SSO, set this option to `login` . +* maxAge - Used just if user is already authenticated. Specifies maximum time since the authentication of user happened. If user is already authenticated for longer time than `maxAge`, the SSO is ignored and he will need to re-authenticate again. * loginHint - Used to pre-fill the username/email field on the login form. * action - If value is 'register' then user is redirected to registration page, otherwise to login page. * locale - Specifies the desired locale for the UI. @@ -264,10 +265,7 @@ Options is an Object, where: Returns the URL to login form on (options is an optional object with redirectUri and/or prompt fields). -Options is an Object, where: - -* redirectUri - Specifies the uri to redirect to after login. -* prompt - Can be set to 'none' to check if the user is logged in already (if not logged in, a login form is not displayed). +Options is an Object, which supports same options like the function `login` . ====== logout(options) From 39ea15a7481e41b88db84beba01c469346b69061 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 2 Aug 2016 20:24:14 +0200 Subject: [PATCH 136/194] Document support for skipPath parameter in servlet-filter-adapter Fixes #29 Signed-off-by: Thomas Darimont --- topics/oidc/java/servlet-filter-adapter.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 9acdb735eb..681630412a 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -33,6 +33,20 @@ There's no way standard way to invalidate an HTTP session based on a session id. In the snippet above there are two url-patterns. _/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {{book.project.name}} server. +If you need to exclude some paths beneath the configured `url-patterns` you can use the Filter init-param `keycloak.config.skipPattern` to configure +a regular expression that describes a path-pattern for which the keycloak filter should immediately delegate to the filter-chain. +By default no skipPattern is configured. + +Patterns are matched against the `requestURI` without the `context-path`. Given the context-path `/myapp` a request for `/myapp/index.html` will be matched with `/index.html` against the skip pattern. + +[source,xml] +---- + + keycloak.config.skipPattern + ^/(path1|path2|path3).* + +---- + Note that you should configure your client in the {{book.project.name}} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern. The Admin URL will make callbacks to the Admin URL to do things like backchannel logout. From 44a12f0d4cf2e621e006aa7318ef8f75c4b53326 Mon Sep 17 00:00:00 2001 From: Mark Janssen Date: Sat, 27 Aug 2016 23:10:39 +0200 Subject: [PATCH 137/194] Project version for Maven dependency snippets --- topics/oidc/java/servlet-filter-adapter.adoc | 12 ++++++------ topics/oidc/java/spring-boot-adapter.adoc | 6 +++--- topics/oidc/java/spring-security-adapter.adoc | 8 +++----- topics/saml/java/servlet-filter-adapter.adoc | 6 +++--- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/topics/oidc/java/servlet-filter-adapter.adoc index 681630412a..72890d4422 100644 --- a/topics/oidc/java/servlet-filter-adapter.adoc +++ b/topics/oidc/java/servlet-filter-adapter.adoc @@ -56,11 +56,11 @@ The {{book.project.name}} filter has the same configuration parameters as the ot To use this filter, include this maven artifact in your WAR poms: -[source,xml, subs="attributes"] +[source,xml,subs="attributes+"] ---- -<dependency> - <groupId>org.keycloak</groupId> - <artifactId>keycloak-servlet-filter-adapter</artifactId> - <version>{{book.project.versionMvn}}</version> -</dependency> + + org.keycloak + keycloak-servlet-filter-adapter + {{book.project.versionMvn}} + ---- diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index 508f52cee1..01027cbc01 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -12,19 +12,19 @@ Depending on what container you are using with Spring Boot, you also need to add If you are using Maven, add the following to your pom.xml (using Tomcat as an example): -[source] +[source,xml,subs="attributes+"] ---- org.keycloak keycloak-spring-boot-adapter - &project.version; + {{book.project.versionMvn}} org.keycloak keycloak-tomcat8-adapter - &project.version; + {{book.project.versionMvn}} ---- diff --git a/topics/oidc/java/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc index 5c8c34db13..9da556a8a6 100755 --- a/topics/oidc/java/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -12,16 +12,14 @@ However, keycloak.json is still required. Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. -[source] +[source,xml,subs="attributes+"] ---- - - org.keycloak keycloak-spring-security-adapter - &project.version; + {{book.project.versionMvn}} ----- +---- ===== Spring Security Configuration diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/topics/saml/java/servlet-filter-adapter.adoc index f377f4f4b2..81e7bfa7e3 100644 --- a/topics/saml/java/servlet-filter-adapter.adoc +++ b/topics/saml/java/servlet-filter-adapter.adoc @@ -45,13 +45,13 @@ WARNING: You must have a filter mapping that covers `/saml`. When registering SPs with an IDP, you must register `http[s]://hostname/{context-root}/saml` as your Assert Consumer Service URL and Single Logout Service URL. -To use this filter, include this maven artifact in your WAR poms +To use this filter, include this maven artifact in your WAR poms: -[source,xml] +[source,xml,subs="attributes+"] ---- org.keycloak keycloak-saml-servlet-filter-adapter - &project.version; + {{book.project.versionMvn}} ---- From 6422fa0faebfe647dbb60b118df57674beca9b17 Mon Sep 17 00:00:00 2001 From: Jonas Kongslund Date: Sun, 4 Sep 2016 09:13:38 +0400 Subject: [PATCH 138/194] Corrected URL for Node.js Auth Utils --- topics/overview/supported-platforms.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 2d3e4d0fd2..4c0f20b19c 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -27,7 +27,7 @@ {% if book.community %} ===== Node.js * https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Connect] (community) -* https://github.com/keycloak/keycloak-nodejs-connect[{{book.project.name}} Auth Utils] (community) +* https://github.com/keycloak/keycloak-nodejs-auth-utils[{{book.project.name}} Auth Utils] (community) {% endif %} {% if book.community %} From 4e2ca4ce44bd6e2b410ab585d2e2171705222de9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 6 Sep 2016 09:04:14 +0200 Subject: [PATCH 139/194] Update topics/oidc/javascript-adapter.adoc --- topics/oidc/javascript-adapter.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 8ea168e0b8..2963168b7e 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -367,4 +367,4 @@ The available events are: * onAuthRefreshSuccess - Called when the token is refreshed. * onAuthRefreshError - Called if there was an error while trying to refresh the token. * onAuthLogout - Called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode). -* onTokenExpired - Called when the access token is expired. When this happens you can for refresh the token, or if refresh is not available (ie. with implicit flow) you can redirect to login screen. +* onTokenExpired - Called when the access token is expired. If a refresh token is available the token can be refreshed with updateToken, or in cases where it's not (ie. with implicit flow) you can redirect to login screen to obtain a new access token. From 6c3b91214983059dea49429694dec8e21d70d07c Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 6 Sep 2016 11:45:57 +0200 Subject: [PATCH 140/194] KEYCLOAK-3526 Fuse adapter ZIP docs --- SUMMARY.adoc | 1 + topics/oidc/java/fuse-adapter.adoc | 1 + topics/oidc/java/fuse/install-feature.adoc | 68 ++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 topics/oidc/java/fuse/install-feature.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 9729f18fe2..ed8fb7f5c4 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -11,6 +11,7 @@ ... link:topics/oidc/java/java-adapter-config.adoc[Java Adapters Config] ... link:topics/oidc/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] ... link:topics/oidc/java/fuse-adapter.adoc[JBoss Fuse Adapter] + .... link:topics/oidc/java/fuse/install-feature.adoc[Install Feature] .... link:topics/oidc/java/fuse/classic-war.adoc[Classic WAR application] .... link:topics/oidc/java/fuse/servlet-whiteboard.adoc[Servlet Deployed as OSGI Service] .... link:topics/oidc/java/fuse/camel.adoc[Apache Camel] diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 58f42914bd..9f3258a023 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -23,6 +23,7 @@ What is supported for Fuse is: ===== How to secure your web applications inside Fuse +The first thing to do is usually installing of {{book.project.name}} Karaf feature. Then do the steps according to what type of application you want to secure. Basically all mentioned web applications require to inject {{book.project.name}} Jetty authenticator into underlying Jetty server . The steps to achieve it are bit different according to application type. The details are described in individual sub-chapters. diff --git a/topics/oidc/java/fuse/install-feature.adoc b/topics/oidc/java/fuse/install-feature.adoc new file mode 100644 index 0000000000..42bb1e6823 --- /dev/null +++ b/topics/oidc/java/fuse/install-feature.adoc @@ -0,0 +1,68 @@ + +[[_fuse_install_feature]] +===== Install Feature + +First thing to be done is to install the feature `keycloak` into the JBoss Fuse environment. This will install the Fuse adapter +together with all needed 3rd party dependencies. There are 2 possibilities to install it. + + +====== Install from Maven Repository + +You need to be online and have access to the maven repository. + +{% if book.community %} +For community it's sufficient to be online as all the artifacts and 3rd party dependencies should be available in maven central repository. +{% endif %} + +You need to start JBoss Fuse and then in the Karaf terminal you type this: + +[source] +---- +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features +features:install keycloak +---- + +Then in JBoss Fuse 6.2 you may need to install Jetty 8 feature: + +[source] +---- +features:install keycloak-jetty8-adapter +---- + +Or in JBoss Fuse 6.3 you may need to install Jetty 9 feature: + +[source] +---- +features:install keycloak-jetty9-adapter +---- + +Then you can check that requested features were installed: + +[source] +---- +features:list | grep keycloak +---- + +====== Install from ZIP bundle + +This is useful if you are offline and/or don't want to use maven for download jar files and other artifacts. Once you download ZIP bundle of {{book.project.name}} Fuse adapter, +you will need to unzip it into the root directory of JBoss Fuse. This should install the dependencies under the `system` directory. For example see this for Fuse 6.2.1 : + +[source] +---- +cd /path-to-fuse/jboss-fuse-6.2.1.redhat-084 +unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-dist-{{book.project.versionMvn}}.zip +---- + +Feel free to overwrite all already existing jars. Once you unzip archive, you can start the Fuse and again run commands in fuse/karaf terminal + +[source] +---- +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features +features:install keycloak +---- + +And also install the corresponding Jetty adapter. The difference from the previous part is, that nothing will be downloaded from maven repository as the artifacts were +available directly in JBoss Fuse `system` directory. + + From aa594b18bbe45e4e139ef176aafd4b92239b7c04 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 9 Sep 2016 06:26:32 +0200 Subject: [PATCH 141/194] RHSSO-424 Document fuse adapter install in RHSSO --- topics/oidc/java/fuse/install-feature.adoc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/topics/oidc/java/fuse/install-feature.adoc b/topics/oidc/java/fuse/install-feature.adoc index 42bb1e6823..b2353392be 100644 --- a/topics/oidc/java/fuse/install-feature.adoc +++ b/topics/oidc/java/fuse/install-feature.adoc @@ -13,6 +13,21 @@ You need to be online and have access to the maven repository. {% if book.community %} For community it's sufficient to be online as all the artifacts and 3rd party dependencies should be available in maven central repository. {% endif %} +{% if book.product %} +For {{book.project.name}} you first need to configure proper maven repository, so you can install the artifacts. You can find on +https://access.redhat.com/maven-repository[Maven Repository] page what's the proper maven repository containing the bits. + +Assuming it's https://maven.repository.redhat.com/ga/ you will need to add this to the file `$FUSE_HOME/etc/org.ops4j.pax.url.mvn.cfg` +and add the repository to the list of supported repositories. For example it may look like this: + +[source] +---- +org.ops4j.pax.url.mvn.repositories= \ + https://maven.repository.redhat.com/ga@id=redhat.product.repo + http://repo1.maven.org/maven2@id=maven.central.repo, \ + ... +---- +{% endif %} You need to start JBoss Fuse and then in the Karaf terminal you type this: From a75534f80b219507a8820aad847f8794b7199fea Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 2 Sep 2016 17:25:29 -0300 Subject: [PATCH 142/194] [KEYCLOAK-2833] Migrate the Node.js documentation to the official repositories --- SUMMARY.adoc | 4 +- topics/oidc/nodejs-adapter.adoc | 132 +++++++++++++++++++++++ topics/overview/supported-platforms.adoc | 3 + 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 topics/oidc/nodejs-adapter.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index ed8fb7f5c4..2583642342 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -36,6 +36,8 @@ .. link:topics/oidc/javascript-adapter.adoc[JavaScript Adapter] + .. link:topics/oidc/nodejs-adapter.adoc[Node.js Adapter] + .. link:topics/oidc/oidc-generic.adoc[Other OpenID Connect libraries] {% if book.community %} ... link:topics/oidc/mod-auth-openidc.adoc[mod_auth_oidc Apache HTTPD Module] @@ -78,4 +80,4 @@ ... link:topics/saml/java/MigrationFromOlderVersions.adoc[Migration from older versions] {% endif %} .. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] - . link:topics/client-registration.adoc[Client Registration] \ No newline at end of file + . link:topics/client-registration.adoc[Client Registration] diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc new file mode 100644 index 0000000000..3c2f54cce2 --- /dev/null +++ b/topics/oidc/nodejs-adapter.adoc @@ -0,0 +1,132 @@ +[[_nodejs_adapter]] +=== Node.js Adapter + +{{book.project.name}} provides a Node.js adapter to protect JavaScript apps on the server side. The library can be downloaded directly from https://www.npmjs.com/package/keycloak-connect[ {{book.project.name}} organization] and the source is available at +https://github.com/keycloak/keycloak-nodejs-connect[GitHub]. + +To use the Node.js adapter you must first create a client for your application in the {{book.project.name}} Administration Console. The adapter supports public, confidential and bearer-only access type. Which one to choose depends on the use-case scenario. + +Once the client is created click on the `Installation` tab select `{{book.project.name}} OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be at the root folder. Exactly, like in https://github.com/keycloak/keycloak-nodejs-connect/tree/master/example[this example]. + +keycloak.json:: + +Alongside the `example.js` lives `keycloak.json` obtained from our {{book.project.name}} +admin console when we provisioned this app. + + + { + "realm": "example-realm", + "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "http://localhost:8080/auth", + "ssl-required": "external", + "resource": "example-app", + "credentials": { + "secret": "mysecret" + } + } + +==== Installation + +Assuming you've already installed https://nodejs.org[Node.js], create a folder for your application: + + mkdir myapp && cd myapp + +Use `npm init` command to create a `package.json` for your application. And now install the {{book.project.name}} connect adapter in the `myapp` folder, saving it in the dependencies list: + + npm install --save keycloak-connect + +==== Usage +Instantiate a Keycloak class:: + +The `Keycloak` class provides a central point for configuration +and integration with your application. The simplest creation +involves no arguments. + + var keycloak = new Keycloak(); + +By default, this will locate a file named `keycloak.json` alongside +the main executable of your application to initialize keycloak-specific +settings (public key, realm name, various URLs). The `keycloak.json` file +is obtained from the {{book.project.name}} Admin Console. + +Instantiation with this method results in all of the reasonable defaults +being used. + +Configuring a web session store:: + +If you wish to use web sessions to manage +server-side state for authentication, you will need to initialize the +`Keycloak(...)` with at least a `store` parameter, passing in the actual +session store that `express-session` is using. + + var session = require('express-session'); + var memoryStore = new session.MemoryStore(); + + var keycloak = new Keycloak({ store: memoryStore }); + +Passing a custom scope value:: + +By default, the scope value `openid` will be passed as query parameter to {{book.project.name}}'s login URL but you can add an additional custom value : + + var keycloak = new Keycloak({ scope: 'offline_access' }); + +==== Install middleware + +Once instantiated, install the middleware into your connect-capable app: + + var app = express(); + + app.use( keycloak.middleware() ); + +==== Protect resources + +Simple authentication:: + +To enforce that a user must be authenticated before accessing a resource, +simply use a no-argument version of `keycloak.protect()`: + + app.get( '/complain', keycloak.protect(), complaintHandler ); + +Role-based authorization:: + +To secure a resource with an application role for the current app: + + app.get( '/special', keycloak.protect('special'), specialHandler ); + +To secure a resource with an application role for a *different* app: + + app.get( '/extra-special', keycloak.protect('other-app:special', extraSpecialHandler ); + +To secure a resource with a realm role: + + app.get( '/admin', keycloak.protect( 'realm:admin' ), adminHandler ); + +Advanced authorization:: + +To secure resources based on parts of the URL itself, assuming a role exists +for each section: + + function protectBySection(token, request) { + return token.hasRole( request.params.section ); + } + + app.get( '/:section/:page', keycloak.protect( protectBySection ), sectionHandler ); + +==== Additional URLs + +Explicit user-triggered logout:: + +By default, the middleware catches calls to `/logout` to send the user through a +{{book.project.name}}-centric logout workflow. This can be changed by specifying a `logout` +configuration parameter to the `middleware()` call: + + app.use( keycloak.middleware( { logout: '/logoff' } )); + +{{book.project.name}} Admin Callbacks:: + +Also, the middleware supports callbacks from the {{book.project.name}} console to logout a single +session or all sessions. By default, these type of admin callbacks occur relative +to the root URL of `/` but can be changed by providing an `admin` parameter +to the `middleware()` call: + + app.use( keycloak.middleware( { admin: '/callbacks' } ); diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 4c0f20b19c..5de11f59da 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -21,6 +21,9 @@ ===== JavaScript (client-side) * <> +===== Node.js (server-side) +* <> + ===== Apache Cordova * <> From b9b1ebbd50fdef6b89977a916338ddc235c86ed5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 28 Sep 2016 09:09:09 -0300 Subject: [PATCH 143/194] Mention Connect and Express.js at the docs --- topics/oidc/nodejs-adapter.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index 3c2f54cce2..3fe80de868 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -1,7 +1,9 @@ [[_nodejs_adapter]] === Node.js Adapter -{{book.project.name}} provides a Node.js adapter to protect JavaScript apps on the server side. The library can be downloaded directly from https://www.npmjs.com/package/keycloak-connect[ {{book.project.name}} organization] and the source is available at +{{book.project.name}} provides a Node.js adapter built on top of https://github.com/senchalabs/connect[Connect] to protect server side JavaScript apps — the goal was to be flexible enough to integrate with frameworks like https://expressjs.com/[Express.js]. + +The library can be downloaded directly from https://www.npmjs.com/package/keycloak-connect[ {{book.project.name}} organization] and the source is available at https://github.com/keycloak/keycloak-nodejs-connect[GitHub]. To use the Node.js adapter you must first create a client for your application in the {{book.project.name}} Administration Console. The adapter supports public, confidential and bearer-only access type. Which one to choose depends on the use-case scenario. @@ -82,7 +84,7 @@ Once instantiated, install the middleware into your connect-capable app: Simple authentication:: -To enforce that a user must be authenticated before accessing a resource, +To enforce that an user must be authenticated before accessing a resource, simply use a no-argument version of `keycloak.protect()`: app.get( '/complain', keycloak.protect(), complaintHandler ); From 186ac3b670a64935ffc378164b159d77587ad3fd Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Thu, 29 Sep 2016 14:30:44 -0400 Subject: [PATCH 144/194] removed references to Java servlet filter and JAAS plugin --- SUMMARY.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 2583642342..ce62271754 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -25,8 +25,10 @@ ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] {% endif %} + {% if book.community %} ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/oidc/java/jaas.adoc[JAAS plugin] + {% endif %} ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] @@ -70,7 +72,9 @@ .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] {% endif %} + {% if book.community %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + {% endif %} ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] ... link:topics/saml/java/logout.adoc[Logout] ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] From 53f0279e8d295220d92f29f749f8d67bd90049ef Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Mon, 3 Oct 2016 11:50:52 -0400 Subject: [PATCH 145/194] Remove reference to Javascript adapter for RH SSO 7.0 docs --- topics/overview/supported-platforms.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 2d3e4d0fd2..4cba044b7a 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -18,8 +18,10 @@ * <> (community) {% endif %} +{% if book.community %} ===== JavaScript (client-side) * <> +{% endif %} ===== Apache Cordova * <> @@ -71,5 +73,3 @@ ===== Apache HTTP Server * https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] - - From 543375d32c07919f332444bbdb5468c113e45092 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 4 Oct 2016 17:55:53 +0200 Subject: [PATCH 146/194] KEYCLOAK-3643 Remove public-key references in keycloak.json . Add more docs about client authentication --- SUMMARY.adoc | 1 + book.json | 8 +++ topics/oidc/java/client-authentication.adoc | 74 +++++++++++++++++++++ topics/oidc/java/fuse/camel.adoc | 1 - topics/oidc/java/fuse/cxf-separate.adoc | 1 - topics/oidc/java/fuse/fuse-admin.adoc | 1 - topics/oidc/java/java-adapter-config.adoc | 14 +++- topics/oidc/java/jboss-adapter.adoc | 2 - topics/oidc/java/jetty9-adapter.adoc | 1 - topics/oidc/java/spring-boot-adapter.adoc | 1 - topics/oidc/nodejs-adapter.adoc | 1 - 11 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 topics/oidc/java/client-authentication.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 2583642342..f0745d06fe 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -31,6 +31,7 @@ ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] ... link:topics/oidc/java/params_forwarding.adoc[Parameters Forwarding] + ... link:topics/oidc/java/client-authentication.adoc[Client Authentication] ... link:topics/oidc/java/multi-tenancy.adoc[Multi Tenancy] ... link:topics/oidc/java/application-clustering.adoc[Application Clustering] diff --git a/book.json b/book.json index 66067958f1..68d0d08daa 100755 --- a/book.json +++ b/book.json @@ -18,9 +18,17 @@ "community": true, "product": false, "images": "keycloak-images", + "developerguide": { + "name": "Server Developer Guide", + "link": "https://keycloak.gitbooks.io/server-developer-guide/content/" + }, "adminguide": { "name": "Server Administration Guide", "link": "https://keycloak.gitbooks.io/server-adminstration-guide/content/" + }, + "installguide": { + "name": "Server Installation and Configuration Guide", + "link": "https://keycloak.gitbooks.io/server-installation-and-configuration/content/" } } } diff --git a/topics/oidc/java/client-authentication.adoc b/topics/oidc/java/client-authentication.adoc new file mode 100644 index 0000000000..a8b647e40c --- /dev/null +++ b/topics/oidc/java/client-authentication.adoc @@ -0,0 +1,74 @@ +[[_client_authentication_adapter]] +==== Client Authentication + +When confidential OIDC client needs to send backchannel request (eg. exchange code for the token, or refresh token) it needs to authenticate +against {{book.project.name}} server. By default, there are 2 possibilities how to authenticate client: + +===== Client ID and Client Secret + +This is traditional method described in OAuth2 specification. Client has a secret, which needs to be known to both adapter (application) and {{book.project.name}} server. +You can simply generate the secret for particular client in {{book.project.name}} admin console and then paste this secret in the `keycloak.json` file on the application's side: + + +[source] +---- +"credentials": { + "secret": "19666a4f-32dd-4049-b082-684c74115f28" +} +---- + +===== Client authentication with signed JWT + +This is based on the https://tools.ietf.org/html/rfc7523[RFC7523] specification. It works this way: + +* Client must have the private key and certificate. In case of {{book.project.name}} this is available through the traditional `keystore` file, which is either available +on client application's classpath or somewhere on the filesystem. + +* Once the client application is started, it allows to download it's public key in https://self-issued.info/docs/draft-ietf-jose-json-web-key.html[JWKS] format on URL +like http://myhost.com/myapp/k_jwks assuming that http://myhost.com/myapp is the base URL of your client application. This URL can be used by the {{book.project.name}} (see below). + +* During authentication, client generates JWT token and signs it with his private key and sends it to the {{book.project.name}} in +the particular backchannel request (eg. code-to-token request) in the `client_assertion` parameter. + +* {{book.project.name}} must have public key or certificate of the client, so that it can verify the signature on JWT. In {{book.project.name}} you either +need to configure client credentials for your client. First you need to choose `Signed JWT` as the method of authenticating your client in the tab `Credentials` in admin console. +Then you can choose either to: +** Configure the JWKS URL where {{book.project.name}} can download the client's public keys. This can be URL like http://myhost.com/myapp/k_jwks (see details above). This is flexible as +client can rotate it's keys anytime and {{book.project.name}} will then always download new keys when needed without need to change something in it's configuration. More acurately, {{book.project.name}} +will download new keys when it sees the token signed by unknown `kid` (Key ID). +** Upload the client's public key or certificate - either in PEM format, in JWK format or from keystore. With this option, public key is hardcoded and +needs to be changed anytime when client generates new keypair. +You can even generate your own keystore from {{book.project.name}} admin console if you don't have your own ready. +See {{book.adminguide.link}}[{{book.adminguide.name}}] for more details of setup in {{book.project.name}} admin console. + +For setup on adapter's side you need to have something like this in your `keycloak.json` file: + +[source] +---- +"credentials": { + "jwt": { + "client-keystore-file": "classpath:keystore-client.jks", + "client-keystore-type": "JKS", + "client-keystore-password": "storepass", + "client-key-password": "keypass", + "client-key-alias": "clientkey", + "token-expiration": 10 + } +} +---- + +With this configuration, the keystore file `keystore-client.jks` must be available on classpath in your WAR. If you don't use prefix `classpath:` +you can point to any file on the filesystem where client application is running. + +{% if book.community %} +For inspiration, you can take a look at the examples distribution into the main demo example into the `product-portal` application. + + +===== Add your own client authentication method + +This is possible. You will need to implement both client's side and server's side providers. See `Authentication SPI` section +in {{book.developerguide.link}}[{{book.developerguide.name}}] for more details. + +{% endif %} + + diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index 3e5668b71a..1cd7a21564 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -20,7 +20,6 @@ The roles, security constraint mappings and {{book.project.name}} adapter config - diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/topics/oidc/java/fuse/cxf-separate.adoc index 178775b56b..eb10c8d076 100644 --- a/topics/oidc/java/fuse/cxf-separate.adoc +++ b/topics/oidc/java/fuse/cxf-separate.adoc @@ -26,7 +26,6 @@ injected `KeycloakJettyAuthenticator` inside. The configuration may look like th - diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index 48410203c4..e90849f598 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -27,7 +27,6 @@ sshRealm=keycloak { "realm": "demo", "resource": "ssh-jmx-admin-client", - "realm-public-key": "MIGfMA...", "ssl-required" : "external", "auth-server-url" : "http://localhost:8080/auth", "credentials": { diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index e2c0e6489f..f9ce2073b2 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -32,7 +32,8 @@ This is what one might look like: "client-keystore" : "path/to/client-keystore.jks", "client-keystore-password" : "geheim", "client-key-password" : "geheim", - "token-minimum-time-to-live" : 10 + "token-minimum-time-to-live" : 10, + "min-time-between-jwks-requests" : 10 } ---- @@ -54,7 +55,9 @@ resource:: realm-public-key:: PEM format of the realm public key. You can obtain this from the administration console. - This is _OPTIONAL_. If not set the adapter will download this from {{book.project.name}}. + This is _OPTIONAL_ and it's not recommended to set it. If not set, the adapter will download this from {{book.project.name}} and + it will always re-download it when needed (eg. {{book.project.name}} rotate it's keys). However if realm-public-key is set, then adapter + will never download new keys from {{book.project.name}}, so when {{book.project.name}} rotate it's keys, adapter will break. auth-server-url:: The base URL of the {{book.project.name}} server. All other {{book.project.name}} pages and REST service endpoints are derived from this. It is usually of the form `$$https://host:port/auth$$`. @@ -198,3 +201,10 @@ token-minimum-time-to-live:: This is especially useful when the access token is sent to another REST client where it could expire before being evaluated. This value should never exceed the realm's access token lifespan. This is _OPTIONAL_. The default value is `0` seconds, so adapter will refresh access token just if it's expired. + +min-time-between-jwks-requests:: + Amount of time, in seconds, specifying minimum interval between two requests to {{book.project.name}} to retrieve new public keys. + It is 10 seconds by default. + Adapter will always try to download new public key when it recognize token with unknown `kid` . However it won't try it more + than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of tokens with bad `kid` forcing adapter + to send lots of requests to {{book.project.name}}. diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index e9036004d0..0686c1c51b 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -260,7 +260,6 @@ This metadata is instead defined within server configuration (i.e. `standalone.x demo - MIGfMA0GCSqGSIb3DQEBAQUAA http://localhost:8081/auth external customer-portal @@ -284,7 +283,6 @@ If you have multiple deployments secured by the same realm you can share the rea ---- - MIGfMA0GCSqGSIb3DQEBA... http://localhost:8080/auth external diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index 70946313a6..7f0bd8bf8d 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -92,7 +92,6 @@ You'll just have to figure out how the json settings match to the `org.keycloak. - MIGfMA0GCSqGSIb3DQEBAQUAA4 diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index 01027cbc01..6cce21170d 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -41,7 +41,6 @@ For example: keycloak.realm = demorealm -keycloak.realmKey = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCWYuxXmsmfV+Xc9Ik8QET8lD4wuHrJAXbbutS2O/eMjQQLNK7QDX/k/XhOkhxP0YBEypqeXeGaeQJjCxDhFjJXQuewUEMlmSja3IpoJ9/hFn4Cns4m7NGO+rtvnfnwgVfsEOS5EmZhRddp+40KBPPJfTH6Vgu6KjQwuFPj6DTwIDAQAB keycloak.auth-server-url = http://127.0.0.1:8080/auth keycloak.ssl-required = external keycloak.resource = demoapp diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index 3fe80de868..e09e124b12 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -18,7 +18,6 @@ admin console when we provisioned this app. { "realm": "example-realm", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-server-url": "http://localhost:8080/auth", "ssl-required": "external", "resource": "example-app", From b1895ce961e27d5a18aecb9e323063496f7fb399 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Tue, 4 Oct 2016 16:36:42 -0400 Subject: [PATCH 147/194] fixed Java Servlet bullet inclusion --- topics/overview/supported-platforms.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 4cba044b7a..d3522cb482 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -12,8 +12,9 @@ * <> * <>, <> {% endif %} -* <> + {% if book.community %} +* <> * <> (community) * <> (community) {% endif %} From 258e9f0210c08c233001d2114c7278ef433aa6b2 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Tue, 4 Oct 2016 17:25:33 -0400 Subject: [PATCH 148/194] correcting master branch --- SUMMARY.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 0abf5d6787..81ddcfbf19 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -24,11 +24,9 @@ ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] - {% endif %} - {% if book.community %} + {% endif %} ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/oidc/java/jaas.adoc[JAAS plugin] - {% endif %} ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] From cfcb88b2429dd3f2ba9a70838efb5f20f71f2033 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Tue, 4 Oct 2016 17:38:45 -0400 Subject: [PATCH 149/194] fixed second Java Servlet Filter reference --- SUMMARY.adoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 81ddcfbf19..74a0ea4f70 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -71,9 +71,7 @@ .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] {% endif %} - {% if book.community %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] - {% endif %} ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] ... link:topics/saml/java/logout.adoc[Logout] ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] From 434b34539895755c66b9557b1b5be7932c619536 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Wed, 5 Oct 2016 15:14:11 -0400 Subject: [PATCH 150/194] fixing missing book community tags in master --- SUMMARY.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 74a0ea4f70..5005d4238c 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -24,9 +24,11 @@ ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] - {% endif %} + {% endif %} + {% if book.community %} ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/oidc/java/jaas.adoc[JAAS plugin] + {% endif %} ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] @@ -71,7 +73,9 @@ .... link:topics/saml/java/jetty-adapter/jetty8-installation.adoc[Jetty 8 Adapter Installation] .... link:topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc[Jetty 8 Per WAR Configuration] {% endif %} + {% if book.community %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] + {% endif %} ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] ... link:topics/saml/java/logout.adoc[Logout] ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] From 50131108b544f3897f82aef680ba259072703977 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Mon, 10 Oct 2016 16:05:21 -0400 Subject: [PATCH 151/194] add a line space --- topics/overview/supported-protocols.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/topics/overview/supported-protocols.adoc b/topics/overview/supported-protocols.adoc index 104de15bcd..285284c8e2 100644 --- a/topics/overview/supported-protocols.adoc +++ b/topics/overview/supported-protocols.adoc @@ -1,4 +1,5 @@ [[_supported_protocols]] + === Supported Protocols ==== OpenID Connect From 1de20b139b34d558392363da03a34cf9312f5810 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Mon, 17 Oct 2016 19:01:15 +0900 Subject: [PATCH 152/194] KEYCLOAK-3718 fix logout URL --- topics/oidc/java/logout.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/oidc/java/logout.adoc b/topics/oidc/java/logout.adoc index ee285201ab..f896efe88a 100755 --- a/topics/oidc/java/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -2,4 +2,4 @@ There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can redirect the browser to -`$$http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri$$`. This will log you out if you have a SSO session with your browser. \ No newline at end of file +`$$http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri$$`. This will log you out if you have a SSO session with your browser. \ No newline at end of file From 7c1b77582512974997ddfd1811b4201f5a51bde2 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 19 Oct 2016 17:52:42 +0200 Subject: [PATCH 153/194] Remove tech preview for OIDC --- topics/oidc/oidc-generic.adoc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/topics/oidc/oidc-generic.adoc b/topics/oidc/oidc-generic.adoc index 434141bed9..f79ed88de1 100644 --- a/topics/oidc/oidc-generic.adoc +++ b/topics/oidc/oidc-generic.adoc @@ -1,9 +1,5 @@ === Other OpenID Connect libraries -{% if book.product %} -NOTE: Using {{book.project.name}} with generic OpenID Connect libraries is a Technology Preview feature and is not fully supported -{% endif %} - {{book.project.name}} can be secured by supplied adapters that usually are easier to use and provide better integration with {{book.project.name}}. However, if there is no adapter available for your programming language, framework or platform you may opt to use a generic OpenID Connect Resource Provider (RP) library instead. This chapter describes details specific to {{book.project.name}} and doesn't go into low-level details of the protocols. For more details refer to the From 4404460e75096f8ce4980c0c5150ffa6076d3e50 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 21 Oct 2016 09:03:06 +0200 Subject: [PATCH 154/194] KEYCLOAK-3666 Client Registration Policies docs --- topics/client-registration.adoc | 53 +++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/topics/client-registration.adoc b/topics/client-registration.adoc index 17a177ddd4..a96d5fb358 100644 --- a/topics/client-registration.adoc +++ b/topics/client-registration.adoc @@ -19,7 +19,8 @@ The following sections will describe how to use the different providers. === Authentication -To invoke the Client Registration Services you need a token. The token can be a bearer token, an initial access token or a registration access token. +To invoke the Client Registration Services you usually need a token. The token can be a bearer token, an initial access token or a registration access token. +There is an alternative to register new client without any token as well, but then you need to configure Client Registration Policies (see below). ==== Bearer Token @@ -37,7 +38,8 @@ The recommended approach to registering new clients is by using initial access t An initial access token can only be used to create clients and has a configurable expiration as well as a configurable limit on how many clients can be created. An initial access token can be created through the admin console. -To create a new initial access token first select the realm in the admin console, then click on `Realm Settings` in the menu on the left, followed by `Initial Access Tokens` in the tabs displayed in the page. +To create a new initial access token first select the realm in the admin console, then click on `Realm Settings` in the menu on the left, followed by +`Client Registration` in the tabs displayed in the page. Then finally click on `Initial Access Tokens` sub-tab. You will now be able to see any existing initial access tokens. If you have access you can delete tokens that are no longer required. You can only retrieve the value of the token when you are creating it. To create a new token click on `Create`. You can now optionally add how long the token should be valid, also how @@ -158,4 +160,49 @@ reg.auth(Auth.token(token)); client = reg.create(client); String registrationAccessToken = client.getRegistrationAccessToken(); ----- +---- + +=== Client Registration Policies + +{{book.project.name}} currently supports 2 ways how can be new clients registered through Client Registration Service. + +* Authenticated requests - Request to register new client must contain either `Initial Access Token` or `Bearer Token` as mentioned above. + +* Anonymous requests - Request to register new client doesn't need to contain any token at all + +Anonymous client registration requests are very interesting and powerful feature, however you usually don't want that anyone is able to register new +client without any limitations. Hence we have `Client Registration Policy SPI`, which provide a way to limit who can register new clients and under which conditions. + +In {{book.project.name}} admin console, you can click to `Client Registration` tab and then `Client Registration Policies` sub-tab. Here you will see what policies +are configured by default for anonymous requests and what policies are configured for authenticated requests. + +NOTE: The anonymous requests (requests without any token) are allowed just for creating (registration) of new clients. So when you register +new client through anonymous request, the response will contain Registration Access Token, which must be used for Read, Update or Delete request of particular client. +However using this Registration Access Token from anonymous registration will be then subject to Anonymous Policy too! This means that for example request for update +client also needs to come from Trusted Host if you have `Trusted Hosts` policy. Also for example it won't be allowed to disable `Consent Required` when updating client and +when `Consent Required` policy is present etc. + +Currently we have these policy implementations: + +* Trusted Hosts Policy - You can configure list of trusted hosts and trusted domains. Request to Client Registration Service can be sent just from those hosts or domains. +Request sent from some untrusted IP will be rejected. URLs of newly registered client must also use just those trusted hosts or domains. For example it won't be allowed +to set `Redirect URI` of client pointing to some untrusted host. By default, there is not any whitelisted host, so anonymous client registration is de-facto disabled by default. + +* Consent Required Policy - Newly registered clients will have `Consent Allowed` switch enabled. So after successful authentication, user will always +see consent screen when he needs to approve personal info and permissions (protocol mappers and roles). It means that client won't have access to any personal +info or permission of user unless user approves it. + +* Protocol Mappers Policy - Allows to configure list of whitelisted protocol mapper implementations. New client can't be registered +or updated if it contains some non-whitelisted protocol mapper. Note that this policy is used for authenticated requests as well, so +even for authenticated request there are some limitations which protocol mappers can be used. + +* Client Template Policy - Allow to whitelist `Client Templates`, which can be used with newly registered or updated clients. +There are no whitelisted templates by default. + +* Full Scope Policy - Newly registered clients will have `Full Scope Allowed` switch disabled. This means they won't have any scoped +realm roles or client roles of other clients. + +* Max Clients Policy - Rejects registration if current number of clients in the realm is same or bigger than specified limit. It's 200 by default for anonymous registrations. + +* Client Disabled Policy - Newly registered client will be disabled. This means that admin needs to manually approve and enable all newly registered clients. +This policy is not used by default even for anonymous registration. From 13e1c545461224ce6162979bc161322a4b6384da Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Mon, 8 Aug 2016 10:48:01 +0200 Subject: [PATCH 155/194] Add client-registration-cli documentation --- SUMMARY.adoc | 1 + .../client-registration-cli.adoc | 386 ++++++++++++++++++ 2 files changed, 387 insertions(+) create mode 100644 topics/client-registration/client-registration-cli.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index 5005d4238c..fba2af9105 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -86,3 +86,4 @@ {% endif %} .. link:topics/saml/mod-auth-mellon.adoc[mod_auth_mellon Apache HTTPD Module] . link:topics/client-registration.adoc[Client Registration] + .. link:topics/client-registration/client-registration-cli.adoc[Client Registration CLI] diff --git a/topics/client-registration/client-registration-cli.adoc b/topics/client-registration/client-registration-cli.adoc new file mode 100644 index 0000000000..138acede8d --- /dev/null +++ b/topics/client-registration/client-registration-cli.adoc @@ -0,0 +1,386 @@ +[[_client_registration_cli]] +== Client Registration CLI + +`Client Registration CLI` is a command line interface tool that can be used by application developers to configure new clients +to integrate with {book_project_name}. It is specifically designed to interact with {{book.project.name}} Client Registration REST endpoints. + +It is necessary to create a new client configuration for each new application hosted on a unique hostname in order for Keycloak +to be able to interact with the application (and vice-versa) and perform its function of providing a login page, SSO session management etc. + +`Client Registration CLI` allows you to configure application clients from a command line, and can be used in shell scripts as well. + +To allow a particular user to use `Client Registration CLI` a {book_project_name} administrator will typically use `Admin Console` to configure + a new user, or configure a new client, and a client secret, to protect access to `Client Registration REST API`. + + +[[_configuring_a_user_for_client_registration_cli]] +=== Configuring a new regular user for use with Client Registration CLI + +Login as `admin` into `Admin Console` (e.g. `http://localhost:8080/auth/admin`). Select a realm you want to administer. +If you want to use existing user, select it for edit, otherwise create a new user. Go to `Role Mappings` tab. Under +`Client Roles` select `realm-management`. Under `Available Roles` select `manage-client` for full set of client management +permissions. Alternatively you can choose `view-clients` for read-only or `create-client` for ability to create new clients. +These permissions grant user the capability to perform operations without the use of `Initial Access Token` or +`Registration Access Token`. + +It's possible to not assign users any of `realm-management` roles. In that case user can still login with `Registration Client CLI` +but will not be able to use it without having possession of an `Initial Access Token`. Trying to perform any operations +without it will result in `403 Forbidden` error. + +Administrator can issue `Initial Access Tokens` from `Admin Console` by selecting `Initial Access Token` tab under `Realm Settings`. + +[[_configuring_a_client_for_use_with_client_registration_cli]] +=== Configuring a client for use with Client Registration CLI + +By default the `Client Registration CLI` identifies as `admin-cli` client which is automatically configured for every new realm. +No additional client configuration is required when using login with a username. You may wish to strengthen security by +configuring the client `Access Type` as `Confidential`, and under `Credentials` tab select `ClientId and Secret`. When +running `kcreg config credentials` you would then also have to provide a secret e.g. by using `--secret`. + +If you want to use a separate client configuration for `Registration Client CLI` then you can create a new client - for +example you can call it `reg-cli`. When running `kcreg config credentials` you then need to specify a `clientId` to use e.g. `--client reg-cli`. + +If you want to use a service account associated with the client, then you need to enable a service account. In `Admin Client` +you go to `Clients` section, and select a client for edit. Then under `Settings` first change `Access Type` to `Confidential`. +Then toggle `Service Accounts Enabled` setting to `On`, and `Save` the configuration. + +Under `Credentials` tab you can choose to configure either `Client Id and Secret`, or `Signed JWT`. + +You can now avoid specifying user when using `kcreg config credentials` and only provide a client secret or keystore info. + +[[_installing_client_registration_cli]] +=== Installing Client Registration CLI + +Client Registration CLI is packaged inside Keycloak Server distribution. You can find execution scripts inside `bin` directory. + +The Linux script is called `kcreg.sh`, and the one for Windows is called `kcreg.bat`. + +In order to setup the client to be used from any location on the filesystem you may want to add Keycloak server directory to your PATH. + +On Linux: +[source,bash] +---- +$ export PATH=$PATH:$KEYCLOAK_HOME/bin +$ kcreg.sh +---- + +On Windows: +[source,bash] +---- +c:\> set PATH=%PATH%;%KEYCLOAK_HOME%\bin +c:\> kcreg +---- + +[[_using_client_registration_cli]] +=== Using Client Registration CLI + +Usually a user will first start an authenticated session by providing credentials, then perform some CRUD operations. + +For example on Linux: + +[source,bash] +---- +$ kcreg.sh config credentials --server http://localhost:8080/auth --realm demo --user user --client reg-cli +$ kcreg.sh create -s clientId=my_client -s 'redirectUris=["http://localhost:8980/myapp/*"]' +$ kcreg.sh get my_client +---- + +Or on Windows: + +[source,bash] +---- +c:\> kcreg config credentials --server http://localhost:8080/auth --realm demo --user user --client reg-cli +c:\> kcreg create -s clientId=my_client -s "redirectUris=[\"http://localhost:8980/myapp/*\"]" +c:\> kcreg get +---- + + +In a production environment Keycloak has to be accessed with `https:` to avoid exposing tokens to network sniffers. If server's +certificate is not issued by one of the trusted CAs that are included in Java's default certificate truststore, then you will +need to prepare a truststore.jks file, and instruct `Client Registration CLI` to use it. + +For example on Linux: +[source,bash] +---- +$ kcreg.sh config truststore --trustpass $PASSWORD ~/.keycloak/truststore.jks +---- + +Or on Windows: + +[source,bash] +---- +c:\> kcreg config truststore --trustpass %PASSWORD% %HOMEPATH%\.keycloak\truststore.jks +---- + + +[[_logging_in]] +==== Logging In + +When logging in with `Client Registration CLI` you specify a server endpoint url, and a realm. Then you specify a username, +or alternatively you can only specify a client id, which will result in special service account being used. In the first case, +a password for the specified user has to be used at login. In the latter case there is no user password - only client secret +or a `Signed JWT` is used. + +Regardless of the method, the account that logs in needs to have proper permissions in order to be able to perform client +registration operations. Keep in mind that any account can only have permissions to manage clients within the same realm. +If you need to manage different realms, you need to configure users in different realms with permissions to manage clients. + +`Client Registration CLI` by itself does not support configuring the users, for that you would need to use `Admin Console` +web interface or `Admin Client CLI` once it's available. + +When `kcreg` successfully logs in it receives authorization tokens and saves them into a private config file so they can be +used for subsequent invocations. See <> for more info on configuration file. + +See built-in help for more information on using `Client Registration CLI`. + + +For example on Linux: +[source,bash] +---- +$ kcreg.sh help +---- + + +Or on Windows: +[source,bash] +---- +c:\> kcreg help +---- + +See `kcreg config credentials --help` for more information about starting an authenticated session. + + + +[[_working_with_alternative_configurations]] +==== Working with alternative configurations + +By default, `Client Registration CLI` automatically maintains a configuration file at a default location - `.keycloak/kcreg.config` +under user's home directory. + +You can use `--config` option at any time to point to a different file / location. This way you can mantain multiple authenticated +sessions in parallel. It is safest to perform operations tied to a single config file from a single thread. + +Make sure to not make a config file visible to other users on the system as it contains access tokens, and secrets that should be kept private. + +You may want to avoid storing any secrets at all inside a config file for the price of less convenience and having to do more token requests. +In that case you can use `--no-config` option with all your commands. You will have to specify all authentication info with each +`kcreg` invocation. + + + +[[_initial_access_and_registration_access_tokens]] +==== Initial Access and Registration Access Tokens + +`Client Registration CLI` can be used by developers who don't have an account configured at Keycloak server they want to use. +That's possible when realm administrator issues developer an `Initial Access Token`. It is up to realm administrator to decide +how to issue and distribute these tokens. Admin can limit an Initial Access Token by maximum age, and a total number of clients +that can be created with it. Many Initial Access Tokens can be created, and it's up to realm administrator to distribute them. + +Once a developer is in possession of Initial Access Token they can use it to create new clients without authenticating +with `kcreg config credentials`. Rather, Initial Access Token can be stored in configuration, or specified as part of `kcreg create` +command. + +For example on Linux: +[source,bash] +---- +$ kcreg.sh config initial-token $TOKEN +$ kcreg.sh create -s clientId=myclient +---- + +or + +[source,bash] +---- +$ kcreg.sh create -s clientId=myclient -t $TOKEN +---- + + +On Windows: +[source,bash] +---- +c:\> kcreg config initial-token %TOKEN% +c:\> kcreg create -s clientId=myclient +---- + +or + +[source,bash] +---- +c:\> kcreg create -s clientId=myclient -t %TOKEN% +---- + + +When Initial Access Token is used, the server response will include a newly issued Registration Access Token for client that was +just created. Any subsequent operation for that client needs to be performed by authenticating with that token. + +`Client Registration CLI` automatically uses its private configuration file to save, and make use of this token for each +created client. As long as the same configuration file is used for all client operations, the developer will not need to +authenticate in order to read, update, or delete a client they created. + + +You can read more about Initial Access and Registration Access Tokens in <>. + +See `kcreg config initial-token --help` and `kcreg config registration-token --help` for more information on how to configure them with `Client Registration CLI`. + + + +[[_performing_crud_operations]] +==== Performing CRUD operations + + +After authenticating with credentials or configuring Initial Access Token, the first operation will usually be to create a new client. + +We've seen the simplest command to create a new client already. Often we may want to use a prepared JSON file as a template, +and set / override some of the attributes. For example, this is how you read a JSON file in default client configuration format, +override any clientId it may contain with a new one, override / set any other attributes as well, and after successful creation +print the new client configuration to standard output. + +On Linux: +[source,bash] +---- +$ kcreg.sh create -s clientId=myclient -f client-template.json -s baseUrl=/myclient -s 'redirectUris=["/myclient/*"]' -o +---- + +On Windows: +[source,bash] +---- +C:\> kcreg create -s clientId=myclient -f client-template.json -s baseUrl=/myclient -s "redirectUris=[\"/myclient/*\"]" -o +---- + + +See `kcreg create --help` for more information about `kcreg create`. + + +You can use `kcreg attrs` to list the available attributes. Note, that many configuration attributes are not checked for +validity or consistency. It is up to you to specify proper values. Also note, that you should not have any `id` fields in your +template or specify them as arguments to `kcreg create`. + + +Once a new client is created you can retrieve it again by using `kcreg get`. + +On Linux: +[source,bash] +---- +$ kcreg.sh get +---- + +On Windows: +[source,bash] +---- +C:\> kcreg get +---- + + +You can also get an adapter configuration which you can drop into your web application in order to integrate with Keycloak server. + +On Linux: +[source,bash] +---- +$ kcreg.sh get -e install +---- + +On Windows: +[source,bash] +---- +C:\> kcreg get -e install +---- + +See `kcreg get --help` for more information about `kcreg get`. + + +It's simple to update client configurations as well. There are two modes of updating. + +One is to submit a complete new state to the server after getting current configuration, saving it into a file, editing it, and posting it back. + +On Linux: +[source,bash] +---- +$ kcreg.sh get > myclient.json +$ vi myclient.json +$ kcreg.sh update myclient -f myclient.json +---- + +On Windows: +[source,bash] +---- +C:\> kcreg get > myclient.json +C:\> notepad myclient.json +C:\> kcreg update myclient -f myclient.json +---- + + +Another is to get current client, set or delete fields on it, and post it back all in one single step. + +On Linux: +[source,bash] +---- +$ kcreg.sh update myclient -s enabled=false -d redirectUris +---- + +On Windows: +[source,bash] +---- +C:\> kcreg update myclient -s enabled=false -d redirectUris +---- + + +You can even use a file that contains only changes to be applied so you don't have to specify too many values as arguments. +In this case we specify `--merge` to tell `Client Registration CLI` that rather than treating mychanges.json as full +new configuration, it should see it as a set of attributes to be applied over existing configuration. + + +On Linux: +[source,bash] +---- +$ kcreg.sh update myclient --merge -d redirectUris -f mychanges.json +---- + +On Windows: +[source,bash] +---- +C:\> kcreg update myclient --merge -d redirectUris -f mychanges.json +---- + +See `kcreg update --help` for more information about `kcreg update`. + + +You may sometimes also need to delete a client. + +On Linux: +[source,bash] +---- +$ kcreg.sh delete myclient +---- + +On Windows: +[source,bash] +---- +C:\> kcreg delete myclient +---- + +See `kcreg delete --help` for more information about `kcreg delete`. + + + +[[_refreshing_invalid_registration_access_tokens]] +==== Refreshing Invalid Registration Access Tokens + +When performing CRUD operation using `no-config` mode `Client Registration CLI` can no longer handle Registration Access Tokens for you. +In that case it is possible to lose track of most recently issued Registration Access Token for a client, which makes it impossible to +perform any further CRUD operations on that client without using credentials of an account with 'manage-clients' permissions. + +If you have permissions you can reissue a new Registration Access Token for the client, and have it printed to stdout or saved to a config +file of your choice. If not you have to ask realm administrator to reissue a new Registration Access Token for your client, and send it +to you. You can then use the token by passing it to any CRUD command via `--token` option. You can also use `kcreg config registration-token` +command to save the new token in configuration file, and have `Client Registration CLI` automatically handle it for you from that point on. + +See `kcreg update-token --help` for more information about `kcreg update-token`. + + + +[[_troubleshooting_2]] +=== Troubleshooting + +* Q: When logging in I get an error: `Parameter client_assertion_type is missing [invalid_client]` ++ +A: Your client is configured with `Signed JWT` token credentials which means you have to use `--keystore` parameter when logging in. From 75e80ab11eeca82e35815b783b0c67a256fe1fa4 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Mon, 17 Oct 2016 10:23:29 -0400 Subject: [PATCH 156/194] fixed logout url and edited existing text --- topics/oidc/java/logout.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/topics/oidc/java/logout.adoc b/topics/oidc/java/logout.adoc index f896efe88a..baa2a20fd1 100755 --- a/topics/oidc/java/logout.adoc +++ b/topics/oidc/java/logout.adoc @@ -1,5 +1,6 @@ ==== Logout -There are multiple ways you can logout from a web application. -For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can redirect the browser to -`$$http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri$$`. This will log you out if you have a SSO session with your browser. \ No newline at end of file +You can log out of a web application in multiple ways. +For Java EE servlet containers, you can call HttpServletRequest.logout(). For other browser applications, you can redirect the browser to +`$$http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri$$`, which logs you out if you have an SSO session with your browser. + From ea3946644a6fd498963938ef38f7af985fac865c Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Mon, 31 Oct 2016 11:34:30 +0100 Subject: [PATCH 157/194] Add Technology Preview disclaimer to client-registration-cli documentation --- topics/client-registration/client-registration-cli.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/topics/client-registration/client-registration-cli.adoc b/topics/client-registration/client-registration-cli.adoc index 138acede8d..456021dfcd 100644 --- a/topics/client-registration/client-registration-cli.adoc +++ b/topics/client-registration/client-registration-cli.adoc @@ -1,6 +1,10 @@ [[_client_registration_cli]] == Client Registration CLI +{% if book.product %} +NOTE: Client Registration CLI is a Technology Preview feature and is not fully supported. +{% endif %} + `Client Registration CLI` is a command line interface tool that can be used by application developers to configure new clients to integrate with {book_project_name}. It is specifically designed to interact with {{book.project.name}} Client Registration REST endpoints. From b929edd2be7d80f92ee80fb399402a6c26cfd469 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 4 Nov 2016 21:43:30 +0100 Subject: [PATCH 158/194] KEYCLOAK-1881 - SAML key rotation at IdP side --- SUMMARY.adoc | 1 + .../idp_httpclient_subelement.adoc | 68 +++++++++++++++++++ .../general-config/idp_keys_subelement.adoc | 31 ++++++++- .../idp_singlesignonservice_subelement.adoc | 1 + 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 topics/saml/java/general-config/idp_httpclient_subelement.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index fba2af9105..ba5bd402c1 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -59,6 +59,7 @@ .... link:topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc[IDP SingleSignOnService sub element] .... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] + .... link:topics/saml/java/general-config/idp_httpclient_subelement.adoc[IDP HttpClient subelement] ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] diff --git a/topics/saml/java/general-config/idp_httpclient_subelement.adoc b/topics/saml/java/general-config/idp_httpclient_subelement.adoc new file mode 100644 index 0000000000..bc19d4501c --- /dev/null +++ b/topics/saml/java/general-config/idp_httpclient_subelement.adoc @@ -0,0 +1,68 @@ +[[_sp-idp-httpclient]] + +===== IDP HttpClient sub element + +The `HttpClient` optional sub element defines the properties of HTTP client used +for automatic obtaining of certificates containing public keys for IDP signature +verification via SAML descriptor of the IDP when +<>. + +[source,xml] +---- + +---- + +connectionPoolSize:: + Adapters will make separate HTTP invocations to the {{book.project.name}} server to turn an access code into an access token. + This config option defines how many connections to the {{book.project.name}} server should be pooled. + This is _OPTIONAL_. + The default value is `10`. + +disableTrustManager:: + If the {{book.project.name}} server requires HTTPS and this config option is set to `true` you do not have to specify a truststore. + This setting should only be used during development and *never* in production as it will disable verification of SSL certificates. + This is _OPTIONAL_. + The default value is `false`. + +allowAnyHostname:: + If the {{book.project.name}} server requires HTTPS and this config option is set to `true` + the {{book.project.name}} server's certificate is validated via the truststore, + but host name validation is not done. + This setting should only be used during development and *never* in production + as it will partly disable verification of SSL certificates. + This seting may be useful in test environments. This is _OPTIONAL_. + The default value is `false`. + +truststore:: + The value is the file path to a keystore file. + If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead. + Used for outgoing HTTPS communications to the {{book.project.name}} server. + Client making HTTPS requests need a way to verify the host of the server they are talking to. + This is what the trustore does. + The keystore contains one or more trusted host certificates or certificate authorities. + You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. + This is _REQUIRED_ unless `disableTrustManager` is `true`. + +truststorePassword:: + Password for the truststore keystore. + This is _REQUIRED_ if `truststore` is set and the truststore requires a password. + +clientKeystore:: + This is the file path to a keystore file. + This keystore contains client certificate for two-way SSL when the adapter makes HTTPS requests to the {{book.project.name}} server. + This is _OPTIONAL_. + +clientKeystorePassword:: + Password for the client keystore and for the client's key. + This is _REQUIRED_ if `clientKeystore` is set. + +proxyUrl:: + URL to HTTP proxy to use for HTTP connections. + This is _OPTIONAL_. diff --git a/topics/saml/java/general-config/idp_keys_subelement.adoc b/topics/saml/java/general-config/idp_keys_subelement.adoc index 195d1564ea..c92cc3fafd 100644 --- a/topics/saml/java/general-config/idp_keys_subelement.adoc +++ b/topics/saml/java/general-config/idp_keys_subelement.adoc @@ -1,9 +1,34 @@ +[[_sp-idp-keys]] -===== IDP Keys subelement +===== IDP Keys sub element The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP. -It is defined in the same way as the <>. -But again, you only have to define one certificate or public key reference. +It is defined in the same way as the <>. +But again, you only have to define one certificate or public key reference. Note that, if both IDP and SP are realized by +{{book.project.name}} server and adapter, respectively, there is no need to specify the keys for signature validation, see below. + +[[_sp-idp-keys-automatic]] +It is possible to configure SP to obtain public keys for IDP signature validation +from published certificates automatically, provided both SP and IDP are +implemented by {{book.project.name}}. +This is done by removing all declarations of signature validation keys in Keys +sub element. If the Keys sub element would then remain empty, it can be omitted +completely. The keys are then automatically obtained by SP from SAML descriptor, +location of which is derived from SAML endpoint URL specified in the +<>. +Settings of the HTTP client that is used for SAML descriptor retrieval usually +needs no additional configuration, however it can be configured in the +<>. + +It is also possible to specify multiple keys for signature verification. This is done by declaring multiple Key elements +within Keys sub element that have `signing` attribute set to `true`. +This is useful for example in situation when the IDP signing keys are rotated: There is +usually a transition period when new SAML protocol messages and assertions are signed +with the new key but those signed by previous key should still be accepted. + +It is not possible to configure {{book.project.name}} to both obtain the keys +for signature verification automatically and define additional static signature +verification keys. [source,xml] ---- diff --git a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc index 3566510450..6166fc63f4 100644 --- a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc +++ b/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc @@ -1,3 +1,4 @@ +[[_sp-idp-singlesignonservice]] ===== IDP SingleSignOnService sub element From 6aa9bee22ce5ef4a87d2190ce54f026812aea46c Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Wed, 9 Nov 2016 11:02:53 +0100 Subject: [PATCH 159/194] KEYCLOAK-3870 Refer to keycloak-saml.xml schema in the example --- topics/saml/java/general-config.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/topics/saml/java/general-config.adoc b/topics/saml/java/general-config.adoc index 8f7a995d76..8eb1c7bdf1 100644 --- a/topics/saml/java/general-config.adoc +++ b/topics/saml/java/general-config.adoc @@ -7,7 +7,9 @@ This is what one might look like: [source,xml] ---- - + Date: Wed, 9 Nov 2016 13:41:22 -0500 Subject: [PATCH 160/194] Update master-docinfo.xml Updated release name references to 7.1-Beta --- master-docinfo.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/master-docinfo.xml b/master-docinfo.xml index 831238c4ba..1ceef7c3b2 100755 --- a/master-docinfo.xml +++ b/master-docinfo.xml @@ -1,10 +1,10 @@ Red Hat Single Sign-On -7.0 +7.1-Beta Securing Applications and Services Guide Securing Applications and Services Guide 7.0 - This guide consist of information for securing applications and services using Red Hat Single Sign-On 7.0 + This guide consists of information for securing applications and services using Red Hat Single Sign-On 7.1-Beta Red Hat Customer Content Services From a723eb0beeaf5210e943648a2802e8c7e7f63db5 Mon Sep 17 00:00:00 2001 From: ccopelloRH Date: Wed, 9 Nov 2016 13:49:45 -0500 Subject: [PATCH 161/194] Update master-docinfo.xml Missed the tag --- master-docinfo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/master-docinfo.xml b/master-docinfo.xml index 1ceef7c3b2..b0cc6f5c42 100755 --- a/master-docinfo.xml +++ b/master-docinfo.xml @@ -2,7 +2,7 @@ 7.1-Beta Securing Applications and Services Guide Securing Applications and Services Guide -7.0 +7.1-Beta This guide consists of information for securing applications and services using Red Hat Single Sign-On 7.1-Beta From 7771e91b41ca828706675ac42b6782f33ef9debf Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Tue, 22 Nov 2016 12:02:26 +0100 Subject: [PATCH 162/194] Fix some typos in client-registration-cli documentation --- .../client-registration-cli.adoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/topics/client-registration/client-registration-cli.adoc b/topics/client-registration/client-registration-cli.adoc index 456021dfcd..51d0d57401 100644 --- a/topics/client-registration/client-registration-cli.adoc +++ b/topics/client-registration/client-registration-cli.adoc @@ -95,7 +95,7 @@ Or on Windows: ---- c:\> kcreg config credentials --server http://localhost:8080/auth --realm demo --user user --client reg-cli c:\> kcreg create -s clientId=my_client -s "redirectUris=[\"http://localhost:8980/myapp/*\"]" -c:\> kcreg get +c:\> kcreg get my_client ---- @@ -265,13 +265,13 @@ Once a new client is created you can retrieve it again by using `kcreg get`. On Linux: [source,bash] ---- -$ kcreg.sh get +$ kcreg.sh get myclient ---- On Windows: [source,bash] ---- -C:\> kcreg get +C:\> kcreg get myclient ---- @@ -280,13 +280,13 @@ You can also get an adapter configuration which you can drop into your web appli On Linux: [source,bash] ---- -$ kcreg.sh get -e install +$ kcreg.sh get myclient -e install ---- On Windows: [source,bash] ---- -C:\> kcreg get -e install +C:\> kcreg get myclient -e install ---- See `kcreg get --help` for more information about `kcreg get`. @@ -299,7 +299,7 @@ One is to submit a complete new state to the server after getting current config On Linux: [source,bash] ---- -$ kcreg.sh get > myclient.json +$ kcreg.sh get myclient > myclient.json $ vi myclient.json $ kcreg.sh update myclient -f myclient.json ---- @@ -307,7 +307,7 @@ $ kcreg.sh update myclient -f myclient.json On Windows: [source,bash] ---- -C:\> kcreg get > myclient.json +C:\> kcreg get myclient > myclient.json C:\> notepad myclient.json C:\> kcreg update myclient -f myclient.json ---- From 3d22972466417a66af28303e49f8b1beae78a752 Mon Sep 17 00:00:00 2001 From: Paolo Antinori Date: Tue, 22 Nov 2016 17:43:27 +0100 Subject: [PATCH 163/194] KEYCLOAK-3678 - Docs - Added Camel RestDSL --- topics/oidc/java/fuse/camel.adoc | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index 1cd7a21564..324c912bb7 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -93,3 +93,63 @@ org.osgi.service.blueprint.container, org.osgi.service.event, ---- +===== Camel RestDSL + +Camel RestDSL is a Camel feature to define your REST endpoints in a fluent way. +But under the hood, the capability to provide all this magic, is still demanded to specific implementation classes and +you have to instruct them on how to integrate with Keycloak. + +The way to configure the integration mechanism depends on the Camel component that you configure your RestDSL defined routes to work with. + +This is an example that show how to do it while using Jetty engine, with reference to some beans defined in the example above. + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + Hello rest service + + Just an helllo + + + + + + + + + + + (__This second sentence is returned from a Camel RestDSL endpoint__) + + + + + +--- From fba2dd9ac2acf9fac132d70abfb6000e16da454a Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 24 Nov 2016 16:48:35 +0100 Subject: [PATCH 164/194] KEYCLOAK-3956 Update fuse documentation. Added docs for Hawtio --- SUMMARY.adoc | 1 + book.json | 3 +- topics/oidc/java/fuse-adapter.adoc | 7 +- topics/oidc/java/fuse/camel.adoc | 2 +- topics/oidc/java/fuse/classic-war.adoc | 22 ++- topics/oidc/java/fuse/fuse-admin.adoc | 25 ++-- topics/oidc/java/fuse/hawtio.adoc | 129 ++++++++++++++++++ topics/oidc/java/fuse/install-feature.adoc | 18 +-- topics/oidc/java/fuse/servlet-whiteboard.adoc | 1 + 9 files changed, 180 insertions(+), 28 deletions(-) create mode 100644 topics/oidc/java/fuse/hawtio.adoc diff --git a/SUMMARY.adoc b/SUMMARY.adoc index ba5bd402c1..f228c8d62e 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -18,6 +18,7 @@ .... link:topics/oidc/java/fuse/cxf-separate.adoc[Apache CXF on Separate Jetty] .... link:topics/oidc/java/fuse/cxf-builtin.adoc[Apache CXF on default Jetty] .... link:topics/oidc/java/fuse/fuse-admin.adoc[Fuse Admin Services] + .... link:topics/oidc/java/fuse/hawtio.adoc[Hawtio Admin Console] {% if book.community %} ... link:topics/oidc/java/tomcat-adapter.adoc[Tomcat 6, 7 and 8 Adapters] ... link:topics/oidc/java/jetty9-adapter.adoc[Jetty 9.x Adapters] diff --git a/book.json b/book.json index 68d0d08daa..9250dfc76b 100755 --- a/book.json +++ b/book.json @@ -29,6 +29,7 @@ "installguide": { "name": "Server Installation and Configuration Guide", "link": "https://keycloak.gitbooks.io/server-installation-and-configuration/content/" - } + }, + "fuseVersion": "JBoss Fuse 6.3.0 Rollup 1" } } diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 9f3258a023..8f2f6bbc7c 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -7,11 +7,15 @@ NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported {% endif %} Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] . + {% if book.community %} -It leverages <> as both JBoss Fuse 6.2 are bundled with http://eclipse.org/jetty/[Jetty 8.1 server] +It leverages <> as {{book.fuseVersion}} is bundled with http://eclipse.org/jetty/[Jetty 9.2 server] under the covers and Jetty is used for running various kinds of web applications. {% endif %} +WARNING: The only supported Fuse version is {{book.fuseVersion}} . If you use older Fuse versions, it's possible that some functionalities won't work correctly. +Especially the http://hawt.io[Hawtio] integration won't work with older Fuse versions. + What is supported for Fuse is: * Security for classic WAR applications deployed on Fuse with https://ops4j1.jira.com/wiki/display/ops4j/Pax+Web+Extender+-+War[Pax Web War Extender]. @@ -20,6 +24,7 @@ What is supported for Fuse is: * Security for http://cxf.apache.org/[Apache CXF] endpoints running on their own separate http://cxf.apache.org/docs/jetty-configuration.html[Jetty engine]. * Security for http://cxf.apache.org/[Apache CXF] endpoints running on default engine provided by CXF servlet. * Security for SSH and JMX admin access. +* Security for http://hawt.io[Hawtio admin console] ===== How to secure your web applications inside Fuse diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index 324c912bb7..a22ffcccfc 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -97,7 +97,7 @@ org.osgi.service.event, Camel RestDSL is a Camel feature to define your REST endpoints in a fluent way. But under the hood, the capability to provide all this magic, is still demanded to specific implementation classes and -you have to instruct them on how to integrate with Keycloak. +you have to instruct them on how to integrate with {{book.project.name}}. The way to configure the integration mechanism depends on the Camel component that you configure your RestDSL defined routes to work with. diff --git a/topics/oidc/java/fuse/classic-war.adoc b/topics/oidc/java/fuse/classic-war.adoc index 56703c1c5c..9f0175cfe3 100644 --- a/topics/oidc/java/fuse/classic-war.adoc +++ b/topics/oidc/java/fuse/classic-war.adoc @@ -63,7 +63,7 @@ The example configuration can look like this: ---- * Add `/WEB-INF/keycloak.json` with your {{book.project.name}} configuration. The format of this config file is described -in the <> section. +in the <> section. It is also possible to have this file available externally as described below. * Make sure your WAR imports `org.keycloak.adapters.jetty` and maybe some more packages in `META-INF/MANIFEST.MF` file in header `Import-Package`. It's recommended to use `maven-bundle-plugin` in your project to properly generate OSGI headers in manifest. @@ -80,3 +80,23 @@ org.keycloak.*;version="{{book.project.versionMvn}}", *;resolution:=optional ---- +====== External adapter configuration + +This is for the case when you don't want adapter configuration file `keycloak.json` to be bundled inside your WAR application. Instead it will be available +externally and loaded based on naming conventions. + +To enable the functionality you need to add this section to your `web.xml`: + +[source,xml] +---- + + keycloak.config.resolver + org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver + +---- + +That component will use `keycloak.config` or `karaf.etc` java properties to look for a base folder to look for the configuration. +Inside one of those folders it will look for a file called `-keycloak.json`. + +So for example if your web application has context `my-portal`, then your adapter configuration will be loaded from the file `$FUSE_HOME/etc/my-portal-keycloak.json` . + diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index e90849f598..f31c4d8675 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -12,7 +12,7 @@ by using JAAS login module, which allows to remotely connect to {{book.project.n Example steps for enable SSH authentication: * In {{book.project.name}} you need to create client (assume it's called `ssh-jmx-admin-client`), which will be used for SSH authentication. -This client needs to have switch `Direct grant enabled` to true. +This client needs to have switch `Direct Access Grants Enabled` to `On`. * You need to update/specify this property in file `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`: @@ -22,6 +22,7 @@ sshRealm=keycloak ---- * Add file `$FUSE_HOME/etc/keycloak-direct-access.json` with the content similar to this (change based on your environment and {{book.project.name}} client settings): + [source,json] ---- { @@ -37,7 +38,7 @@ sshRealm=keycloak This file contains configuration of the client application, which is used by JAAS DirectAccessGrantsLoginModule from `keycloak` JAAS realm for SSH authentication. * Start Fuse and install `keycloak` JAAS realm into Fuse. This could be done easily by installing `keycloak-jaas` feature, which has JAAS realm predefined -(you are able to override it by using your own `keycloak` JAAS realm with higher ranking). Use those commands in Fuse terminal: +(you are able to override it by using your own `keycloak` JAAS realm with higher ranking. See JBoss Fuse documentation for more details). Use those commands in Fuse terminal: [source, subs="attributes"] ---- @@ -51,13 +52,20 @@ features:install keycloak-jaas ssh -o PubkeyAuthentication=no -p 8101 admin@localhost ``` -And login with password `password`. Note that your user needs to have realm role `admin` . The required roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` +And login with password `password`. + +NOTE: On some newer operating systems, you may also need to use this option of SSH command `-o HostKeyAlgorithms=+ssh-dss` because newer SSH clients +don't allow to use `ssh-dss` algorithm by default, but it's currently used by default in {{book.fuseVersion}} . + + +Note that your user needs to have realm role `admin` if he wants to do everything or some other roles to be able to do just subset of operations +(eg. role `viewer` to be able to run just read-only Karaf commands) . The available roles are configured in `$FUSE_HOME/etc/org.apache.karaf.shell.cfg` or `$FUSE_HOME/etc/system.properties` . ====== JMX authentication This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may -be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default. +be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default. See <> section for more details. * In file `$FUSE_HOME/etc/org.apache.karaf.management.cfg` you can change this property: @@ -76,12 +84,3 @@ service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root ---- and credentials: admin/password (based on the user with admin privileges according to your environment) - -Note again that users without `admin` role are not able to login as they are not authorized. However users with access to Hawt.io admin console -may be still able to access MBeans remotely via HTTP (Hawtio). So make sure to protect Hawt.io web console with same roles like JMX through RMI to -really protect JMX mbeans. - - -====== Secure Fuse admin console - -Fuse admin console is Hawt.io. See http://hawt.io/configuration/index.html[Hawt.io documentation] for more info about how to secure it with {{book.project.name}}. \ No newline at end of file diff --git a/topics/oidc/java/fuse/hawtio.adoc b/topics/oidc/java/fuse/hawtio.adoc new file mode 100644 index 0000000000..32694a0130 --- /dev/null +++ b/topics/oidc/java/fuse/hawtio.adoc @@ -0,0 +1,129 @@ + +[[_hawtio]] +===== Secure Hawtio Admin Console + +The steps to secure Hawtio Admin Console with {{book.project.name}} are: + +* Add these properties to the file `$FUSE_HOME/etc/system.properties` : + +[source] +---- +hawtio.keycloakEnabled=true +hawtio.realm=keycloak +hawtio.keycloakClientConfig=${karaf.base}/etc/keycloak-hawtio-client.json +hawtio.rolePrincipalClasses=org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal +---- + +* Create client in {{book.project.name}} admin console in your realm. Assuming you have {{book.project.name}} realm `demo` and you created client `hawtio-client`, which is marked +with `public` Access Type and has redirect URI pointing to Hawtio - http://localhost:8181/hawtio/* . + +* Create file `keycloak-hawtio-client.json` in the directory `$FUSE_HOME/etc` and use the content like this (Change properties `realm`, `resource` and `auth-server-url` according to +your {{book.project.name}} environment. The property `resource` should point to the client created in previous step. This file is used by the client (Hawtio Javascript application) side. + +[source,json] +---- +{ + "realm" : "demo", + "resource" : "hawtio-client", + "auth-server-url" : "http://localhost:8080/auth", + "ssl-required" : "external", + "public-client" : true +} +---- + +* Create file `keycloak-hawtio.json` in the directory `$FUSE_HOME/etc` and use the content like this (Change properties `realm` and `auth-server-url` according to +your {{book.project.name}} environment. This file is used by the adapters on server (JAAS Login module) side. + + +[source,json] +---- +{ + "realm" : "demo", + "resource" : "jaas", + "bearer-only" : true, + "auth-server-url" : "http://localhost:8080/auth", + "ssl-required" : "external", + "use-resource-role-mappings": false, + "principal-attribute": "preferred_username" +} +---- + +* Start {{book.fuseVersion}} and install feature `keycloak` if you didn't already. The commands in Karaf terminal are like: + + +[source, subs="attributes"] +---- +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features +features:install keycloak +---- + +* Go to http://localhost:8181/hawtio and login as some user from your {{book.project.name}} realm. See file `$FUSE_HOME/etc/system.properties` and property `hawtio.roles` . +Just those with some of the role are able to successfully authenticate to Hawtio and do something here. + + +====== Secure Hawtio on EAP + +This subsection contains needed steps for the case, when you want to run Hawtio on the Wildfly 10 server. + +* Setup {{book.project.name}} similarly like mentioned above in the section for securing Hawtion on JBoss Fuse. So assuming you have {{book.project.name}} realm `demo` +and client `hawtio-client`. Assumption is that your {{book.project.name}} is running on `localhost:8080` when the Wildfly with deployed hawtio will be running on `localhost:8181`. + +* Copy `hawtio.war` to the `$WILDFLY_HOME/standalone/configuration` directory. See Hawtio/Fuse documentation for more details about the Hawtio deployment. + +* Copy files `keycloak-hawtio.json` and `keycloak-hawtio-client.json` with the above content to the `$WILDFLY_HOME/standalone/configuration` directory. + +* Install {{book.project.name}} adapter subsystem to your Wildfly as described in the <> + +* In `$WILDFLY_HOME/standalone/configuration/standalone.xml` configure system properties like this: + +[source,xml] +---- + +... + + + + + + + + + + + +---- + +Also add hawtio realm to this file to the `security-domains` section: + +[source,xml] +---- + + + + + + + +---- + +Finally add the `secure-deployment` section `hawtio` to the adapter subsystem. It should ensure that Hawtio WAR is able to find the JAAS login module classes. + + +[source,xml] +---- + + + +---- + +* Restart Wildfly server with Hawtio + +[source,xml] +---- +cd $WILDFLY_HOME/bin +./standalone.sh -Djboss.socket.binding.port-offset=101 +---- + +* Access Hawtio on http://localhost:8181/hawtio . It will be secured by the {{book.project.name}} . + + diff --git a/topics/oidc/java/fuse/install-feature.adoc b/topics/oidc/java/fuse/install-feature.adoc index b2353392be..6cb17efab3 100644 --- a/topics/oidc/java/fuse/install-feature.adoc +++ b/topics/oidc/java/fuse/install-feature.adoc @@ -29,7 +29,7 @@ org.ops4j.pax.url.mvn.repositories= \ ---- {% endif %} -You need to start JBoss Fuse and then in the Karaf terminal you type this: +You need to start {{book.fuseVersion}} and then in the Karaf terminal you type this: [source] ---- @@ -37,20 +37,16 @@ features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMv features:install keycloak ---- -Then in JBoss Fuse 6.2 you may need to install Jetty 8 feature: - -[source] ----- -features:install keycloak-jetty8-adapter ----- - -Or in JBoss Fuse 6.3 you may need to install Jetty 9 feature: +Then you may need to install Jetty 9 feature: [source] ---- features:install keycloak-jetty9-adapter ---- +NOTE: If you are on JBoss Fuse 6.2 or older, you should use `keycloak-jetty8-adapter` . However it's highly recommended to +rather upgrade to {{book.fuseVersion}} instead. + Then you can check that requested features were installed: [source] @@ -61,11 +57,11 @@ features:list | grep keycloak ====== Install from ZIP bundle This is useful if you are offline and/or don't want to use maven for download jar files and other artifacts. Once you download ZIP bundle of {{book.project.name}} Fuse adapter, -you will need to unzip it into the root directory of JBoss Fuse. This should install the dependencies under the `system` directory. For example see this for Fuse 6.2.1 : +you will need to unzip it into the root directory of JBoss Fuse. This should install the dependencies under the `system` directory. Use this for {{book.fuseVersion}} : [source] ---- -cd /path-to-fuse/jboss-fuse-6.2.1.redhat-084 +cd /path-to-fuse/jboss-fuse-6.3.0.redhat-198 unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-dist-{{book.project.versionMvn}}.zip ---- diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/topics/oidc/java/fuse/servlet-whiteboard.adoc index 386cca93ad..a1b8fc43d7 100644 --- a/topics/oidc/java/fuse/servlet-whiteboard.adoc +++ b/topics/oidc/java/fuse/servlet-whiteboard.adoc @@ -10,6 +10,7 @@ The needed steps to secure your servlet with {{book.project.name}} are: * {{book.project.name}} provides PaxWebIntegrationService, which allows to inject jetty-web.xml and configure security constraints for your application. You need to declare such service in `OSGI-INF/blueprint/blueprint.xml` inside your application. Note that your servlet needs to depend on it. The example configuration can look like this: + [source,xml] ---- From fb8c55022a922b7140985cf93dd40bd177b761e8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 28 Nov 2016 17:24:56 -0200 Subject: [PATCH 165/194] KEYCLOAK-3991: Node.js module should be available through a single file download --- topics/oidc/nodejs-adapter.adoc | 52 +++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index e09e124b12..2bac68cf02 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -3,28 +3,30 @@ {{book.project.name}} provides a Node.js adapter built on top of https://github.com/senchalabs/connect[Connect] to protect server side JavaScript apps — the goal was to be flexible enough to integrate with frameworks like https://expressjs.com/[Express.js]. +{% if book.community %} The library can be downloaded directly from https://www.npmjs.com/package/keycloak-connect[ {{book.project.name}} organization] and the source is available at https://github.com/keycloak/keycloak-nodejs-connect[GitHub]. +{% endif %} -To use the Node.js adapter you must first create a client for your application in the {{book.project.name}} Administration Console. The adapter supports public, confidential and bearer-only access type. Which one to choose depends on the use-case scenario. +To use the Node.js adapter, first you must create a client for your application in the {{book.project.name}} Administration Console. The adapter supports public, confidential and bearer-only access type. Which one to choose depends on the use-case scenario. -Once the client is created click on the `Installation` tab select `{{book.project.name}} OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be at the root folder. Exactly, like in https://github.com/keycloak/keycloak-nodejs-connect/tree/master/example[this example]. +Once the client is created click on the `Installation` tab select `{{book.project.name}} OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be at the root folder of your project. -keycloak.json:: +[source,json] +---- +{ + "realm": "example-realm", + "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "http://localhost:8080/auth", + "ssl-required": "external", + "resource": "example-app", + "credentials": { + "secret": "mysecret" + } +} +---- -Alongside the `example.js` lives `keycloak.json` obtained from our {{book.project.name}} -admin console when we provisioned this app. - - - { - "realm": "example-realm", - "auth-server-url": "http://localhost:8080/auth", - "ssl-required": "external", - "resource": "example-app", - "credentials": { - "secret": "mysecret" - } - } +Please notice that `realm-public-key` attribute is mandatory. Otherwise, your Node.js app won't start. ==== Installation @@ -32,9 +34,23 @@ Assuming you've already installed https://nodejs.org[Node.js], create a folder f mkdir myapp && cd myapp -Use `npm init` command to create a `package.json` for your application. And now install the {{book.project.name}} connect adapter in the `myapp` folder, saving it in the dependencies list: +Use `npm init` command to create a `package.json` for your application. Now add the {{book.project.name}} connect adapter in the dependencies list: - npm install --save keycloak-connect +{% if book.community %} + + dependencies": { + "keycloak-connect": "{{book.project.versionMvn}}" + } + +{% endif %} + +{% if book.product %} + + dependencies": { + "keycloak-connect": "file://{{book.project.name}}.tgz" + } + +{% endif %} ==== Usage Instantiate a Keycloak class:: From c9b9e9f678ca9544bc52aa2c94196cf362ff140e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Nov 2016 11:30:24 -0200 Subject: [PATCH 166/194] Section about realm-public-key --- topics/oidc/nodejs-adapter.adoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index 2bac68cf02..2d84e848d4 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -12,6 +12,12 @@ To use the Node.js adapter, first you must create a client for your application Once the client is created click on the `Installation` tab select `{{book.project.name}} OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be at the root folder of your project. +==== Known issue + +Node.js adapter requires `realm-public-key` attribute and does not retrieve it automatically from {{book.project.name}}. Otherwise, your Node.js app won't start. + +In order to prevent any issues, go to your `Realm Settings` and `Keys`, copy the `Public Key` to your keycloak.json file adding `realm-public-key` attribute like the snippet below: + [source,json] ---- { @@ -26,8 +32,6 @@ Once the client is created click on the `Installation` tab select `{{book.projec } ---- -Please notice that `realm-public-key` attribute is mandatory. Otherwise, your Node.js app won't start. - ==== Installation Assuming you've already installed https://nodejs.org[Node.js], create a folder for your application: From a42274f89326cb33b24dbabb92b1cd9315c15515 Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Tue, 29 Nov 2016 17:15:51 -0500 Subject: [PATCH 167/194] fixing RHSSO622 623 624 625 pulled in 613 --- topics/oidc/java/java-adapter-config.adoc | 5 +++-- topics/overview/supported-platforms.adoc | 4 ++-- topics/saml/java/jboss-adapter.adoc | 4 +--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index f9ce2073b2..a71b1b68d9 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -38,7 +38,7 @@ This is what one might look like: ---- You can use `${...}` enclosure for system property replacement. For example `${jboss.server.config.dir}` would be replaced by `/path/to/{{book.project.name}}`. -Replacement of environment variables is also supported via the `env` prefix, e.g. `${env.MY_ENVIRONMENT_VARIABLE}`. +Replacement of environment variables is also supported via the `env` prefix, e.g. `${env.MY_ENVIRONMENT_VARIABLE}`. The initial config file can be obtained from the the admin console. This can be done by opening the admin console, select `Clients` from the menu and clicking on the corresponding client. Once the page for the client is opened click on the `Installation` tab and select `Keycloak OIDC JSON`. @@ -118,6 +118,7 @@ expose-token:: The default value is _false_. credentials:: + Not required for public clients or where the client is "bearer-only." Specify the credentials of the application. This is an object notation where the key is the credential type and the value is the value of the credential type. Currently `password` and `jwt` is supported. This is _REQUIRED_. @@ -148,7 +149,7 @@ truststore:: Client making HTTPS requests need a way to verify the host of the server they are talking to. This is what the trustore does. The keystore contains one or more trusted host certificates or certificate authorities. - You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. + You can create this truststore by extracting the public certificate of the {{book.project.name}} server's SSL keystore. This is _REQUIRED_ unless `ssl-required` is `none` or `disable-trust-manager` is `true`. truststore-password:: diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index a0cab39a96..88cc916482 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -27,7 +27,7 @@ ===== Node.js (server-side) * <> -===== Apache Cordova +===== JavaScript * <> {% if book.community %} @@ -76,4 +76,4 @@ ===== Apache HTTP Server -* https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] +* <> diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/jboss-adapter.adoc index 9091c41bf6..7ca4f173aa 100644 --- a/topics/saml/java/jboss-adapter.adoc +++ b/topics/saml/java/jboss-adapter.adoc @@ -15,6 +15,4 @@ To be able to secure WAR apps deployed on JBoss EAP, you must install and config {% endif %} You then provide a keycloak config, `/WEB-INF/keycloak-saml.xml` file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml. -Both methods are described in this section. - - +Both methods are described in this section. From 488f883b91246cbcd160aed01f06551a3c4ed299 Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Tue, 29 Nov 2016 17:20:33 -0500 Subject: [PATCH 168/194] missed the oidc jboss fix --- topics/oidc/java/jboss-adapter.adoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 0686c1c51b..c28185fac0 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -1,5 +1,5 @@ - [[_jboss_adapter]] + {% if book.community %} ==== JBoss EAP/Wildfly Adapter {% endif %} @@ -112,7 +112,7 @@ is not running: [source] ---- $ ./bin/jboss-cli.sh --file=adapter-install-offline.cli ----- +---- If you are planning to add it manually you need to add the extension and subsystem definition to the server configuration: @@ -146,7 +146,7 @@ If you need to be able to propagate the security context from the web tier to th ... ---- -For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: +For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: [source] ---- @@ -183,7 +183,7 @@ public class CustomerService { ===== Required Per WAR Configuration -This section describes how to secure a WAR directly by adding config and editing files within your WAR package. +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. @@ -241,7 +241,7 @@ Here's an example: user ----- +---- ===== Securing WARs via Adapter Subsystem @@ -272,10 +272,10 @@ This metadata is instead defined within server configuration (i.e. `standalone.x The `secure-deployment` `name` attribute identifies the WAR you want to secure. Its value is the `module-name` defined in `web.xml` with `.war` appended. The rest of the configuration corresponds pretty much one to one with the `keycloak.json` configuration options defined in <>. -The exception is the `credential` element. +The exception is the `credential` element. To make it easier for you, you can go to the {{book.project.name}} Administration Console and go to the Client/Installation tab of the application this WAR is aligned with. -It provides an example XML file you can cut and paste. +It provides an example XML file you can cut and paste. If you have multiple deployments secured by the same realm you can share the realm configuration in a separate element. For example: @@ -302,4 +302,4 @@ If you have multiple deployments secured by the same realm you can share the rea true ----- +---- From c267eba3fda63958ec82c3ba95a524d8f11a1fca Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Tue, 29 Nov 2016 20:39:57 -0500 Subject: [PATCH 169/194] Fixing mod_auth_mellon link --- topics/overview/supported-platforms.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index 88cc916482..f12642e675 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -76,4 +76,4 @@ ===== Apache HTTP Server -* <> +* <> From f899841a7519510e25a034cdc0670665a68ca39c Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Wed, 30 Nov 2016 07:56:49 -0500 Subject: [PATCH 170/194] added extra - to last XML example --- topics/oidc/java/fuse/camel.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/java/fuse/camel.adoc b/topics/oidc/java/fuse/camel.adoc index a22ffcccfc..0ee38425ba 100644 --- a/topics/oidc/java/fuse/camel.adoc +++ b/topics/oidc/java/fuse/camel.adoc @@ -1,5 +1,5 @@ - [[_fuse_adapter_camel]] + ===== Apache Camel Application * You can secure your Apache camel endpoint using http://camel.apache.org/jetty.html[camel-jetty] component by adding securityHandler with `KeycloakJettyAuthenticator` and @@ -152,4 +152,4 @@ This is an example that show how to do it while using Jetty engine, with referen ---- +---- From 4b3ed3733c90d0c475e239941a83367fde0d846f Mon Sep 17 00:00:00 2001 From: Chuck Copello Date: Wed, 30 Nov 2016 13:43:18 -0500 Subject: [PATCH 171/194] updated text --- topics/oidc/java/java-adapter-config.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index a71b1b68d9..91791a3a52 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -118,10 +118,8 @@ expose-token:: The default value is _false_. credentials:: - Not required for public clients or where the client is "bearer-only." Specify the credentials of the application. This is an object notation where the key is the credential type and the value is the value of the credential type. - Currently `password` and `jwt` is supported. - This is _REQUIRED_. + Currently password and jwt is supported. This is _REQUIRED_ only for clients with 'Confidential' access type. connection-pool-size:: Adapters will make separate HTTP invocations to the {{book.project.name}} server to turn an access code into an access token. From 2eba8201b405083d5bc97938f7b8a61d7e9bf909 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 1 Dec 2016 17:23:05 +0100 Subject: [PATCH 172/194] KEYCLOAK-3824 Note about public-key-cache-ttl adapter option --- topics/oidc/java/java-adapter-config.adoc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index 91791a3a52..df28bb0ecb 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -33,7 +33,8 @@ This is what one might look like: "client-keystore-password" : "geheim", "client-key-password" : "geheim", "token-minimum-time-to-live" : 10, - "min-time-between-jwks-requests" : 10 + "min-time-between-jwks-requests" : 10, + "public-key-cache-ttl": 86400 } ---- @@ -207,3 +208,10 @@ min-time-between-jwks-requests:: Adapter will always try to download new public key when it recognize token with unknown `kid` . However it won't try it more than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of tokens with bad `kid` forcing adapter to send lots of requests to {{book.project.name}}. + +public-key-cache-ttl:: + Amount of time, in seconds, specifying maximum interval between two requests to {{book.project.name}} to retrieve new public keys. + It is 86400 seconds (1 day) by default. + Adapter will always try to download new public key when it recognize token with unknown `kid` . If it recognize token with known `kid`, it will + just use the public key downloaded previously. However at least once per this configured interval (1 day by default) will be new + public key always downloaded even if the `kid` of token is already known. \ No newline at end of file From 4d87776b9e610d7a3a8855da04fe1b18fd4848da Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Thu, 1 Dec 2016 15:01:51 -0500 Subject: [PATCH 173/194] fixed hard-coded link issue, cleaned up wording, and added build script --- book-product.json | 4 +- buildGuide.sh | 69 +++++++++++++++++++++++++ topics/oidc/java/params_forwarding.adoc | 12 ++--- 3 files changed, 77 insertions(+), 8 deletions(-) create mode 100755 buildGuide.sh diff --git a/book-product.json b/book-product.json index 3d404759ab..efd38f445b 100755 --- a/book-product.json +++ b/book-product.json @@ -12,7 +12,7 @@ "title": "Securing Applications and Services Guide", "project": { "name": "Red Hat Single Sign-On", - "version": "7.0.0", + "version": "7.1.0", "versionMvn": "1.9.8.Final-redhat-1" }, "community": false, @@ -20,7 +20,7 @@ "images": "rhsso-images", "adminguide": { "name": "Server Administration Guide", - "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.0/server-administration-guide/" + "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.1-Beta/server-administration-guide/" } } } diff --git a/buildGuide.sh b/buildGuide.sh new file mode 100755 index 0000000000..cb916ade78 --- /dev/null +++ b/buildGuide.sh @@ -0,0 +1,69 @@ +# Build the guide + +# Find the directory name and full path +CURRENT_GUIDE=${PWD##*/} +CURRENT_DIRECTORY=$(pwd) + +usage(){ + cat <&2 + usage + exit 1;; + esac +done + +if [ ! -d target ]; then + echo "You must run 'python gitlab-conversion.py' to convert the content before you run this script." + exit +fi + +# Remove the html and build directories and then recreate the html/images/ directory +if [ -d target/html ]; then +- rm -r target/html/ +fi +if [ -d target/html ]; then + rm -r target/html/ +fi + +mkdir -p html +cp -r target/images/ target/html/ + +echo "" +echo "********************************************" +echo " Building $CURRENT_GUIDE " +echo "********************************************" +echo "" +echo "Building an asciidoctor version of the guide" +asciidoctor -t -dbook -a toc -o target/html/$CURRENT_GUIDE.html target/master.adoc + +echo "" +echo "Building a ccutil version of the guide" +ccutil compile --lang en_US --format html-single --main-file target/master.adoc + +cd .. + +echo "View the asciidoctor build here: " file://$CURRENT_DIRECTORY/target/html/$CURRENT_GUIDE.html + +if [ -d $CURRENT_DIRECTORY/build/tmp/en-US/html-single/ ]; then + echo "View the ccutil build here: " file://$CURRENT_DIRECTORY/build/tmp/en-US/html-single/index.html + exit 0 +else + echo -e "${RED}Build using ccutil failed!" + echo -e "${BLACK}See the log above for details." + exit 1 +fi diff --git a/topics/oidc/java/params_forwarding.adoc b/topics/oidc/java/params_forwarding.adoc index 91c8300717..89d7588375 100644 --- a/topics/oidc/java/params_forwarding.adoc +++ b/topics/oidc/java/params_forwarding.adoc @@ -2,11 +2,11 @@ ==== Parameters Forwarding The {{book.project.name}} initial authorization endpoint request has support for various parameters. Most of the parameters are described in -http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification] . Some parameters are added automatically by adapter based -on the adapter configuration. However there are also few parameters, which can be added on per-invocation basis. When you open the secured application URI, +http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification]. Some parameters are added automatically by the adapter based +on the adapter configuration. However, there are also a few parameters that can be added on a per-invocation basis. When you open the secured application URI, the particular parameter will be forwarded to the {{book.project.name}} authorization endpoint. -For example, if you request offline token, then you can open the secured application URI with the `scope` parameter like: +For example, if you request an offline token, then you can open the secured application URI with the `scope` parameter like: [source] ---- @@ -15,7 +15,7 @@ http://myappserver/mysecuredapp?scope=offline_access and the parameter `scope=offline_access` will be automatically forwarded to the {{book.project.name}} authorization endpoint. -The supported parameters are actually: +The supported parameters are: * scope @@ -28,5 +28,5 @@ The supported parameters are actually: * kc_idp_hint Most of the parameters are described in the http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification]. -The only exception is parameter `kc_idp_hint`, which is specific to {{book.project.name}} and contains the name of Identity provider to automatically use. -More info in {{book.adminguide.link}}[{{book.adminguide.name}}] in `Identity Brokering` section. +The only exception is parameter `kc_idp_hint`, which is specific to {{book.project.name}} and contains the name of the identity provider to automatically use. +For more information see the `Identity Brokering` section in {{book.adminguide.link}}[{{book.adminguide.name}}]. From 127ab7a76af5ab22f64aa81afb78c1d17686be17 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 2 Dec 2016 12:48:02 -0200 Subject: [PATCH 174/194] Some minor fixes for Node.js guide --- topics/oidc/nodejs-adapter.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index 2d84e848d4..61950d7987 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -42,17 +42,23 @@ Use `npm init` command to create a `package.json` for your application. Now add {% if book.community %} +[source,json,subs="attributes"] +---- dependencies": { "keycloak-connect": "{{book.project.versionMvn}}" } +---- {% endif %} {% if book.product %} +[source,json,subs="attributes"] +---- dependencies": { - "keycloak-connect": "file://{{book.project.name}}.tgz" + "keycloak-connect": "file:rh-sso-{{book.project.version}}-nodejs-adapter.tgz" } +---- {% endif %} From 6be3161a99af3ea7ede652d78aaf6e73c0e750d6 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Fri, 2 Dec 2016 14:54:19 -0500 Subject: [PATCH 175/194] RHSSO-632 fixed Fuse link --- topics/oidc/java/fuse-adapter.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/topics/oidc/java/fuse-adapter.adoc b/topics/oidc/java/fuse-adapter.adoc index 8f2f6bbc7c..a75a306ed7 100755 --- a/topics/oidc/java/fuse-adapter.adoc +++ b/topics/oidc/java/fuse-adapter.adoc @@ -3,18 +3,18 @@ ==== JBoss Fuse Adapter {% if book.product %} -NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported +NOTE: JBoss Fuse is a Technology Preview feature and is not fully supported. {% endif %} -Currently {{book.project.name}} supports securing your web applications running inside http://www.jboss.org/products/fuse/overview/[JBoss Fuse] . +Currently {{book.project.name}} supports securing your web applications running inside http://developers.redhat.com/products/fuse/overview/[JBoss Fuse]. {% if book.community %} It leverages <> as {{book.fuseVersion}} is bundled with http://eclipse.org/jetty/[Jetty 9.2 server] under the covers and Jetty is used for running various kinds of web applications. {% endif %} -WARNING: The only supported Fuse version is {{book.fuseVersion}} . If you use older Fuse versions, it's possible that some functionalities won't work correctly. -Especially the http://hawt.io[Hawtio] integration won't work with older Fuse versions. +WARNING: The only supported Fuse version is {{book.fuseVersion}}. If you use earlier versions of Fuse, it's possible that some functionalities won't work correctly. +Especially the http://hawt.io[Hawtio] integration won't work with earlier versions of Fuse. What is supported for Fuse is: From 43c9920519abe8977f936c06e033e3b0025e12f6 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Sun, 4 Dec 2016 15:44:53 -0500 Subject: [PATCH 176/194] RHSSO-655: fixed 2 errors --- topics/saml/java/general-config.adoc | 2 +- topics/saml/java/general-config/sp-keys/key_pems.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/saml/java/general-config.adoc b/topics/saml/java/general-config.adoc index 8eb1c7bdf1..f2bc70a31d 100644 --- a/topics/saml/java/general-config.adoc +++ b/topics/saml/java/general-config.adoc @@ -26,7 +26,7 @@ This is what one might look like: - + - 2341251234AB31234==231BB998311222423522334 From e711d1048bc7839322d6462911b47940f2f6baec Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 5 Dec 2016 10:31:38 -0200 Subject: [PATCH 177/194] Fuse adapter documentation fixes --- book-product.json | 5 +++-- topics/oidc/java/fuse/fuse-admin.adoc | 2 +- topics/oidc/java/fuse/hawtio.adoc | 2 +- topics/oidc/java/fuse/install-feature.adoc | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/book-product.json b/book-product.json index efd38f445b..f5a61e0ef8 100755 --- a/book-product.json +++ b/book-product.json @@ -13,7 +13,7 @@ "project": { "name": "Red Hat Single Sign-On", "version": "7.1.0", - "versionMvn": "1.9.8.Final-redhat-1" + "versionMvn": "2.4.0.Final-redhat-1" }, "community": false, "product": true, @@ -21,6 +21,7 @@ "adminguide": { "name": "Server Administration Guide", "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.1-Beta/server-administration-guide/" - } + }, + "fuseVersion": "JBoss Fuse 6.3.0 Rollup 1" } } diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/topics/oidc/java/fuse/fuse-admin.adoc index f31c4d8675..b660669311 100644 --- a/topics/oidc/java/fuse/fuse-admin.adoc +++ b/topics/oidc/java/fuse/fuse-admin.adoc @@ -42,7 +42,7 @@ This file contains configuration of the client application, which is used by JAA [source, subs="attributes"] ---- -features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features features:install keycloak-jaas ---- diff --git a/topics/oidc/java/fuse/hawtio.adoc b/topics/oidc/java/fuse/hawtio.adoc index 32694a0130..60c2687fc9 100644 --- a/topics/oidc/java/fuse/hawtio.adoc +++ b/topics/oidc/java/fuse/hawtio.adoc @@ -53,7 +53,7 @@ your {{book.project.name}} environment. This file is used by the adapters on ser [source, subs="attributes"] ---- -features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.version}}/xml/features +features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features features:install keycloak ---- diff --git a/topics/oidc/java/fuse/install-feature.adoc b/topics/oidc/java/fuse/install-feature.adoc index 6cb17efab3..8059ebab90 100644 --- a/topics/oidc/java/fuse/install-feature.adoc +++ b/topics/oidc/java/fuse/install-feature.adoc @@ -31,7 +31,7 @@ org.ops4j.pax.url.mvn.repositories= \ You need to start {{book.fuseVersion}} and then in the Karaf terminal you type this: -[source] +[source,subs="attributes"] ---- features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features features:install keycloak @@ -59,7 +59,7 @@ features:list | grep keycloak This is useful if you are offline and/or don't want to use maven for download jar files and other artifacts. Once you download ZIP bundle of {{book.project.name}} Fuse adapter, you will need to unzip it into the root directory of JBoss Fuse. This should install the dependencies under the `system` directory. Use this for {{book.fuseVersion}} : -[source] +[source,subs="attributes"] ---- cd /path-to-fuse/jboss-fuse-6.3.0.redhat-198 unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-dist-{{book.project.versionMvn}}.zip @@ -67,7 +67,7 @@ unzip -q /path-to-adapter-zip/keycloak-fuse-adapter-dist-{{book.project.versionM Feel free to overwrite all already existing jars. Once you unzip archive, you can start the Fuse and again run commands in fuse/karaf terminal -[source] +[source,subs="attributes"] ---- features:addurl mvn:org.keycloak/keycloak-osgi-features/{{book.project.versionMvn}}/xml/features features:install keycloak From 88a9747825e9a0a9e67603e1fa4eaccdcadb17fb Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Mon, 5 Dec 2016 10:14:37 -0500 Subject: [PATCH 178/194] RHSSO-655: fixed end tag change missed in initial commit --- topics/saml/java/general-config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/saml/java/general-config.adoc b/topics/saml/java/general-config.adoc index f2bc70a31d..7eef62f5d2 100644 --- a/topics/saml/java/general-config.adoc +++ b/topics/saml/java/general-config.adoc @@ -28,7 +28,7 @@ This is what one might look like: - + Date: Thu, 8 Dec 2016 09:41:04 -0200 Subject: [PATCH 179/194] Removal of Known issues section for Node.js --- topics/oidc/nodejs-adapter.adoc | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/topics/oidc/nodejs-adapter.adoc b/topics/oidc/nodejs-adapter.adoc index 61950d7987..ebc78b06c8 100644 --- a/topics/oidc/nodejs-adapter.adoc +++ b/topics/oidc/nodejs-adapter.adoc @@ -12,26 +12,6 @@ To use the Node.js adapter, first you must create a client for your application Once the client is created click on the `Installation` tab select `{{book.project.name}} OIDC JSON` for `Format Option` then click on `Download`. The downloaded `keycloak.json` file should be at the root folder of your project. -==== Known issue - -Node.js adapter requires `realm-public-key` attribute and does not retrieve it automatically from {{book.project.name}}. Otherwise, your Node.js app won't start. - -In order to prevent any issues, go to your `Realm Settings` and `Keys`, copy the `Public Key` to your keycloak.json file adding `realm-public-key` attribute like the snippet below: - -[source,json] ----- -{ - "realm": "example-realm", - "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", - "auth-server-url": "http://localhost:8080/auth", - "ssl-required": "external", - "resource": "example-app", - "credentials": { - "secret": "mysecret" - } -} ----- - ==== Installation Assuming you've already installed https://nodejs.org[Node.js], create a folder for your application: From 1c1cadacae59c5e1daa22afa5c6199b0b67928fa Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Thu, 8 Dec 2016 10:22:41 -0500 Subject: [PATCH 180/194] added variables to json and master-docinfo files --- book-product.json | 4 +++- master-docinfo.xml | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/book-product.json b/book-product.json index f5a61e0ef8..f98fa62009 100755 --- a/book-product.json +++ b/book-product.json @@ -13,7 +13,9 @@ "project": { "name": "Red Hat Single Sign-On", "version": "7.1.0", - "versionMvn": "2.4.0.Final-redhat-1" + "versionMvn": "2.4.0.Final-redhat-1", + "doc_base_url": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/", + "doc_info_version_url": "7.1-Beta" }, "community": false, "product": true, diff --git a/master-docinfo.xml b/master-docinfo.xml index b0cc6f5c42..e7e6ccc9f7 100755 --- a/master-docinfo.xml +++ b/master-docinfo.xml @@ -1,10 +1,10 @@ -Red Hat Single Sign-On -7.1-Beta -Securing Applications and Services Guide -Securing Applications and Services Guide -7.1-Beta +{book_project_name} +{book_project_doc_info_version_url} +For Use with {book_project_name} {book_project_doc_info_version_url} +{book_title} +{doc_info_version_url} - This guide consists of information for securing applications and services using Red Hat Single Sign-On 7.1-Beta + This guide consists of information for securing applications and services using {book_project_name} {book_project_doc_info_version_url} Red Hat Customer Content Services From 857cb2a30bb55d112a76df8916146d6dd0508068 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Thu, 8 Dec 2016 11:05:22 -0500 Subject: [PATCH 181/194] added variables for external URLs --- book-product.json | 2 +- topics/client-registration.adoc | 4 ++-- topics/oidc/java/client-authentication.adoc | 2 +- topics/oidc/java/params_forwarding.adoc | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/book-product.json b/book-product.json index f98fa62009..a81d9aba14 100755 --- a/book-product.json +++ b/book-product.json @@ -22,7 +22,7 @@ "images": "rhsso-images", "adminguide": { "name": "Server Administration Guide", - "link": "https://access.redhat.com/documentation/en/red-hat-single-sign-on/7.1-Beta/server-administration-guide/" + "link": "/single/server-administration-guide/" }, "fuseVersion": "JBoss Fuse 6.3.0 Rollup 1" } diff --git a/topics/client-registration.adoc b/topics/client-registration.adoc index a96d5fb358..7d9d9dcdd6 100644 --- a/topics/client-registration.adoc +++ b/topics/client-registration.adoc @@ -24,13 +24,13 @@ There is an alternative to register new client without any token as well, but th ==== Bearer Token -The bearer token can be issued on behalf of a user or a Service Account. The following permissions are required to invoke the endpoints (see link:{{book.adminguide.link}}[{{book.adminguide.name}}] for more details): +The bearer token can be issued on behalf of a user or a Service Account. The following permissions are required to invoke the endpoints (see link:{{book.project.doc_base_url}}{{book.project.doc_info_version_url}}{{book.adminguide.link}}[{{book.adminguide.name}}] for more details): * create-client or manage-client - To create clients * view-client or manage-client - To view clients * manage-client - To update or delete client -If you are using a bearer token to create clients it's recommend to use a token from a Service Account with only the `create-client` role (see link:{{book.adminguide.link}}[{{book.adminguide.name}}] for more details). +If you are using a bearer token to create clients it's recommend to use a token from a Service Account with only the `create-client` role (see link:{{book.project.doc_base_url}}{{book.project.doc_info_version_url}}{{book.adminguide.link}}[{{book.adminguide.name}}] for more details). ==== Initial Access Token diff --git a/topics/oidc/java/client-authentication.adoc b/topics/oidc/java/client-authentication.adoc index a8b647e40c..750bb467d7 100644 --- a/topics/oidc/java/client-authentication.adoc +++ b/topics/oidc/java/client-authentication.adoc @@ -39,7 +39,7 @@ will download new keys when it sees the token signed by unknown `kid` (Key ID). ** Upload the client's public key or certificate - either in PEM format, in JWK format or from keystore. With this option, public key is hardcoded and needs to be changed anytime when client generates new keypair. You can even generate your own keystore from {{book.project.name}} admin console if you don't have your own ready. -See {{book.adminguide.link}}[{{book.adminguide.name}}] for more details of setup in {{book.project.name}} admin console. +See {{book.project.doc_base_url}}{{book.project.doc_info_version_url}}{{book.adminguide.link}}[{{book.adminguide.name}}] for more details of setup in {{book.project.name}} admin console. For setup on adapter's side you need to have something like this in your `keycloak.json` file: diff --git a/topics/oidc/java/params_forwarding.adoc b/topics/oidc/java/params_forwarding.adoc index 89d7588375..99e692478f 100644 --- a/topics/oidc/java/params_forwarding.adoc +++ b/topics/oidc/java/params_forwarding.adoc @@ -29,4 +29,4 @@ The supported parameters are: Most of the parameters are described in the http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint[OIDC specification]. The only exception is parameter `kc_idp_hint`, which is specific to {{book.project.name}} and contains the name of the identity provider to automatically use. -For more information see the `Identity Brokering` section in {{book.adminguide.link}}[{{book.adminguide.name}}]. +For more information see the `Identity Brokering` section in {{book.project.doc_base_url}}{{book.project.doc_info_version_url}}{{book.adminguide.link}}[{{book.adminguide.name}}]. From 964406affdba9e41e49888378ef34204b1584803 Mon Sep 17 00:00:00 2001 From: Sande Gilda Date: Mon, 12 Dec 2016 11:01:26 -0500 Subject: [PATCH 182/194] Rename saml/java/jboss-adapter.adoc file to saml-jboss-adapter.adoc and update references to work around bug in python script path conversion issue. --- .gitignore | 4 ++++ SUMMARY.adoc | 8 ++++---- topics/overview/supported-platforms.adoc | 4 ++-- .../java/{jboss-adapter.adoc => saml-jboss-adapter.adoc} | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) rename topics/saml/java/{jboss-adapter.adoc => saml-jboss-adapter.adoc} (96%) diff --git a/.gitignore b/.gitignore index 39e1f9943f..3a0afb1a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ catalog.xml ######### target +# ccutil build directory # +########################## +build/ + diff --git a/SUMMARY.adoc b/SUMMARY.adoc index f228c8d62e..6afa750d7b 100644 --- a/SUMMARY.adoc +++ b/SUMMARY.adoc @@ -25,11 +25,11 @@ ... link:topics/oidc/java/jetty8-adapter.adoc[Jetty 8.1.x Adapter] ... link:topics/oidc/java/spring-boot-adapter.adoc[Spring Boot Adapter] ... link:topics/oidc/java/spring-security-adapter.adoc[Spring Security Adapter] - {% endif %} + {% endif %} {% if book.community %} ... link:topics/oidc/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] ... link:topics/oidc/java/jaas.adoc[JAAS plugin] - {% endif %} + {% endif %} ... link:topics/oidc/java/adapter-context.adoc[Security Context] ... link:topics/oidc/java/adapter_error_handling.adoc[Error Handling] ... link:topics/oidc/java/logout.adoc[Logout] @@ -61,7 +61,7 @@ .... link:topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc[IDP SingleLogoutService sub element] .... link:topics/saml/java/general-config/idp_keys_subelement.adoc[IDP Keys subelement] .... link:topics/saml/java/general-config/idp_httpclient_subelement.adoc[IDP HttpClient subelement] - ... link:topics/saml/java/jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] + ... link:topics/saml/java/saml-jboss-adapter.adoc[JBoss EAP/Wildfly Adapter] .... link:topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc[Adapter Installation] .... link:topics/saml/java/jboss-adapter/required_per_war_configuration.adoc[Per WAR Configuration] .... link:topics/saml/java/jboss-adapter/securing_wars.adoc[Securing WARs via SAML Subsystem] @@ -77,7 +77,7 @@ {% endif %} {% if book.community %} ... link:topics/saml/java/servlet-filter-adapter.adoc[Java Servlet Filter Adapter] - {% endif %} + {% endif %} ... link:topics/saml/java/idp-registration.adoc[Registering with an IDP] ... link:topics/saml/java/logout.adoc[Logout] ... link:topics/saml/java/assertion-api.adoc[Obtaining Assertion Attributes] diff --git a/topics/overview/supported-platforms.adoc b/topics/overview/supported-platforms.adoc index f12642e675..e8585c1463 100644 --- a/topics/overview/supported-platforms.adoc +++ b/topics/overview/supported-platforms.adoc @@ -67,9 +67,9 @@ ===== Java -* <> +* <> {% if book.community %} -* <> +* <> * <> * <> {% endif %} diff --git a/topics/saml/java/jboss-adapter.adoc b/topics/saml/java/saml-jboss-adapter.adoc similarity index 96% rename from topics/saml/java/jboss-adapter.adoc rename to topics/saml/java/saml-jboss-adapter.adoc index 7ca4f173aa..c090aba125 100644 --- a/topics/saml/java/jboss-adapter.adoc +++ b/topics/saml/java/saml-jboss-adapter.adoc @@ -1,4 +1,4 @@ -[[_saml-jboss-adapter]] +[[_saml_jboss_adapter]] {% if book.community %} ==== JBoss EAP/Wildfly Adapter From d3f88f91d293f6acf012f52a37ec02df54454b82 Mon Sep 17 00:00:00 2001 From: Slawomir Dabek Date: Mon, 19 Dec 2016 18:03:41 +0100 Subject: [PATCH 183/194] KEYCLOAK-2962 Added autodetect-bearer-only config parameter --- topics/oidc/java/java-adapter-config.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index df28bb0ecb..ea79163db1 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -108,6 +108,13 @@ bearer-only:: This is _OPTIONAL_. The default value is _false_. +autodetect-bearer-only:: + This should be set to __true__ if your application serves both a web application and web services (e.g. SOAP or REST). + It allows you to redirect unauthenticated users of the web application to the Keycloak login page, + but send an HTTP `401` status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. + Keycloak auto-detects SOAP or REST clients based on typical headers like `X-Requested-With`, `SOAPAction` or `Accept`. + The default value is _false_. + enable-basic-auth:: This tells the adapter to also support basic authentication. If this option is enabled, then _secret_ must also be provided. This is _OPTIONAL_. From 99951191e91f0adb7fe549a0d13b3314bb7d12d5 Mon Sep 17 00:00:00 2001 From: WalkerWatch Date: Tue, 3 Jan 2017 15:42:58 -0500 Subject: [PATCH 184/194] Doc update for KEYCLOAK-4108 --- topics/oidc/java/jetty9-adapter.adoc | 48 +++++++++++++--------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/topics/oidc/java/jetty9-adapter.adoc b/topics/oidc/java/jetty9-adapter.adoc index 7f0bd8bf8d..42a0fcc757 100755 --- a/topics/oidc/java/jetty9-adapter.adoc +++ b/topics/oidc/java/jetty9-adapter.adoc @@ -2,44 +2,42 @@ [[_jetty9_adapter]] ==== Jetty 9.x Adapters -Keycloak has a separate adapter for Jetty 9.1.x and Jetty 9.2.x that you will have to install into your Jetty installation. +Keycloak has a separate adapter for Jetty 9.1.x, Jetty 9.2.x and Jetty 9.3.x that you will have to install into your Jetty installation. You then have to provide some extra configuration in each WAR you deploy to Jetty. -Let's go over these steps. +Let's go over these steps. [[_jetty9_adapter_installation]] ===== Adapter Installation Adapters are no longer included with the appliance or war distribution.Each adapter is a separate download on the Keycloak download site. -They are also available as a maven artifact. - -You must unzip the Jetty 9.x distro into Jetty 9.x's root directory. -Including adapter's jars within your WEB-INF/lib directory will not work! - - -[source] ----- - -$ cd $JETTY_HOME -$ unzip keycloak-jetty92-adapter-dist.zip ----- - -Next, you will have to enable the keycloak module for your jetty.base. +They are also available as a maven artifact. +You must unzip the Jetty 9.x distro into Jetty 9.x's link:https://www.eclipse.org/jetty/documentation/current/startup-base-and-home.html[base directory.] +Including adapter's jars within your WEB-INF/lib directory will not work! +In the example below, the Jetty base is named `your-base`: [source] ---- $ cd your-base +$ unzip keycloak-jetty93-adapter-dist-2.5.0.Final.zip +---- + +Next, you will have to enable the `keycloak` module for your Jetty base: + +[source] +---- + $ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak ----- +---- [[_jetty9_per_war]] ===== Required Per WAR Configuration -This section describes how to secure a WAR directly by adding config and editing files within your WAR package. +This section describes how to secure a WAR directly by adding config and editing files within your WAR package. The first thing you must do is create a `WEB-INF/jetty-web.xml` file in your WAR package. -This is a Jetty specific config file and you must define a Keycloak specific authenticator within it. +This is a Jetty specific config file and you must define a Keycloak specific authenticator within it. [source] ---- @@ -62,10 +60,10 @@ Next you must create a `keycloak.json` adapter config file within the `WEB-INF` The format of this config file is describe in the <> section. WARNING: The Jetty 9.1.x adapter will not be able to find the `keycloak.json` file. -You will have to define all adapter settings within the `jetty-web.xml` file as described below. +You will have to define all adapter settings within the `jetty-web.xml` file as described below. Instead of using keycloak.json, you can define everything within the `jetty-web.xml`. -You'll just have to figure out how the json settings match to the `org.keycloak.representations.adapters.config.AdapterConfig` class. +You'll just have to figure out how the json settings match to the `org.keycloak.representations.adapters.config.AdapterConfig` class. [source] @@ -98,15 +96,15 @@ You'll just have to figure out how the json settings match to the `org.keycloak. ----- +---- You do not have to crack open your WAR to secure it with keycloak. Instead create the jetty-web.xml file in your webapps directory with the name of yourwar.xml. Jetty should pick it up. -In this mode, you'll have to declare keycloak.json configuration directly within the xml file. +In this mode, you'll have to declare keycloak.json configuration directly within the xml file. Finally you must specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs. -Here's an example: +Here's an example: [source] @@ -145,4 +143,4 @@ Here's an example: user ----- +---- From 0df18a5ac7d89a368c7cdba7ebd8b7806cb042dc Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 5 Jan 2017 08:22:21 +0100 Subject: [PATCH 185/194] Add docs for proxy-url property --- topics/oidc/java/java-adapter-config.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/topics/oidc/java/java-adapter-config.adoc b/topics/oidc/java/java-adapter-config.adoc index ea79163db1..5d9f94e1fe 100644 --- a/topics/oidc/java/java-adapter-config.adoc +++ b/topics/oidc/java/java-adapter-config.adoc @@ -148,6 +148,9 @@ allow-any-hostname:: This seting may be useful in test environments This is _OPTIONAL_. The default value is `false`. +proxy-url:: + The URL for the HTTP proxy if one is used. + truststore:: The value is the file path to a keystore file. If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead. From f86727b266b64243980f15ca7eb8ed2cae640228 Mon Sep 17 00:00:00 2001 From: "Nicole C. Engard" Date: Fri, 6 Jan 2017 14:24:49 -0600 Subject: [PATCH 186/194] Create README.adoc --- internal-resources/README.adoc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 internal-resources/README.adoc diff --git a/internal-resources/README.adoc b/internal-resources/README.adoc new file mode 100644 index 0000000000..82d46c27d8 --- /dev/null +++ b/internal-resources/README.adoc @@ -0,0 +1,3 @@ += Documentation : internal-resources/ + +The `internal-resources/` directory contains internal reference documentation written for use by the documentation team. From d164222ca0f4220a8d779386abcf3b72536f2f38 Mon Sep 17 00:00:00 2001 From: "Nicole C. Engard" Date: Fri, 6 Jan 2017 14:25:21 -0600 Subject: [PATCH 187/194] Add files via upload --- internal-resources/template_concept.adoc | 17 ++++++ internal-resources/template_reference.adoc | 13 +++++ internal-resources/template_task.adoc | 59 +++++++++++++++++++++ internal-resources/template_task_brief.adoc | 20 +++++++ 4 files changed, 109 insertions(+) create mode 100644 internal-resources/template_concept.adoc create mode 100644 internal-resources/template_reference.adoc create mode 100644 internal-resources/template_task.adoc create mode 100644 internal-resources/template_task_brief.adoc diff --git a/internal-resources/template_concept.adoc b/internal-resources/template_concept.adoc new file mode 100644 index 0000000000..bc1c1a295e --- /dev/null +++ b/internal-resources/template_concept.adoc @@ -0,0 +1,17 @@ +[[concept_module]] + += Concept Template and Guidelines + +_In the title, include nouns that are used in the body text -- this helps readers and search engines find information quickly._ + +A concept module describes and explains things such as a product, subsystem, or feature -- what a customer needs to understand to do a task. A concept module may also explain how things relate and interact with other things. The use of graphics and diagrams can speed up understanding of a concept. + +* Look at nouns and noun phrases in related task modules and user story assemblies to find the concepts to explain to users. + +* A concept module in product documentation should explain only things that are visible to users. + +* If a product concept is interesting, but not visible to users, the concept probably does not require explanation in a concept module. + +* A concept module should NOT include numbered steps or other wording that instructs a user to execute a command or perform an action. Instead, put that information in a separate task module or user story assembly. + + diff --git a/internal-resources/template_reference.adoc b/internal-resources/template_reference.adoc new file mode 100644 index 0000000000..c1ce32824d --- /dev/null +++ b/internal-resources/template_reference.adoc @@ -0,0 +1,13 @@ +[[reference_module]] + += Reference Template and Guidelines + +_In the title, include nouns that are used in the body text — this helps readers and search engines find information quickly._ + +A reference module lists things (such as a list of commands) or has a very regimented structure (such as the consistent structure of man pages). A reference module explains the details that a customer needs to know to do a task. A reference module is well-organized if users can scan it to quickly find the details they want. + +* A reference module that is a list of things may be made easier to scan if its content is organized alphabetically or formatted as a table. Think of an alphabetical list of commands that can be used with an application, or of an alphabetical list of system components with brief definitions formatted as a 2-column table. + +* If you have a large volume of the same type of information to document, figure out a consistent structure that the information details can fit into and then doument each logical unit of information as 1 reference module. Think of man pages, which document very different information details, but that use consistent titles and formats to present those details in a consistent information structure. + + diff --git a/internal-resources/template_task.adoc b/internal-resources/template_task.adoc new file mode 100644 index 0000000000..5ecbfbc4cd --- /dev/null +++ b/internal-resources/template_task.adoc @@ -0,0 +1,59 @@ +[[task_module]] + += Do One Task + +_Start title with verb form such as Creating. Use the gerund form (noun form of verb) for titles, not the imperative form._ + +_Avoid using the word 'Configuring' over and over. Other words might include:_ + +* _Specifying_ +* _Adding_ +* _Constructing_ +* _Arranging_ +* _Building_ +* _Setting_ +* _Managing_ +* _Defining_ +* _Customizing_ +* _Or instead of using synonyms (which becomes obvious) 'verb-alize' a word and refactor the heading - limiting (instead of 'setting resource limits')_ + +_The standard for all Red Hat documentation is title case for all headings and titles._ + +_A task module is a procedure written with numbered steps -- what a customer needs to do to accomplish a goal successfully._ + +This paragraph explains why the user performs the task, sets the context of the task, and may explain or list special considerations specific to this task. Keep the information brief and focused on what is needed for this specific task. Suggested length is 1 to 3 sentences, can be longer if needed. + + +.Prerequisites _(if needed)_ + +* Sentence or a bulleted list of prerequisites that must be in place or done before the user starts this task. + +* Delete section title and bullets if the task has no required prerequisites. + +* Text can be a link to a prerequisite task that the user must do before starting this task. + + +.Procedure + +_Start title with verb form such as Creating. Use the gerund form (noun form of verb) for titles, not the imperative form._ + +_Put steps in a numbered list. The step sequence is important to a repeatable successful outcome._ + +. Start each step with an active verb, because each step corresponds to one user action. + +. Include one command or action per step. + +. Format the step line as an unnumbered bullet if the procedure includes only 1 step (exception to numbering the steps). + +. Include one command or action per step. + +. Include one command or action per step. + + +.Related Information _(if needed)_ + +* Bulleted list of links to concepts, reference, or other tasks closely related to this task. + +* Include only the most relevant items as links, not every possible related item. + +* Delete section title and bullets if no related information is needed. diff --git a/internal-resources/template_task_brief.adoc b/internal-resources/template_task_brief.adoc new file mode 100644 index 0000000000..2e41865a29 --- /dev/null +++ b/internal-resources/template_task_brief.adoc @@ -0,0 +1,20 @@ +[[task_module]] + += Do One Task + +This paragraph explains why the user performs the task, sets the context of the task, and may explain or list special considerations specific to this task. Keep the information brief and focused on what is needed for this specific task. Suggested length is 1 to 3 sentences, can be longer if needed. + + +.Prerequisites _(if needed)_ + +* List + + +.Procedure + +. List + + +.Related Information _(if needed)_ + +* List From 6958b6df23a147d98a7a3c4117bb9a1f7cd784a1 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Thu, 12 Jan 2017 17:49:24 +0100 Subject: [PATCH 188/194] Fixing numbering of roles Otherwise "user" will overwrite "admin" in array index 0 --- topics/oidc/java/spring-boot-adapter.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index 6cce21170d..afec241f3a 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -57,10 +57,10 @@ Here's an example configuration: keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin -keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = user +keycloak.securityConstraints[0].securityCollections[0].authRoles[1] = user keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure keycloak.securityConstraints[0].securityCollections[1].name = admin stuff keycloak.securityConstraints[0].securityCollections[1].authRoles[0] = admin keycloak.securityConstraints[0].securityCollections[1].patterns[0] = /admin ----- \ No newline at end of file +---- From a36abc83b64817bb6c5fdf66d16ce468bcfecef4 Mon Sep 17 00:00:00 2001 From: sebastienblanc Date: Fri, 20 Jan 2017 09:38:00 +0100 Subject: [PATCH 189/194] update spring's documentation --- topics/oidc/java/spring-boot-adapter.adoc | 23 ++++-- topics/oidc/java/spring-security-adapter.adoc | 74 ++++++++++++------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/topics/oidc/java/spring-boot-adapter.adoc index afec241f3a..41838e41c7 100755 --- a/topics/oidc/java/spring-boot-adapter.adoc +++ b/topics/oidc/java/spring-boot-adapter.adoc @@ -2,14 +2,14 @@ ==== Spring Boot Adapter To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app. -You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`). Let's go over these steps. +You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`). Let's go over these steps. [[_spring_boot_adapter_installation]] ===== Adapter Installation The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all you need to do is add the Keycloak Spring Boot adapter JAR to your project. Depending on what container you are using with Spring Boot, you also need to add the appropriate Keycloak container adapter. -If you are using Maven, add the following to your pom.xml (using Tomcat as an example): +If you are using Maven, add the following to your pom.xml (using Tomcat as an example): [source,xml,subs="attributes+"] @@ -26,15 +26,21 @@ If you are using Maven, add the following to your pom.xml (using Tomcat as an ex keycloak-tomcat8-adapter {{book.project.versionMvn}} ----- +---- + +Currently the following embedded containers are supported : + +* Tomcat +* Undertow +* Jetty [[_spring_boot_adapter_configuration]] ===== Required Spring Boot Adapter Configuration -This section describes how to configure your Spring Boot app to use Keycloak. +This section describes how to configure your Spring Boot app to use Keycloak. Instead of a `keycloak.json` file, you configure the realm for the Spring Boot Keycloak adapter via the normal Spring Boot configuration. -For example: +For example: [source] ---- @@ -48,8 +54,11 @@ keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111 keycloak.use-resource-role-mappings = true ---- -You also need to specify the J2EE security config that would normally go in the `web.xml`. -Here's an example configuration: +To configure a Policy Enforcer, unlike keycloak.json, `policy-enforcer-config` must be used instead of just `policy-enforcer`. + +You also need to specify the Java EE security config that would normally go in the `web.xml`. +The Spring Boot Adapter will set the `login-method` to `KEYCLOAK` and configure the `security-constraints` at startup time. +Here's an example configuration: [source] ---- diff --git a/topics/oidc/java/spring-security-adapter.adoc b/topics/oidc/java/spring-security-adapter.adoc index 9da556a8a6..30f8157d06 100755 --- a/topics/oidc/java/spring-security-adapter.adoc +++ b/topics/oidc/java/spring-security-adapter.adoc @@ -2,14 +2,14 @@ ==== Spring Security Adapter To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project. -You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline. +You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline. Unlike the other Keycloak Adapters, you should not configure your security in web.xml. -However, keycloak.json is still required. +However, keycloak.json is still required. ===== Adapter Installation -Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. +Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build. [source,xml,subs="attributes+"] @@ -23,19 +23,19 @@ Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle ===== Spring Security Configuration -The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax. +The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax. ====== Java Configuration Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a http://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/web/WebSecurityConfigurer.html[WebSecurityConfigurer] instance. The implementation allows customization by overriding methods. -While its use is not required, it greatly simplifies your security context configuration. +While its use is not required, it greatly simplifies your security context configuration. [source] ---- - + @Configuration @EnableWebSecurity @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) @@ -69,22 +69,22 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter .anyRequest().permitAll(); } } ----- +---- -You must provide a session authentication strategy bean which should be of type `RegisterSessionAuthenticationStrategy` for public or confidential applications and `NullAuthenticatedSessionStrategy` for bearer-only applications. +You must provide a session authentication strategy bean which should be of type `RegisterSessionAuthenticationStrategy` for public or confidential applications and `NullAuthenticatedSessionStrategy` for bearer-only applications. Spring Security's `SessionFixationProtectionStrategy` is currently not supported because it changes the session identifier after login via Keycloak. -If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier. +If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier. ====== XML Configuration -While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose. +While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose. [source] ---- - + ----- +---- ===== Multi Tenancy @@ -155,23 +155,23 @@ More details on how to implement the `KeycloakConfigResolver` can be found in << ===== Naming Security Roles Spring Security, when using role-based authentication, requires that role names start with `ROLE_`. -For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`. +For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`. The class `org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider` supports an optional `org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper` which can be used to map roles coming from Keycloak to roles recognized by Spring Security. Use, for example, `org.springframework.security.core.authority.mapping.SimpleAuthorityMapper` to insert the `ROLE_` prefix and convert the role name to upper case. -The class is part of Spring Security Core module. +The class is part of Spring Security Core module. ===== Client to Client Support To simplify communication between clients, Keycloak provides an extension of Spring's `RestTemplate` that handles bearer token authentication for you. To enable this feature your security configuration must add the `KeycloakRestTemplate` bean. -Note that it must be scoped as a prototype to function correctly. +Note that it must be scoped as a prototype to function correctly. -For Java configuration: +For Java configuration: [source] ---- - + @Configuration @EnableWebSecurity @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) @@ -190,24 +190,24 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { ... } ----- +---- -For XML configuration: +For XML configuration: [source] ---- - + ----- +---- Your application code can then use `KeycloakRestTemplate` any time it needs to make a call to another client. -For example: +For example: [source] ---- - + @Service public class RemoteProductService implements ProductService { @@ -223,18 +223,36 @@ public class RemoteProductService implements ProductService { return Arrays.asList(response.getBody()); } } ----- +---- -===== Spring Boot Configuration +===== Spring Boot Integration + +The Spring Boot and the Spring Security adapters can be combined. + +====== Using Spring Boot Configuration + +By Default, the Spring Security Adapter looks for a `keycloak.json` configuration file. You can make sure it looks at the configuration provided by the Spring Boot Adapter by adding this bean : + +[source] +---- + +@Bean +public KeycloakConfigResolver KeycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); +} + +---- + +====== Avoid double Filter bean registration Spring Boot attempts to eagerly register filter beans with the web application context. -Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add two ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice. +Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add two ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice. [source] ---- - + @Configuration @EnableWebSecurity public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter @@ -259,4 +277,4 @@ public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter ... } ----- \ No newline at end of file +---- From afe8777bbb58bb46bca58ac7686ddf785c23d8c4 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 2 Feb 2017 13:20:15 +0100 Subject: [PATCH 190/194] Added instructions to upgrade WF/EAP and JS adapters --- topics/oidc/java/jboss-adapter.adoc | 128 ++++++++++------------------ topics/oidc/javascript-adapter.adoc | 3 + 2 files changed, 47 insertions(+), 84 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index c28185fac0..9af33b2208 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -22,9 +22,9 @@ Alternatively, you don't have to modify your WAR at all and you can secure it vi Both methods are described in this section. [[_jboss_adapter_installation]] -===== Adapter Installation +===== Installing the adapter -Adapters are available as a separate archive and are also available as Maven artifacts. +Adapters are available as a separate archive depending on what server version you are using. {% if book.community %} Install on Wildfly 9 or 10: @@ -86,101 +86,38 @@ $ unzip rh-sso-{{book.project.version}}-eap6-adapter.zip ---- {% endif %} -This ZIP archive contains JBoss Modules specific to the {{book.project.name}} adapter. It also contains JBoss CLI scripts to install and configure the adapter. +This ZIP archive contains JBoss Modules specific to the {{book.project.name}} adapter. It also contains JBoss CLI scripts +to configure the adapter subsystem. -Once the ZIP archive is extracted you have to enable the {{book.project.name}} subystem in the server configuration (i.e. `standalone.xml`). The easiest way to -do this is to use the supplied JBoss CLI scripts. - -To install and configure the adapter, first start the server and then run the JBoss CLI installation script : - -[source] ----- -$ ./bin/jboss-cli.sh -c --file=adapter-install.cli ----- - -The script will add the required configuration to the server configuration file. - -{% if book.community %} -For JBoss EAP 7 and WildFly 9+ there is also an offline CLI script that can be used to install the adapter while the server -is not running: -{% endif %} -{% if book.product %} -For JBoss EAP 7 there is also an offline CLI script that can be used to install the adapter while the server -is not running: -{% endif %} +To configure the adapter subsystem if the server is not running execute: [source] ---- $ ./bin/jboss-cli.sh --file=adapter-install-offline.cli ---- -If you are planning to add it manually you need to add the extension and subsystem definition to the server configuration: +NOTE: The offline script is not available for JBoss EAP 6 -[source,xml] ----- - - - ... - - - - - ... - ----- - -If you need to be able to propagate the security context from the web tier to the EJB tier you also need to add the `keycloak` security domain: - -[source,xml] ----- - - - ... - - - - - - - ... ----- - -For example, if you have a JAX-RS service that is an EJB within your WEB-INF/classes directory, you'll want to annotate it with the @SecurityDomain annotation as follows: +Alternatively, if the server is running execute: [source] ---- - -import org.jboss.ejb3.annotation.SecurityDomain; -import org.jboss.resteasy.annotations.cache.NoCache; - -import javax.annotation.security.RolesAllowed; -import javax.ejb.EJB; -import javax.ejb.Stateless; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import java.util.ArrayList; -import java.util.List; - -@Path("customers") -@Stateless -@SecurityDomain("keycloak") -public class CustomerService { - - @EJB - CustomerDB db; - - @GET - @Produces("application/json") - @NoCache - @RolesAllowed("db_user") - public List getCustomers() { - return db.getCustomers(); - } -} +$ ./bin/jboss-cli.sh --file=adapter-install.cli ---- +[[_jboss_adapter_upgrading]] +===== Upgrading the adapter + +It is important that you upgrade the ${book.project.name}} server first then the adapters. This is because older adapters +will work with the newer server, but not always the other way around. + +The steps to upgrade the adapter is slightly different to the steps to install the adapter. + +First step is to delete the old adapter modules. Do this be deleting the directory `modules/system/add-ons/keycloak/`. + +Next follow the steps in the <<_jboss_adapter_installation,Installing the adapter>> to unzip the adapter. Since the +adapter subsystem has already been configured skip that step. + ===== Required Per WAR Configuration This section describes how to secure a WAR directly by adding config and editing files within your WAR package. @@ -303,3 +240,26 @@ If you have multiple deployments secured by the same realm you can share the rea ---- + + +===== Security Domain + +To propogate the security context to the EJB tier you need to configure it to use the "keycloak" security domain. This +can be achieved with the @SecurityDomain annotation: + +[source] +---- + +import org.jboss.ejb3.annotation.SecurityDomain; +... + +@Stateless +@SecurityDomain("keycloak") +public class CustomerService { + + @RolesAllowed("user") + public List getCustomers() { + return db.getCustomers(); + } +} +---- \ No newline at end of file diff --git a/topics/oidc/javascript-adapter.adoc b/topics/oidc/javascript-adapter.adoc index 2963168b7e..03107fdd4a 100644 --- a/topics/oidc/javascript-adapter.adoc +++ b/topics/oidc/javascript-adapter.adoc @@ -6,6 +6,9 @@ support for Cordova applications. The library can be retrieved directly from the {{book.project.name}} server at `/auth/js/keycloak.js` and is also distributed as a ZIP archive. +It's recommended to load the JavaScript adapter directly from the ${book.project.name}} server as it will automatically be updated when you +upgrade the server. If you copy the adapter to your web application instead make sure you upgrade it after you have upgraded the server. + One important thing to note about using client-side applications is that the client has to be a public client as there is no secure way to store client credentials in a client-side application. This makes it very important to make sure the redirect URIs you have configured for the client are correct and as specific as possible. From cd0d52c9812efe17946407ceedbb01af3d57e9b9 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Fri, 3 Feb 2017 17:14:17 -0500 Subject: [PATCH 191/194] WIP: mod_auth_mellon edits --- .../saml/java/MigrationFromOlderVersions.adoc | 2 +- .../roleidentifiers_element.adoc | 4 +- topics/saml/java/general-config/sp-keys.adoc | 8 +- .../saml/java/general-config/sp_element.adoc | 14 +- topics/saml/java/idp-registration.adoc | 6 +- topics/saml/java/saml_adapter_overview.adoc | 2 +- topics/saml/java/servlet-filter-adapter.adoc | 6 +- topics/saml/mod-auth-mellon.adoc | 205 ++++++++++++++++-- 8 files changed, 213 insertions(+), 34 deletions(-) diff --git a/topics/saml/java/MigrationFromOlderVersions.adoc b/topics/saml/java/MigrationFromOlderVersions.adoc index dc944a6dbf..adce88816a 100644 --- a/topics/saml/java/MigrationFromOlderVersions.adoc +++ b/topics/saml/java/MigrationFromOlderVersions.adoc @@ -4,7 +4,7 @@ ====== SAML SP Client Adapter Changes -Keycloak SAML SP Client Adapter now requires a specific endpoint, `/saml` to be registered with your IDP. +Keycloak SAML SP Client Adapter now requires a specific endpoint, `/saml` to be registered with your IdP. The SamlFilter must also be bound to /saml in addition to any other binding it has. This had to be done because SAML POST binding would eat the request input stream and this would be really bad for clients that relied on it. diff --git a/topics/saml/java/general-config/roleidentifiers_element.adoc b/topics/saml/java/general-config/roleidentifiers_element.adoc index ff0a3313ca..a001e64b33 100644 --- a/topics/saml/java/general-config/roleidentifiers_element.adoc +++ b/topics/saml/java/general-config/roleidentifiers_element.adoc @@ -1,5 +1,5 @@ -===== RoleIdentifiers element +===== RoleIdentifiers Element The `RoleIdentifiers` element defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user. @@ -15,7 +15,7 @@ as role identifiers within the Java EE Security Context for the user. ---- By default `Role` attribute values are converted to Java EE roles. -Some IDPs send roles via a `member` or `memberOf` attribute assertion. +Some IdPs send roles using a `member` or `memberOf` attribute assertion. You can define one or more `Attribute` elements to specify which SAML attributes must be converted into roles. diff --git a/topics/saml/java/general-config/sp-keys.adoc b/topics/saml/java/general-config/sp-keys.adoc index 45e9c4cd16..a36440a019 100644 --- a/topics/saml/java/general-config/sp-keys.adoc +++ b/topics/saml/java/general-config/sp-keys.adoc @@ -1,10 +1,10 @@ [[_saml-sp-keys]] -===== SP Keys and Key elements +===== Service Provider Keys and Key Elements -If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. -For client signed documents you must define both the private and public key or certificate that will be used to sign documents. -For encryption, you only have to define the private key that will be used to decrypt. +If the IdP requires that the client application (or SP) sign all of its requests and/or if the IdP will encrypt assertions, you must define the keys used to do this. +For client-signed documents you must define both the private and public key or certificate that is used to sign documents. +For encryption, you only have to define the private key that is used to decrypt it. There are two ways to describe your keys. They can be stored within a Java KeyStore or you can copy/paste the keys directly within `keycloak-saml.xml` in the PEM format. diff --git a/topics/saml/java/general-config/sp_element.adoc b/topics/saml/java/general-config/sp_element.adoc index 03b8b69144..66bad877f1 100644 --- a/topics/saml/java/general-config/sp_element.adoc +++ b/topics/saml/java/general-config/sp_element.adoc @@ -1,7 +1,7 @@ ===== SP Element -Here is the explanation of the SP element attributes +Here is the explanation of the SP element attributes: [source,xml] ---- @@ -16,7 +16,7 @@ Here is the explanation of the SP element attributes ---- entityID:: This is the identifier for this client. - The IDP needs this value to determine who the client is that is communicating with it. This setting is _REQUIRED_. + The IdP needs this value to determine who the client is that is communicating with it. This setting is _REQUIRED_. sslPolicy:: This is the SSL policy the adapter will enforce. @@ -24,28 +24,28 @@ sslPolicy:: For `ALL`, all requests must come in via HTTPS. For `EXTERNAL`, only non-private IP addresses must come over the wire via HTTPS. For `NONE`, no requests are required to come over via HTTPS. - This settings is _OPTIONAL_. Default value is `EXTERNAL`. + This setting is _OPTIONAL_. Default value is `EXTERNAL`. nameIDPolicyFormat:: SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. - It must be a standard SAML format identifier, i.e. `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. + It must be a standard SAML format identifier: `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. This setting is _OPTIONAL_. By default, no special format is requested. forceAuthentication:: - SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. + SAML clients can request that a user is re-authenticated even if they are already logged in at the IdP. Set this to `true` to enable. This setting is _OPTIONAL_. Default value is `false`. isPassive:: - SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. + SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IdP. Set this to `true` if you want this. Do not use together with `forceAuthentication` as they are opposite. This setting is _OPTIONAL_. Default value is `false`. turnOffChangeSessionIdOnLogin:: - The session id is changed by default on a successful login on some platforms to plug a security attack vector. + The session ID is changed by default on a successful login on some platforms to plug a security attack vector. Change this to `true` to disable this. It is recommended you do not turn it off. Default value is `false`. diff --git a/topics/saml/java/idp-registration.adoc b/topics/saml/java/idp-registration.adoc index 285971e51f..ca6a0579fe 100644 --- a/topics/saml/java/idp-registration.adoc +++ b/topics/saml/java/idp-registration.adoc @@ -1,6 +1,6 @@ -==== Registering with an IDP +==== Registering with an Identity Provider -For each servlet based adapter, the endpoint you register for the assert consumer service url and and single logout service -must be the base url of your servlet application with `/saml` appended to it i.e. `$$https://example.com/contextPath/saml$$` +For each servlet-based adapter, the endpoint you register for the assert consumer service URL and and single logout service +must be the base URL of your servlet application with `/saml` appended to it, that is, `$$https://example.com/contextPath/saml$$`. diff --git a/topics/saml/java/saml_adapter_overview.adoc b/topics/saml/java/saml_adapter_overview.adoc index 9e85b2abe4..7cb0677496 100644 --- a/topics/saml/java/saml_adapter_overview.adoc +++ b/topics/saml/java/saml_adapter_overview.adoc @@ -3,4 +3,4 @@ This document describes the Keycloak SAML client adapter and how it can be configured for a variety of platforms. The Keycloak SAML client adapter is a standalone component that provides generic SAML 2.0 support for your web applications. There are no Keycloak server extensions built into it. -As long as the IDP you are talking to supports standard SAML, the Keycloak SAML client adapter should be able to integrate with it. +As long as the Identity Provider (IdP) being communicated with supports standard SAML, the Keycloak SAML client adapter should be able to integrate with it. diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/topics/saml/java/servlet-filter-adapter.adoc index 81e7bfa7e3..a00dd72d32 100644 --- a/topics/saml/java/servlet-filter-adapter.adoc +++ b/topics/saml/java/servlet-filter-adapter.adoc @@ -10,8 +10,8 @@ you do not define security constraints in _web.xml_. Instead you define a filter mapping using the {{book.project.name}} servlet filter adapter to secure the url patterns you want to secure. NOTE: Backchannel logout works a bit differently than the standard adapters. - Instead of invalidating the http session it instead marks the session id as logged out. - There's just no way of arbitrarily invalidating an http session based on a session id. + Instead of invalidating the http session it instead marks the session ID as logged out. + There's just no way of arbitrarily invalidating an http session based on a session ID. WARNING: Backchannel logout does not currently work when you have a clustered application that uses the SAML filter. @@ -43,7 +43,7 @@ You can define multiple filter mappings if you have various different secure and WARNING: You must have a filter mapping that covers `/saml`. This mapping covers all server callbacks. -When registering SPs with an IDP, you must register `http[s]://hostname/{context-root}/saml` as your Assert Consumer Service URL and Single Logout Service URL. +When registering SPs with an IdP, you must register `http[s]://hostname/{context-root}/saml` as your Assert Consumer Service URL and Single Logout Service URL. To use this filter, include this maven artifact in your WAR poms: diff --git a/topics/saml/mod-auth-mellon.adoc b/topics/saml/mod-auth-mellon.adoc index 0467afb9a0..1c4945a4e0 100644 --- a/topics/saml/mod-auth-mellon.adoc +++ b/topics/saml/mod-auth-mellon.adoc @@ -2,24 +2,203 @@ === mod_auth_mellon Apache HTTPD Module -The https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD -as a proxy, then you can use _mod_auth_mellon_ to secure your web application with SAML. Configuration of this adapter -is beyond the scope of this document. Please see the _mod_auth_mellon_ Github repo for more details on configuration. +The https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD as a proxy, then you can use mod_auth_mellon to secure your web application with SAML. For more details on configuration of this adapter see the _mod_auth_mellon_ Github repo. -To configure _mod_auth_mellon_ you'll need +To configure mod_auth_mellon you'll need: -* IDP entity descriptor XML file. This describes the connection to {{book.project.name}} or another SAML IDP -* SP entity descriptor XML file. This describes the SAML connections and config for the application you are securing. -* Private key PEM file. This is a text file that defines the private key the application will use to sign documents. It is - in the PEM format -* Certificate PEM file. This is a text file that defines the certificate for your application. -* _mod_auth_mellon_ specific Apache HTTPD module config. +* An Identity Provider (IdP) entity descriptor XML file, which describes the connection to {{book.project.name}} or another SAML IdP +* An SP entity descriptor XML file, which describes the SAML connections and configuration for the application you are securing. +* A private key PEM file, which is a text file in the PEM format that defines the private key the application uses to sign documents. +* A certificate PEM file, which is a text file that defines the certificate for your application. +* mod_auth_mellon-specific Apache HTTPD module configuration. If you have already defined and registered the client application within a realm on the {{book.project.name}} application server, -{{book.project.name}} can generate all the files you need except the Apache HTTPD module config. -Go to the `Installation` tab of your SAML client and select the `Mod Auth Mellon files` option. +{{book.project.name}} can generate all the files you need except the Apache HTTPD module configuration. +To do this, complete the following steps: +. Go to the *Installation* page of your SAML client and select the *Mod Auth Mellon files* option. ++ .mod_auth_mellon config download image:../../{{book.images}}/mod-auth-mellon-config-download.png[] -Click the `Download` button and you will download a zip file that contains the XML descriptor and pem files you need. +. Click *Download* to download a zip file that contains the XML descriptor and PEM files you need. + +New content start: + +=== Configuring mod_auth_mellon with Red Hat Single Sign-On + +There are two hosts involved: + +*The host on which Red Hat Single Sign-On is running, which will be referred to as $idp_host because Red Hat Single Sign-On is a SAML Identity Provider (IdP). + +*The host on which the web application is running, which will be referred to as $sp_host. In SAML an application using an IdP is called a Service Provider (SP). + +All of the following steps need to performed on $sp_host with root privileges. + +==== Installing the Packages + +You need to install the necessary packages. To do this, you will need: + +* Apache Web Server (httpd) +* Mellon SAML SP add-on module for Apache +* Tools to create X509 certificates + +To install the necessary packages, run this command: +yum install httpd mod_auth_mellon mod_ssl openssl + +==== Creating a Configuration Directory for Apache SAML + +It is advisable to keep configuration files related to Apache's use of SAML in one location. + +To create a new directory saml2 located under the Apache configuration root /etc/httpd: + +mkdir /etc/httpd/saml2 + +==== Configuring the Mellon Service Provider + +Configuration files for Apache add-on modules are located in the directory /etc/httpd/conf.d and have a file name extension of .conf. You need to add the file /etc/httpd/conf.d/mellon.conf and place Mellon's configuration directives in it. + +Mellon's configuration directives can roughly be broken down into two classes of information: + +* Which URLs to protect with SAML authentication +* What SAML parameters will be used when a protected URL is referenced. + +Apache configuration directives typically follow a hierarchical tree structure in the URL space, which are known as locations. You will need to specify one or more URL locations that Mellon protects. You have flexibility in how you add the configuration parameters that apply to each location. You can either add all the necessary parameters to the location block or you can add Mellon parameters to a common location high up in the URL location hierarchy that specific protected locations inherit (or some combination of the two). Since it is common for an SP to operate in the same way no matter which location triggers SAML actions, the example configuration used here places common Mellon configuration directives in the root of the hierarchy and then specific locations to be protected by Mellon can be defined with minimal directives. This strategy avoids duplicating the same parameters for each protected location. + +This example will have just one protected location: https://$sp_host/protected. + +To configure the Mellon service provider, complete the following steps: + +. Create the file /etc/httpd/conf.d/mellon.conf with this content: + + + MellonEnable info + MellonEndpointPath /mellon/ + MellonSPMetadataFile /etc/httpd/saml2/mellon_metadata.xml + MellonSPPrivateKeyFile /etc/httpd/saml2/mellon.key + MellonSPCertFile /etc/httpd/saml2/mellon.crt + MellonIdPMetadataFile /etc/httpd/saml2/idp_metadata.xml + + + + AuthType Mellon + MellonEnable auth + Require valid-user + + +Note: Some of the files referenced in the above code are created in later steps. + +==== Creating the Service Provider Metadata + +In SAML IdPs and SPs learn about each other by exchanging SAML metadata. SAML metadata is in XML format. The schema for the metadata is a standard thus assuring participating SAML entities can consume each other's metadata. You need: + +* Metadata for the IdP the SP utilizes +* Metadata describing the SP provided to the IdP + +One of the components of SAML metadata is X509 certificates. These certificates are used for two purposes: + +* Sign SAML messages so the other end can prove the message originated from the expected party. +* Encrypt the message during transport (seldom used because SAM messages typically occur on TLS-protected transports) + +You can use your own certificates if you already have a Certificate Authority (CA) or you can generate a self-signed certificate. For simplicity in this example a self-signed certificate is used. + +Because Mellon's SP metadata must reflect the capabilities of the installed version of mod_auth_mellon, must be valid SP metadata XML, and must contain an X509 certificate (whose creation can be obtuse unless you are familiar with X509 certificate generation) the most expedient way to produce the SP metadata is to use a tool included in the mod_auth_mellon package (mellon_create_metadata.sh). The generated metadata can always be edited later because it is a text file. The tool also creates your X509 key and certificate. + +SAML IdPs and SPs identify themselves using a unique name known as an EntityID. To use the Mellon metadata creation tool you need: + +* The EntityID, which is typically the URL of the SP, and often the URL of the SP where the SP metadata can be retrieved +* The URL where SAML messages for the SP will be consumed, which Mellon calls the MellonEndPointPath. + +The following steps : + +. Create a few helper shell variables +. Invoke the Mellon metadata creation tool +. Move the generated files to their destination (referenced in /etc/httpd/conf.d/mellon.conf created above). + +Do this: + +fqdn=`hostname` +mellon_endpoint_url="https://${fqdn}/mellon" +mellon_entity_id="${mellon_endpoint_url}/metadata" +file_prefix="$(echo "$mellon_entity_id" | sed 's/[^A-Za-z.]/_/g' | sed 's/__*/_/g')" + +/usr/libexec/mod_auth_mellon/mellon_create_metadata.sh $mellon_entity_id $mellon_endpoint_url + +mv ${file_prefix}.cert /etc/httpd/saml2/mellon.crt +mv ${file_prefix}.key /etc/httpd/saml2/mellon.key +mv ${file_prefix}.xml /etc/httpd/saml2/mellon_metadata.xml + +==== Adding the Mellon Service Provider to the Red Hat Single Sign-On Identity Provider + +Assumption: The Red Hat Single Sign-On IdP has already been installed on the $idp_host. + +Red Hat Single Sign-On supports multiple tenancy where all users, clients, and so on are grouped in what is called a realm. Each realm is independent of other realms. You can use an existing realm in your Red Hat Single Sign-On but for this example we will create a new realm called test_realm and utilize that. + +All these operations are performed using the Red Hat Single Sign-On administration web console. You need to know the admin username and password for $idp_host. + +Perform these steps: + +. Open the Admin Console and log on by entering the admin username and password. ++ +After logging into the admin console there will be an existing realm. When Red Hat Single Sign-On is first set up a root realm, master, is created by default. Any previously created realms are listed in the upper left corner of the admin console in a drop-down list. + +. From the realm drop-down list select *Add realm*. + +. In the Name field type `test_realm` and click *Create*. + +==== Adding the Mellon Service Provider as a Client of the Realm + +In Red Hat Single Sign-On SAML SPs are known as clients. To add the SP we must be in the Clients section of the realm. + +. Click the Clients menu item on the left and click *Create* in the upper right corner to create a new client. + +Add the Mellon SP Client + +You will need to: + +. Set the client protocol to SAML by selecting *saml* from the Client Protocol drop down list. +. Provide the Mellon SP metadata file we created above (/etc/httpd/saml2/mellon_metadata.xml). Depending on where you are running your browser from you might have to copy the SP metadata from from $sp_host to the machine you're running your browser on so the browser can find the file. +. Click *Save*. + +Edit the Client + +There are several client configuration parameters we suggest setting: + +* Make sure "Force POST Binding" is On +* Add paosResponse to the Valid Redirect URIs list: +. Copy the postResponse URL in "Valid Redirect URIs" and paste it into the empty add text fields just below the "+". +. Change "postResponse" to "paosResponse". (The paosResponse URL is needed for SAML ECP.) +. Click *Save* at the bottom. + +Many SAML SPs determine authorization based on a user's membership in a group. The Red Hat Single Sign-On IdP can manage user group information but it won't supply the user's groups unless the IdP is configured to supply it as a SAML attribute. + +To configure the IdP to supply the user's groups as as a SAML attribute, complete the following steps: + +. Click the Mappers tab of the client. +. In the upper right corner of the Mappers page, click *Create*. +. From the Mapper Type drop-down list select *Group list*. +. Set Name to "group list." +. Set the SAML attribute name to "groups." +. Click *Save.* + +The remaining steps are performed on $sp_host. + +==== Retrieving the Identity Provider Metadata + +Now that you have created the realm on the IdP you need to retrieve the IdP metadata associated with it so the Mellon SP knows about it. In the /etc/httpd/conf.d/mellon.conf file created previously, the MellonIdPMetadataFile is specified as /etc/httpd/saml2/idp_metadata.xml but until now that file has not existed on $sp_host. To get that file we will retrieve it from the IdP. + +. Do this by substituting $idp_host with the correct value: +curl -k -o /etc/httpd/saml2/idp_metadata.xml \ +https://$idp_host/auth/realms/test_realm/protocol/saml/descriptor ++ +Mellon is now fully configured. + +. To run a syntax check for Apache configuration files: +apachectl configtest ++ +Note: configtest is equivalent to the -t argument to apachectl. If the configuration test shows any errors, correct them before proceeding. + +. Restart the Apache server: +systemctl restart httpd.service + +You've now setup both Red Hat Single Sign-On as a SAML IdP in the test_realm and mod_auth_mellon as SAML SP protecting the URL $sp_host/protected (and everything beneath it) by authenticating against the $``$idp_host`` IdP. From 70b815cee567467c524fe9b30fa054e5ad5d0b7c Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Fri, 3 Feb 2017 18:34:01 -0500 Subject: [PATCH 192/194] removed upgrading EAP adapters content from Securing Apps Guide for addition to Upgrade Guide --- topics/oidc/java/jboss-adapter.adoc | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/topics/oidc/java/jboss-adapter.adoc b/topics/oidc/java/jboss-adapter.adoc index 9af33b2208..7b4ce16f51 100644 --- a/topics/oidc/java/jboss-adapter.adoc +++ b/topics/oidc/java/jboss-adapter.adoc @@ -105,26 +105,13 @@ Alternatively, if the server is running execute: $ ./bin/jboss-cli.sh --file=adapter-install.cli ---- -[[_jboss_adapter_upgrading]] -===== Upgrading the adapter - -It is important that you upgrade the ${book.project.name}} server first then the adapters. This is because older adapters -will work with the newer server, but not always the other way around. - -The steps to upgrade the adapter is slightly different to the steps to install the adapter. - -First step is to delete the old adapter modules. Do this be deleting the directory `modules/system/add-ons/keycloak/`. - -Next follow the steps in the <<_jboss_adapter_installation,Installing the adapter>> to unzip the adapter. Since the -adapter subsystem has already been configured skip that step. - ===== Required Per WAR Configuration -This section describes how to secure a WAR directly by adding config and editing files within your WAR package. +This section describes how to secure a WAR directly by adding configuration and editing files within your WAR package. -The first thing you must do is create a `keycloak.json` adapter config file within the `WEB-INF` directory of your WAR. +The first thing you must do is create a `keycloak.json` adapter configuration file within the `WEB-INF` directory of your WAR. -The format of this config file is describe in the <> section. +The format of this configuration file is described in the <> section. Next you must set the `auth-method` to `KEYCLOAK` in `web.xml`. You also have to use standard servlet security to specify role-base constraints on your URLs. @@ -244,7 +231,7 @@ If you have multiple deployments secured by the same realm you can share the rea ===== Security Domain -To propogate the security context to the EJB tier you need to configure it to use the "keycloak" security domain. This +To propagate the security context to the EJB tier you need to configure it to use the "keycloak" security domain. This can be achieved with the @SecurityDomain annotation: [source] From 5dd733937825b1c4cc2dc411608c24bf1fc20f55 Mon Sep 17 00:00:00 2001 From: Jen Malloy Date: Thu, 9 Feb 2017 16:21:20 -0500 Subject: [PATCH 193/194] further edits to mod_auth_mellon chapter, which is now ready for SME review --- topics/saml/mod-auth-mellon.adoc | 93 ++++++++++++++++---------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/topics/saml/mod-auth-mellon.adoc b/topics/saml/mod-auth-mellon.adoc index 1c4945a4e0..1ac3812d07 100644 --- a/topics/saml/mod-auth-mellon.adoc +++ b/topics/saml/mod-auth-mellon.adoc @@ -2,7 +2,7 @@ === mod_auth_mellon Apache HTTPD Module -The https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD as a proxy, then you can use mod_auth_mellon to secure your web application with SAML. For more details on configuration of this adapter see the _mod_auth_mellon_ Github repo. +The https://github.com/UNINETT/mod_auth_mellon[mod_auth_mellon] module is an Apache HTTPD plugin for SAML. If your language/environment supports using Apache HTTPD as a proxy, then you can use mod_auth_mellon to secure your web application with SAML. For more details on this module see the _mod_auth_mellon_ Github repo. To configure mod_auth_mellon you'll need: @@ -14,7 +14,7 @@ To configure mod_auth_mellon you'll need: If you have already defined and registered the client application within a realm on the {{book.project.name}} application server, {{book.project.name}} can generate all the files you need except the Apache HTTPD module configuration. -To do this, complete the following steps: +To generate the Apache HTTPD module configuration, complete the following steps: . Go to the *Installation* page of your SAML client and select the *Mod Auth Mellon files* option. + @@ -23,49 +23,50 @@ image:../../{{book.images}}/mod-auth-mellon-config-download.png[] . Click *Download* to download a zip file that contains the XML descriptor and PEM files you need. -New content start: + -=== Configuring mod_auth_mellon with Red Hat Single Sign-On +==== Configuring mod_auth_mellon with Red Hat Single Sign-On There are two hosts involved: -*The host on which Red Hat Single Sign-On is running, which will be referred to as $idp_host because Red Hat Single Sign-On is a SAML Identity Provider (IdP). +*The host on which Red Hat Single Sign-On is running, which will be referred to as $idp_host because Red Hat Single Sign-On is a SAML identity provider (IdP). -*The host on which the web application is running, which will be referred to as $sp_host. In SAML an application using an IdP is called a Service Provider (SP). +*The host on which the web application is running, which will be referred to as $sp_host. In SAML an application using an IdP is called a service provider (SP). All of the following steps need to performed on $sp_host with root privileges. -==== Installing the Packages +===== Installing the Packages -You need to install the necessary packages. To do this, you will need: +To install the necessary packages, you will need: * Apache Web Server (httpd) * Mellon SAML SP add-on module for Apache * Tools to create X509 certificates To install the necessary packages, run this command: -yum install httpd mod_auth_mellon mod_ssl openssl -==== Creating a Configuration Directory for Apache SAML +`yum install httpd mod_auth_mellon mod_ssl openssl` + +===== Creating a Configuration Directory for Apache SAML It is advisable to keep configuration files related to Apache's use of SAML in one location. -To create a new directory saml2 located under the Apache configuration root /etc/httpd: +Create a new directory named saml2 located under the Apache configuration root /etc/httpd: -mkdir /etc/httpd/saml2 +`mkdir /etc/httpd/saml2` -==== Configuring the Mellon Service Provider +===== Configuring the Mellon Service Provider -Configuration files for Apache add-on modules are located in the directory /etc/httpd/conf.d and have a file name extension of .conf. You need to add the file /etc/httpd/conf.d/mellon.conf and place Mellon's configuration directives in it. +Configuration files for Apache add-on modules are located in the /etc/httpd/conf.d directory and have a file name extension of .conf. You need to create the /etc/httpd/conf.d/mellon.conf file and place Mellon's configuration directives in it. Mellon's configuration directives can roughly be broken down into two classes of information: * Which URLs to protect with SAML authentication * What SAML parameters will be used when a protected URL is referenced. -Apache configuration directives typically follow a hierarchical tree structure in the URL space, which are known as locations. You will need to specify one or more URL locations that Mellon protects. You have flexibility in how you add the configuration parameters that apply to each location. You can either add all the necessary parameters to the location block or you can add Mellon parameters to a common location high up in the URL location hierarchy that specific protected locations inherit (or some combination of the two). Since it is common for an SP to operate in the same way no matter which location triggers SAML actions, the example configuration used here places common Mellon configuration directives in the root of the hierarchy and then specific locations to be protected by Mellon can be defined with minimal directives. This strategy avoids duplicating the same parameters for each protected location. +Apache configuration directives typically follow a hierarchical tree structure in the URL space, which are known as locations. You need to specify one or more URL locations for Mellon to protect. You have flexibility in how you add the configuration parameters that apply to each location. You can either add all the necessary parameters to the location block or you can add Mellon parameters to a common location high up in the URL location hierarchy that specific protected locations inherit (or some combination of the two). Since it is common for an SP to operate in the same way no matter which location triggers SAML actions, the example configuration used here places common Mellon configuration directives in the root of the hierarchy and then specific locations to be protected by Mellon can be defined with minimal directives. This strategy avoids duplicating the same parameters for each protected location. -This example will have just one protected location: https://$sp_host/protected. +This example has just one protected location: https://$sp_host/protected. To configure the Mellon service provider, complete the following steps: @@ -86,19 +87,19 @@ To configure the Mellon service provider, complete the following steps: Require valid-user -Note: Some of the files referenced in the above code are created in later steps. +Note: Some of the files referenced in the code above are created in later steps. -==== Creating the Service Provider Metadata +===== Creating the Service Provider Metadata -In SAML IdPs and SPs learn about each other by exchanging SAML metadata. SAML metadata is in XML format. The schema for the metadata is a standard thus assuring participating SAML entities can consume each other's metadata. You need: +In SAML IdPs and SPs exchange SAML metadata, which is in XML format. The schema for the metadata is a standard, thus assuring participating SAML entities can consume each other's metadata. You need: -* Metadata for the IdP the SP utilizes +* Metadata for the IdP that the SP utilizes * Metadata describing the SP provided to the IdP One of the components of SAML metadata is X509 certificates. These certificates are used for two purposes: -* Sign SAML messages so the other end can prove the message originated from the expected party. -* Encrypt the message during transport (seldom used because SAM messages typically occur on TLS-protected transports) +* Sign SAML messages so the receiving end can prove the message originated from the expected party. +* Encrypt the message during transport (seldom used because SAML messages typically occur on TLS-protected transports) You can use your own certificates if you already have a Certificate Authority (CA) or you can generate a self-signed certificate. For simplicity in this example a self-signed certificate is used. @@ -109,34 +110,34 @@ SAML IdPs and SPs identify themselves using a unique name known as an EntityID. * The EntityID, which is typically the URL of the SP, and often the URL of the SP where the SP metadata can be retrieved * The URL where SAML messages for the SP will be consumed, which Mellon calls the MellonEndPointPath. -The following steps : - -. Create a few helper shell variables -. Invoke the Mellon metadata creation tool -. Move the generated files to their destination (referenced in /etc/httpd/conf.d/mellon.conf created above). - -Do this: +To create the SP metadata, complete the following steps: +. Create a few helper shell variables: ++ fqdn=`hostname` mellon_endpoint_url="https://${fqdn}/mellon" mellon_entity_id="${mellon_endpoint_url}/metadata" file_prefix="$(echo "$mellon_entity_id" | sed 's/[^A-Za-z.]/_/g' | sed 's/__*/_/g')" -/usr/libexec/mod_auth_mellon/mellon_create_metadata.sh $mellon_entity_id $mellon_endpoint_url +. Invoke the Mellon metadata creation tool by running this command: ++ +`/usr/libexec/mod_auth_mellon/mellon_create_metadata.sh $mellon_entity_id $mellon_endpoint_url` +. Move the generated files to their destination (referenced in the /etc/httpd/conf.d/mellon.conf file created above): ++ mv ${file_prefix}.cert /etc/httpd/saml2/mellon.crt mv ${file_prefix}.key /etc/httpd/saml2/mellon.key mv ${file_prefix}.xml /etc/httpd/saml2/mellon_metadata.xml -==== Adding the Mellon Service Provider to the Red Hat Single Sign-On Identity Provider +===== Adding the Mellon Service Provider to the Red Hat Single Sign-On Identity Provider Assumption: The Red Hat Single Sign-On IdP has already been installed on the $idp_host. -Red Hat Single Sign-On supports multiple tenancy where all users, clients, and so on are grouped in what is called a realm. Each realm is independent of other realms. You can use an existing realm in your Red Hat Single Sign-On but for this example we will create a new realm called test_realm and utilize that. +Red Hat Single Sign-On supports multiple tenancy where all users, clients, and so on are grouped in what is called a realm. Each realm is independent of other realms. You can use an existing realm in your Red Hat Single Sign-On, but this example shows how to create a new realm called test_realm and use that realm. -All these operations are performed using the Red Hat Single Sign-On administration web console. You need to know the admin username and password for $idp_host. +All these operations are performed using the Red Hat Single Sign-On administration web console. You must have the admin username and password for $idp_host. -Perform these steps: +To complete the following steps: . Open the Admin Console and log on by entering the admin username and password. + @@ -146,31 +147,31 @@ After logging into the admin console there will be an existing realm. When Red H . In the Name field type `test_realm` and click *Create*. -==== Adding the Mellon Service Provider as a Client of the Realm +====== Adding the Mellon Service Provider as a Client of the Realm In Red Hat Single Sign-On SAML SPs are known as clients. To add the SP we must be in the Clients section of the realm. . Click the Clients menu item on the left and click *Create* in the upper right corner to create a new client. -Add the Mellon SP Client +====== Adding the Mellon SP Client -You will need to: +To add the Mellon SP client, complete the following steps: -. Set the client protocol to SAML by selecting *saml* from the Client Protocol drop down list. -. Provide the Mellon SP metadata file we created above (/etc/httpd/saml2/mellon_metadata.xml). Depending on where you are running your browser from you might have to copy the SP metadata from from $sp_host to the machine you're running your browser on so the browser can find the file. +. Set the client protocol to SAML. From the Client Protocol drop down list, select *saml*. +. Provide the Mellon SP metadata file created above (/etc/httpd/saml2/mellon_metadata.xml). Depending on where your browser is running you might have to copy the SP metadata from $sp_host to the machine on which your browser is running so the browser can find the file. . Click *Save*. -Edit the Client +====== Editing the Mellon SP Client There are several client configuration parameters we suggest setting: -* Make sure "Force POST Binding" is On +* Ensure "Force POST Binding" is On. * Add paosResponse to the Valid Redirect URIs list: . Copy the postResponse URL in "Valid Redirect URIs" and paste it into the empty add text fields just below the "+". . Change "postResponse" to "paosResponse". (The paosResponse URL is needed for SAML ECP.) . Click *Save* at the bottom. -Many SAML SPs determine authorization based on a user's membership in a group. The Red Hat Single Sign-On IdP can manage user group information but it won't supply the user's groups unless the IdP is configured to supply it as a SAML attribute. +Many SAML SPs determine authorization based on a user's membership in a group. The Red Hat Single Sign-On IdP can manage user group information but it does not supply the user's groups unless the IdP is configured to supply it as a SAML attribute. To configure the IdP to supply the user's groups as as a SAML attribute, complete the following steps: @@ -183,11 +184,11 @@ To configure the IdP to supply the user's groups as as a SAML attribute, complet The remaining steps are performed on $sp_host. -==== Retrieving the Identity Provider Metadata +====== Retrieving the Identity Provider Metadata -Now that you have created the realm on the IdP you need to retrieve the IdP metadata associated with it so the Mellon SP knows about it. In the /etc/httpd/conf.d/mellon.conf file created previously, the MellonIdPMetadataFile is specified as /etc/httpd/saml2/idp_metadata.xml but until now that file has not existed on $sp_host. To get that file we will retrieve it from the IdP. +Now that you have created the realm on the IdP you need to retrieve the IdP metadata associated with it so the Mellon SP recognizes it. In the /etc/httpd/conf.d/mellon.conf file created previously, the MellonIdPMetadataFile is specified as /etc/httpd/saml2/idp_metadata.xml but until now that file has not existed on $sp_host. To get that file we will retrieve it from the IdP. -. Do this by substituting $idp_host with the correct value: +. Retrieve the file from the IdP by substituting $idp_host with the correct value: curl -k -o /etc/httpd/saml2/idp_metadata.xml \ https://$idp_host/auth/realms/test_realm/protocol/saml/descriptor + @@ -201,4 +202,4 @@ Note: configtest is equivalent to the -t argument to apachectl. If the configura . Restart the Apache server: systemctl restart httpd.service -You've now setup both Red Hat Single Sign-On as a SAML IdP in the test_realm and mod_auth_mellon as SAML SP protecting the URL $sp_host/protected (and everything beneath it) by authenticating against the $``$idp_host`` IdP. +You have now set up both Red Hat Single Sign-On as a SAML IdP in the test_realm and mod_auth_mellon as SAML SP protecting the URL $sp_host/protected (and everything beneath it) by authenticating against the $``$idp_host`` IdP. From 8a36a4b28aa0638a8a5d0c12948127ff0a7daa2e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 14 Feb 2017 10:05:15 +0100 Subject: [PATCH 194/194] Prepare to move to single repository --- README.adoc | 9 ++------- securing_apps/README.adoc | 9 +++++++++ SUMMARY.adoc => securing_apps/SUMMARY.adoc | 0 .../book-product.json | 0 book.json => securing_apps/book.json | 0 build.sh => securing_apps/build.sh | 0 buildGuide.sh => securing_apps/buildGuide.sh | 0 .../gitlab-conversion.py | 0 {images => securing_apps/images}/keycloak_logo.png | Bin .../internal-resources}/README.adoc | 0 .../internal-resources}/template_concept.adoc | 0 .../internal-resources}/template_reference.adoc | 0 .../internal-resources}/template_task.adoc | 0 .../internal-resources}/template_task_brief.adoc | 0 .../mod-auth-mellon-config-download.png | Bin .../master-docinfo.xml | 0 metadata.ini => securing_apps/metadata.ini | 0 .../mod-auth-mellon-config-download.png | Bin .../topics}/client-registration.adoc | 0 .../client-registration-cli.adoc | 0 .../topics}/oidc/java/adapter-context.adoc | 0 .../topics}/oidc/java/adapter_error_handling.adoc | 0 .../topics}/oidc/java/application-clustering.adoc | 0 .../topics}/oidc/java/client-authentication.adoc | 0 .../topics}/oidc/java/fuse-adapter.adoc | 0 .../topics}/oidc/java/fuse/camel.adoc | 0 .../topics}/oidc/java/fuse/classic-war.adoc | 0 .../topics}/oidc/java/fuse/cxf-builtin.adoc | 0 .../topics}/oidc/java/fuse/cxf-separate.adoc | 0 .../topics}/oidc/java/fuse/fuse-admin.adoc | 0 .../topics}/oidc/java/fuse/hawtio.adoc | 0 .../topics}/oidc/java/fuse/install-feature.adoc | 0 .../topics}/oidc/java/fuse/servlet-whiteboard.adoc | 0 .../topics}/oidc/java/jaas.adoc | 0 .../topics}/oidc/java/java-adapter-config.adoc | 0 .../topics}/oidc/java/java-adapters.adoc | 0 .../topics}/oidc/java/jboss-adapter.adoc | 0 .../topics}/oidc/java/jetty8-adapter.adoc | 0 .../topics}/oidc/java/jetty9-adapter.adoc | 0 .../topics}/oidc/java/logout.adoc | 0 .../topics}/oidc/java/multi-tenancy.adoc | 0 .../topics}/oidc/java/params_forwarding.adoc | 0 .../topics}/oidc/java/servlet-filter-adapter.adoc | 0 .../topics}/oidc/java/spring-boot-adapter.adoc | 0 .../topics}/oidc/java/spring-security-adapter.adoc | 0 .../topics}/oidc/java/tomcat-adapter.adoc | 0 .../topics}/oidc/javascript-adapter.adoc | 0 .../topics}/oidc/mod-auth-openidc.adoc | 0 .../topics}/oidc/nodejs-adapter.adoc | 0 .../topics}/oidc/oidc-generic.adoc | 0 .../topics}/oidc/oidc-overview.adoc | 0 .../topics}/overview/overview.adoc | 0 .../topics}/overview/supported-platforms.adoc | 0 .../topics}/overview/supported-protocols.adoc | 0 .../topics}/overview/what-are-client-adapters.adoc | 0 .../saml/java/MigrationFromOlderVersions.adoc | 0 .../topics}/saml/java/assertion-api.adoc | 0 .../topics}/saml/java/debugging.adoc | 0 .../topics}/saml/java/error_handling.adoc | 0 .../topics}/saml/java/general-config.adoc | 0 .../saml/java/general-config/idp_element.adoc | 0 .../general-config/idp_httpclient_subelement.adoc | 0 .../java/general-config/idp_keys_subelement.adoc | 0 .../idp_singlelogoutservice_subelement.adoc | 0 .../idp_singlesignonservice_subelement.adoc | 0 .../general-config/roleidentifiers_element.adoc | 0 .../topics}/saml/java/general-config/sp-keys.adoc | 0 .../saml/java/general-config/sp-keys/key_pems.adoc | 0 .../general-config/sp-keys/keystore_element.adoc | 0 .../saml/java/general-config/sp_element.adoc | 0 .../sp_principalname_mapping_element.adoc | 0 .../topics}/saml/java/idp-registration.adoc | 0 .../topics}/saml/java/java-adapters.adoc | 0 .../jboss-adapter/jboss_adapter_installation.adoc | 0 .../required_per_war_configuration.adoc | 0 .../saml/java/jboss-adapter/securing_wars.adoc | 0 .../topics}/saml/java/jetty-adapter.adoc | 0 .../java/jetty-adapter/jetty8-installation.adoc | 0 .../java/jetty-adapter/jetty8-per_war_config.adoc | 0 .../java/jetty-adapter/jetty9_installation.adoc | 0 .../java/jetty-adapter/jetty9_per_war_config.adoc | 0 .../topics}/saml/java/logout.adoc | 0 .../topics}/saml/java/saml-jboss-adapter.adoc | 0 .../topics}/saml/java/saml_adapter_overview.adoc | 0 .../topics}/saml/java/servlet-filter-adapter.adoc | 0 .../topics}/saml/java/tomcat-adapter.adoc | 0 .../tomcat-adapter/tomcat_adapter_installation.adoc | 0 .../tomcat_adapter_per_war_config.adoc | 0 .../topics}/saml/mod-auth-mellon.adoc | 0 .../topics}/saml/saml-overview.adoc | 0 90 files changed, 11 insertions(+), 7 deletions(-) create mode 100755 securing_apps/README.adoc rename SUMMARY.adoc => securing_apps/SUMMARY.adoc (100%) rename book-product.json => securing_apps/book-product.json (100%) rename book.json => securing_apps/book.json (100%) rename build.sh => securing_apps/build.sh (100%) rename buildGuide.sh => securing_apps/buildGuide.sh (100%) rename gitlab-conversion.py => securing_apps/gitlab-conversion.py (100%) rename {images => securing_apps/images}/keycloak_logo.png (100%) rename {internal-resources => securing_apps/internal-resources}/README.adoc (100%) rename {internal-resources => securing_apps/internal-resources}/template_concept.adoc (100%) rename {internal-resources => securing_apps/internal-resources}/template_reference.adoc (100%) rename {internal-resources => securing_apps/internal-resources}/template_task.adoc (100%) rename {internal-resources => securing_apps/internal-resources}/template_task_brief.adoc (100%) rename {keycloak-images => securing_apps/keycloak-images}/mod-auth-mellon-config-download.png (100%) rename master-docinfo.xml => securing_apps/master-docinfo.xml (100%) rename metadata.ini => securing_apps/metadata.ini (100%) rename {rhsso-images => securing_apps/rhsso-images}/mod-auth-mellon-config-download.png (100%) rename {topics => securing_apps/topics}/client-registration.adoc (100%) rename {topics => securing_apps/topics}/client-registration/client-registration-cli.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/adapter-context.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/adapter_error_handling.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/application-clustering.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/client-authentication.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/camel.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/classic-war.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/cxf-builtin.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/cxf-separate.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/fuse-admin.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/hawtio.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/install-feature.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/fuse/servlet-whiteboard.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/jaas.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/java-adapter-config.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/java-adapters.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/jboss-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/jetty8-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/jetty9-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/logout.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/multi-tenancy.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/params_forwarding.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/servlet-filter-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/spring-boot-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/spring-security-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/java/tomcat-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/javascript-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/mod-auth-openidc.adoc (100%) rename {topics => securing_apps/topics}/oidc/nodejs-adapter.adoc (100%) rename {topics => securing_apps/topics}/oidc/oidc-generic.adoc (100%) rename {topics => securing_apps/topics}/oidc/oidc-overview.adoc (100%) rename {topics => securing_apps/topics}/overview/overview.adoc (100%) rename {topics => securing_apps/topics}/overview/supported-platforms.adoc (100%) rename {topics => securing_apps/topics}/overview/supported-protocols.adoc (100%) rename {topics => securing_apps/topics}/overview/what-are-client-adapters.adoc (100%) rename {topics => securing_apps/topics}/saml/java/MigrationFromOlderVersions.adoc (100%) rename {topics => securing_apps/topics}/saml/java/assertion-api.adoc (100%) rename {topics => securing_apps/topics}/saml/java/debugging.adoc (100%) rename {topics => securing_apps/topics}/saml/java/error_handling.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/idp_element.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/idp_httpclient_subelement.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/idp_keys_subelement.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/idp_singlelogoutservice_subelement.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/idp_singlesignonservice_subelement.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/roleidentifiers_element.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/sp-keys.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/sp-keys/key_pems.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/sp-keys/keystore_element.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/sp_element.adoc (100%) rename {topics => securing_apps/topics}/saml/java/general-config/sp_principalname_mapping_element.adoc (100%) rename {topics => securing_apps/topics}/saml/java/idp-registration.adoc (100%) rename {topics => securing_apps/topics}/saml/java/java-adapters.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jboss-adapter/jboss_adapter_installation.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jboss-adapter/required_per_war_configuration.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jboss-adapter/securing_wars.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jetty-adapter.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jetty-adapter/jetty8-installation.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jetty-adapter/jetty8-per_war_config.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jetty-adapter/jetty9_installation.adoc (100%) rename {topics => securing_apps/topics}/saml/java/jetty-adapter/jetty9_per_war_config.adoc (100%) rename {topics => securing_apps/topics}/saml/java/logout.adoc (100%) rename {topics => securing_apps/topics}/saml/java/saml-jboss-adapter.adoc (100%) rename {topics => securing_apps/topics}/saml/java/saml_adapter_overview.adoc (100%) rename {topics => securing_apps/topics}/saml/java/servlet-filter-adapter.adoc (100%) rename {topics => securing_apps/topics}/saml/java/tomcat-adapter.adoc (100%) rename {topics => securing_apps/topics}/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc (100%) rename {topics => securing_apps/topics}/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc (100%) rename {topics => securing_apps/topics}/saml/mod-auth-mellon.adoc (100%) rename {topics => securing_apps/topics}/saml/saml-overview.adoc (100%) diff --git a/README.adoc b/README.adoc index 9701180643..c6e1816330 100755 --- a/README.adoc +++ b/README.adoc @@ -1,9 +1,4 @@ -= Securing Applications and Services Guide - -image:images/keycloak_logo.png[alt="Keycloak"] - -{{book.project.name}} {{book.project.version}} - -http://www.keycloak.org += Moved +Moved to https://github.com/keycloak/keycloak-documentation diff --git a/securing_apps/README.adoc b/securing_apps/README.adoc new file mode 100755 index 0000000000..9701180643 --- /dev/null +++ b/securing_apps/README.adoc @@ -0,0 +1,9 @@ + += Securing Applications and Services Guide + +image:images/keycloak_logo.png[alt="Keycloak"] + +{{book.project.name}} {{book.project.version}} + +http://www.keycloak.org + diff --git a/SUMMARY.adoc b/securing_apps/SUMMARY.adoc similarity index 100% rename from SUMMARY.adoc rename to securing_apps/SUMMARY.adoc diff --git a/book-product.json b/securing_apps/book-product.json similarity index 100% rename from book-product.json rename to securing_apps/book-product.json diff --git a/book.json b/securing_apps/book.json similarity index 100% rename from book.json rename to securing_apps/book.json diff --git a/build.sh b/securing_apps/build.sh similarity index 100% rename from build.sh rename to securing_apps/build.sh diff --git a/buildGuide.sh b/securing_apps/buildGuide.sh similarity index 100% rename from buildGuide.sh rename to securing_apps/buildGuide.sh diff --git a/gitlab-conversion.py b/securing_apps/gitlab-conversion.py similarity index 100% rename from gitlab-conversion.py rename to securing_apps/gitlab-conversion.py diff --git a/images/keycloak_logo.png b/securing_apps/images/keycloak_logo.png similarity index 100% rename from images/keycloak_logo.png rename to securing_apps/images/keycloak_logo.png diff --git a/internal-resources/README.adoc b/securing_apps/internal-resources/README.adoc similarity index 100% rename from internal-resources/README.adoc rename to securing_apps/internal-resources/README.adoc diff --git a/internal-resources/template_concept.adoc b/securing_apps/internal-resources/template_concept.adoc similarity index 100% rename from internal-resources/template_concept.adoc rename to securing_apps/internal-resources/template_concept.adoc diff --git a/internal-resources/template_reference.adoc b/securing_apps/internal-resources/template_reference.adoc similarity index 100% rename from internal-resources/template_reference.adoc rename to securing_apps/internal-resources/template_reference.adoc diff --git a/internal-resources/template_task.adoc b/securing_apps/internal-resources/template_task.adoc similarity index 100% rename from internal-resources/template_task.adoc rename to securing_apps/internal-resources/template_task.adoc diff --git a/internal-resources/template_task_brief.adoc b/securing_apps/internal-resources/template_task_brief.adoc similarity index 100% rename from internal-resources/template_task_brief.adoc rename to securing_apps/internal-resources/template_task_brief.adoc diff --git a/keycloak-images/mod-auth-mellon-config-download.png b/securing_apps/keycloak-images/mod-auth-mellon-config-download.png similarity index 100% rename from keycloak-images/mod-auth-mellon-config-download.png rename to securing_apps/keycloak-images/mod-auth-mellon-config-download.png diff --git a/master-docinfo.xml b/securing_apps/master-docinfo.xml similarity index 100% rename from master-docinfo.xml rename to securing_apps/master-docinfo.xml diff --git a/metadata.ini b/securing_apps/metadata.ini similarity index 100% rename from metadata.ini rename to securing_apps/metadata.ini diff --git a/rhsso-images/mod-auth-mellon-config-download.png b/securing_apps/rhsso-images/mod-auth-mellon-config-download.png similarity index 100% rename from rhsso-images/mod-auth-mellon-config-download.png rename to securing_apps/rhsso-images/mod-auth-mellon-config-download.png diff --git a/topics/client-registration.adoc b/securing_apps/topics/client-registration.adoc similarity index 100% rename from topics/client-registration.adoc rename to securing_apps/topics/client-registration.adoc diff --git a/topics/client-registration/client-registration-cli.adoc b/securing_apps/topics/client-registration/client-registration-cli.adoc similarity index 100% rename from topics/client-registration/client-registration-cli.adoc rename to securing_apps/topics/client-registration/client-registration-cli.adoc diff --git a/topics/oidc/java/adapter-context.adoc b/securing_apps/topics/oidc/java/adapter-context.adoc similarity index 100% rename from topics/oidc/java/adapter-context.adoc rename to securing_apps/topics/oidc/java/adapter-context.adoc diff --git a/topics/oidc/java/adapter_error_handling.adoc b/securing_apps/topics/oidc/java/adapter_error_handling.adoc similarity index 100% rename from topics/oidc/java/adapter_error_handling.adoc rename to securing_apps/topics/oidc/java/adapter_error_handling.adoc diff --git a/topics/oidc/java/application-clustering.adoc b/securing_apps/topics/oidc/java/application-clustering.adoc similarity index 100% rename from topics/oidc/java/application-clustering.adoc rename to securing_apps/topics/oidc/java/application-clustering.adoc diff --git a/topics/oidc/java/client-authentication.adoc b/securing_apps/topics/oidc/java/client-authentication.adoc similarity index 100% rename from topics/oidc/java/client-authentication.adoc rename to securing_apps/topics/oidc/java/client-authentication.adoc diff --git a/topics/oidc/java/fuse-adapter.adoc b/securing_apps/topics/oidc/java/fuse-adapter.adoc similarity index 100% rename from topics/oidc/java/fuse-adapter.adoc rename to securing_apps/topics/oidc/java/fuse-adapter.adoc diff --git a/topics/oidc/java/fuse/camel.adoc b/securing_apps/topics/oidc/java/fuse/camel.adoc similarity index 100% rename from topics/oidc/java/fuse/camel.adoc rename to securing_apps/topics/oidc/java/fuse/camel.adoc diff --git a/topics/oidc/java/fuse/classic-war.adoc b/securing_apps/topics/oidc/java/fuse/classic-war.adoc similarity index 100% rename from topics/oidc/java/fuse/classic-war.adoc rename to securing_apps/topics/oidc/java/fuse/classic-war.adoc diff --git a/topics/oidc/java/fuse/cxf-builtin.adoc b/securing_apps/topics/oidc/java/fuse/cxf-builtin.adoc similarity index 100% rename from topics/oidc/java/fuse/cxf-builtin.adoc rename to securing_apps/topics/oidc/java/fuse/cxf-builtin.adoc diff --git a/topics/oidc/java/fuse/cxf-separate.adoc b/securing_apps/topics/oidc/java/fuse/cxf-separate.adoc similarity index 100% rename from topics/oidc/java/fuse/cxf-separate.adoc rename to securing_apps/topics/oidc/java/fuse/cxf-separate.adoc diff --git a/topics/oidc/java/fuse/fuse-admin.adoc b/securing_apps/topics/oidc/java/fuse/fuse-admin.adoc similarity index 100% rename from topics/oidc/java/fuse/fuse-admin.adoc rename to securing_apps/topics/oidc/java/fuse/fuse-admin.adoc diff --git a/topics/oidc/java/fuse/hawtio.adoc b/securing_apps/topics/oidc/java/fuse/hawtio.adoc similarity index 100% rename from topics/oidc/java/fuse/hawtio.adoc rename to securing_apps/topics/oidc/java/fuse/hawtio.adoc diff --git a/topics/oidc/java/fuse/install-feature.adoc b/securing_apps/topics/oidc/java/fuse/install-feature.adoc similarity index 100% rename from topics/oidc/java/fuse/install-feature.adoc rename to securing_apps/topics/oidc/java/fuse/install-feature.adoc diff --git a/topics/oidc/java/fuse/servlet-whiteboard.adoc b/securing_apps/topics/oidc/java/fuse/servlet-whiteboard.adoc similarity index 100% rename from topics/oidc/java/fuse/servlet-whiteboard.adoc rename to securing_apps/topics/oidc/java/fuse/servlet-whiteboard.adoc diff --git a/topics/oidc/java/jaas.adoc b/securing_apps/topics/oidc/java/jaas.adoc similarity index 100% rename from topics/oidc/java/jaas.adoc rename to securing_apps/topics/oidc/java/jaas.adoc diff --git a/topics/oidc/java/java-adapter-config.adoc b/securing_apps/topics/oidc/java/java-adapter-config.adoc similarity index 100% rename from topics/oidc/java/java-adapter-config.adoc rename to securing_apps/topics/oidc/java/java-adapter-config.adoc diff --git a/topics/oidc/java/java-adapters.adoc b/securing_apps/topics/oidc/java/java-adapters.adoc similarity index 100% rename from topics/oidc/java/java-adapters.adoc rename to securing_apps/topics/oidc/java/java-adapters.adoc diff --git a/topics/oidc/java/jboss-adapter.adoc b/securing_apps/topics/oidc/java/jboss-adapter.adoc similarity index 100% rename from topics/oidc/java/jboss-adapter.adoc rename to securing_apps/topics/oidc/java/jboss-adapter.adoc diff --git a/topics/oidc/java/jetty8-adapter.adoc b/securing_apps/topics/oidc/java/jetty8-adapter.adoc similarity index 100% rename from topics/oidc/java/jetty8-adapter.adoc rename to securing_apps/topics/oidc/java/jetty8-adapter.adoc diff --git a/topics/oidc/java/jetty9-adapter.adoc b/securing_apps/topics/oidc/java/jetty9-adapter.adoc similarity index 100% rename from topics/oidc/java/jetty9-adapter.adoc rename to securing_apps/topics/oidc/java/jetty9-adapter.adoc diff --git a/topics/oidc/java/logout.adoc b/securing_apps/topics/oidc/java/logout.adoc similarity index 100% rename from topics/oidc/java/logout.adoc rename to securing_apps/topics/oidc/java/logout.adoc diff --git a/topics/oidc/java/multi-tenancy.adoc b/securing_apps/topics/oidc/java/multi-tenancy.adoc similarity index 100% rename from topics/oidc/java/multi-tenancy.adoc rename to securing_apps/topics/oidc/java/multi-tenancy.adoc diff --git a/topics/oidc/java/params_forwarding.adoc b/securing_apps/topics/oidc/java/params_forwarding.adoc similarity index 100% rename from topics/oidc/java/params_forwarding.adoc rename to securing_apps/topics/oidc/java/params_forwarding.adoc diff --git a/topics/oidc/java/servlet-filter-adapter.adoc b/securing_apps/topics/oidc/java/servlet-filter-adapter.adoc similarity index 100% rename from topics/oidc/java/servlet-filter-adapter.adoc rename to securing_apps/topics/oidc/java/servlet-filter-adapter.adoc diff --git a/topics/oidc/java/spring-boot-adapter.adoc b/securing_apps/topics/oidc/java/spring-boot-adapter.adoc similarity index 100% rename from topics/oidc/java/spring-boot-adapter.adoc rename to securing_apps/topics/oidc/java/spring-boot-adapter.adoc diff --git a/topics/oidc/java/spring-security-adapter.adoc b/securing_apps/topics/oidc/java/spring-security-adapter.adoc similarity index 100% rename from topics/oidc/java/spring-security-adapter.adoc rename to securing_apps/topics/oidc/java/spring-security-adapter.adoc diff --git a/topics/oidc/java/tomcat-adapter.adoc b/securing_apps/topics/oidc/java/tomcat-adapter.adoc similarity index 100% rename from topics/oidc/java/tomcat-adapter.adoc rename to securing_apps/topics/oidc/java/tomcat-adapter.adoc diff --git a/topics/oidc/javascript-adapter.adoc b/securing_apps/topics/oidc/javascript-adapter.adoc similarity index 100% rename from topics/oidc/javascript-adapter.adoc rename to securing_apps/topics/oidc/javascript-adapter.adoc diff --git a/topics/oidc/mod-auth-openidc.adoc b/securing_apps/topics/oidc/mod-auth-openidc.adoc similarity index 100% rename from topics/oidc/mod-auth-openidc.adoc rename to securing_apps/topics/oidc/mod-auth-openidc.adoc diff --git a/topics/oidc/nodejs-adapter.adoc b/securing_apps/topics/oidc/nodejs-adapter.adoc similarity index 100% rename from topics/oidc/nodejs-adapter.adoc rename to securing_apps/topics/oidc/nodejs-adapter.adoc diff --git a/topics/oidc/oidc-generic.adoc b/securing_apps/topics/oidc/oidc-generic.adoc similarity index 100% rename from topics/oidc/oidc-generic.adoc rename to securing_apps/topics/oidc/oidc-generic.adoc diff --git a/topics/oidc/oidc-overview.adoc b/securing_apps/topics/oidc/oidc-overview.adoc similarity index 100% rename from topics/oidc/oidc-overview.adoc rename to securing_apps/topics/oidc/oidc-overview.adoc diff --git a/topics/overview/overview.adoc b/securing_apps/topics/overview/overview.adoc similarity index 100% rename from topics/overview/overview.adoc rename to securing_apps/topics/overview/overview.adoc diff --git a/topics/overview/supported-platforms.adoc b/securing_apps/topics/overview/supported-platforms.adoc similarity index 100% rename from topics/overview/supported-platforms.adoc rename to securing_apps/topics/overview/supported-platforms.adoc diff --git a/topics/overview/supported-protocols.adoc b/securing_apps/topics/overview/supported-protocols.adoc similarity index 100% rename from topics/overview/supported-protocols.adoc rename to securing_apps/topics/overview/supported-protocols.adoc diff --git a/topics/overview/what-are-client-adapters.adoc b/securing_apps/topics/overview/what-are-client-adapters.adoc similarity index 100% rename from topics/overview/what-are-client-adapters.adoc rename to securing_apps/topics/overview/what-are-client-adapters.adoc diff --git a/topics/saml/java/MigrationFromOlderVersions.adoc b/securing_apps/topics/saml/java/MigrationFromOlderVersions.adoc similarity index 100% rename from topics/saml/java/MigrationFromOlderVersions.adoc rename to securing_apps/topics/saml/java/MigrationFromOlderVersions.adoc diff --git a/topics/saml/java/assertion-api.adoc b/securing_apps/topics/saml/java/assertion-api.adoc similarity index 100% rename from topics/saml/java/assertion-api.adoc rename to securing_apps/topics/saml/java/assertion-api.adoc diff --git a/topics/saml/java/debugging.adoc b/securing_apps/topics/saml/java/debugging.adoc similarity index 100% rename from topics/saml/java/debugging.adoc rename to securing_apps/topics/saml/java/debugging.adoc diff --git a/topics/saml/java/error_handling.adoc b/securing_apps/topics/saml/java/error_handling.adoc similarity index 100% rename from topics/saml/java/error_handling.adoc rename to securing_apps/topics/saml/java/error_handling.adoc diff --git a/topics/saml/java/general-config.adoc b/securing_apps/topics/saml/java/general-config.adoc similarity index 100% rename from topics/saml/java/general-config.adoc rename to securing_apps/topics/saml/java/general-config.adoc diff --git a/topics/saml/java/general-config/idp_element.adoc b/securing_apps/topics/saml/java/general-config/idp_element.adoc similarity index 100% rename from topics/saml/java/general-config/idp_element.adoc rename to securing_apps/topics/saml/java/general-config/idp_element.adoc diff --git a/topics/saml/java/general-config/idp_httpclient_subelement.adoc b/securing_apps/topics/saml/java/general-config/idp_httpclient_subelement.adoc similarity index 100% rename from topics/saml/java/general-config/idp_httpclient_subelement.adoc rename to securing_apps/topics/saml/java/general-config/idp_httpclient_subelement.adoc diff --git a/topics/saml/java/general-config/idp_keys_subelement.adoc b/securing_apps/topics/saml/java/general-config/idp_keys_subelement.adoc similarity index 100% rename from topics/saml/java/general-config/idp_keys_subelement.adoc rename to securing_apps/topics/saml/java/general-config/idp_keys_subelement.adoc diff --git a/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc b/securing_apps/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc similarity index 100% rename from topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc rename to securing_apps/topics/saml/java/general-config/idp_singlelogoutservice_subelement.adoc diff --git a/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc b/securing_apps/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc similarity index 100% rename from topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc rename to securing_apps/topics/saml/java/general-config/idp_singlesignonservice_subelement.adoc diff --git a/topics/saml/java/general-config/roleidentifiers_element.adoc b/securing_apps/topics/saml/java/general-config/roleidentifiers_element.adoc similarity index 100% rename from topics/saml/java/general-config/roleidentifiers_element.adoc rename to securing_apps/topics/saml/java/general-config/roleidentifiers_element.adoc diff --git a/topics/saml/java/general-config/sp-keys.adoc b/securing_apps/topics/saml/java/general-config/sp-keys.adoc similarity index 100% rename from topics/saml/java/general-config/sp-keys.adoc rename to securing_apps/topics/saml/java/general-config/sp-keys.adoc diff --git a/topics/saml/java/general-config/sp-keys/key_pems.adoc b/securing_apps/topics/saml/java/general-config/sp-keys/key_pems.adoc similarity index 100% rename from topics/saml/java/general-config/sp-keys/key_pems.adoc rename to securing_apps/topics/saml/java/general-config/sp-keys/key_pems.adoc diff --git a/topics/saml/java/general-config/sp-keys/keystore_element.adoc b/securing_apps/topics/saml/java/general-config/sp-keys/keystore_element.adoc similarity index 100% rename from topics/saml/java/general-config/sp-keys/keystore_element.adoc rename to securing_apps/topics/saml/java/general-config/sp-keys/keystore_element.adoc diff --git a/topics/saml/java/general-config/sp_element.adoc b/securing_apps/topics/saml/java/general-config/sp_element.adoc similarity index 100% rename from topics/saml/java/general-config/sp_element.adoc rename to securing_apps/topics/saml/java/general-config/sp_element.adoc diff --git a/topics/saml/java/general-config/sp_principalname_mapping_element.adoc b/securing_apps/topics/saml/java/general-config/sp_principalname_mapping_element.adoc similarity index 100% rename from topics/saml/java/general-config/sp_principalname_mapping_element.adoc rename to securing_apps/topics/saml/java/general-config/sp_principalname_mapping_element.adoc diff --git a/topics/saml/java/idp-registration.adoc b/securing_apps/topics/saml/java/idp-registration.adoc similarity index 100% rename from topics/saml/java/idp-registration.adoc rename to securing_apps/topics/saml/java/idp-registration.adoc diff --git a/topics/saml/java/java-adapters.adoc b/securing_apps/topics/saml/java/java-adapters.adoc similarity index 100% rename from topics/saml/java/java-adapters.adoc rename to securing_apps/topics/saml/java/java-adapters.adoc diff --git a/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc b/securing_apps/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc similarity index 100% rename from topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc rename to securing_apps/topics/saml/java/jboss-adapter/jboss_adapter_installation.adoc diff --git a/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc b/securing_apps/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc similarity index 100% rename from topics/saml/java/jboss-adapter/required_per_war_configuration.adoc rename to securing_apps/topics/saml/java/jboss-adapter/required_per_war_configuration.adoc diff --git a/topics/saml/java/jboss-adapter/securing_wars.adoc b/securing_apps/topics/saml/java/jboss-adapter/securing_wars.adoc similarity index 100% rename from topics/saml/java/jboss-adapter/securing_wars.adoc rename to securing_apps/topics/saml/java/jboss-adapter/securing_wars.adoc diff --git a/topics/saml/java/jetty-adapter.adoc b/securing_apps/topics/saml/java/jetty-adapter.adoc similarity index 100% rename from topics/saml/java/jetty-adapter.adoc rename to securing_apps/topics/saml/java/jetty-adapter.adoc diff --git a/topics/saml/java/jetty-adapter/jetty8-installation.adoc b/securing_apps/topics/saml/java/jetty-adapter/jetty8-installation.adoc similarity index 100% rename from topics/saml/java/jetty-adapter/jetty8-installation.adoc rename to securing_apps/topics/saml/java/jetty-adapter/jetty8-installation.adoc diff --git a/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc b/securing_apps/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc similarity index 100% rename from topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc rename to securing_apps/topics/saml/java/jetty-adapter/jetty8-per_war_config.adoc diff --git a/topics/saml/java/jetty-adapter/jetty9_installation.adoc b/securing_apps/topics/saml/java/jetty-adapter/jetty9_installation.adoc similarity index 100% rename from topics/saml/java/jetty-adapter/jetty9_installation.adoc rename to securing_apps/topics/saml/java/jetty-adapter/jetty9_installation.adoc diff --git a/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc b/securing_apps/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc similarity index 100% rename from topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc rename to securing_apps/topics/saml/java/jetty-adapter/jetty9_per_war_config.adoc diff --git a/topics/saml/java/logout.adoc b/securing_apps/topics/saml/java/logout.adoc similarity index 100% rename from topics/saml/java/logout.adoc rename to securing_apps/topics/saml/java/logout.adoc diff --git a/topics/saml/java/saml-jboss-adapter.adoc b/securing_apps/topics/saml/java/saml-jboss-adapter.adoc similarity index 100% rename from topics/saml/java/saml-jboss-adapter.adoc rename to securing_apps/topics/saml/java/saml-jboss-adapter.adoc diff --git a/topics/saml/java/saml_adapter_overview.adoc b/securing_apps/topics/saml/java/saml_adapter_overview.adoc similarity index 100% rename from topics/saml/java/saml_adapter_overview.adoc rename to securing_apps/topics/saml/java/saml_adapter_overview.adoc diff --git a/topics/saml/java/servlet-filter-adapter.adoc b/securing_apps/topics/saml/java/servlet-filter-adapter.adoc similarity index 100% rename from topics/saml/java/servlet-filter-adapter.adoc rename to securing_apps/topics/saml/java/servlet-filter-adapter.adoc diff --git a/topics/saml/java/tomcat-adapter.adoc b/securing_apps/topics/saml/java/tomcat-adapter.adoc similarity index 100% rename from topics/saml/java/tomcat-adapter.adoc rename to securing_apps/topics/saml/java/tomcat-adapter.adoc diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc b/securing_apps/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc similarity index 100% rename from topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc rename to securing_apps/topics/saml/java/tomcat-adapter/tomcat_adapter_installation.adoc diff --git a/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc b/securing_apps/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc similarity index 100% rename from topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc rename to securing_apps/topics/saml/java/tomcat-adapter/tomcat_adapter_per_war_config.adoc diff --git a/topics/saml/mod-auth-mellon.adoc b/securing_apps/topics/saml/mod-auth-mellon.adoc similarity index 100% rename from topics/saml/mod-auth-mellon.adoc rename to securing_apps/topics/saml/mod-auth-mellon.adoc diff --git a/topics/saml/saml-overview.adoc b/securing_apps/topics/saml/saml-overview.adoc similarity index 100% rename from topics/saml/saml-overview.adoc rename to securing_apps/topics/saml/saml-overview.adoc