(Quick Reference)

9 Authentication - Reference Documentation

Authors: Burt Beckwith, Beverley Talbott

Version: 1.2.7.3

9 Authentication

The Spring Security plugin supports several approaches to authentication.

The default approach stores users and roles in your database, and uses an HTML login form which prompts the user for a username and password. The plugin also supports other approaches as described in the sections below, as well as add-on plugins that provide external authentication providers such as OpenID, LDAP, and single sign-on using CAS

9.1 Basic and Digest Authentication

To use HTTP Basic Authentication in your application, set the useBasicAuth attribute to true. Also change the basic.realmName default value to one that suits your application, for example:

grails.plugins.springsecurity.useBasicAuth = true
grails.plugins.springsecurity.basic.realmName = "Ralph's Bait and Tackle"

PropertyDefaultDescription
useBasicAuthfalseWhether to use basic authentication.
basic.realmName'Grails Realm'Realm name displayed in the browser authentication popup.

With this authentication in place, users are prompted with the standard browser login dialog instead of being redirected to a login page.

If you don't want all of your URLs guarded by Basic Auth, you can partition the URL patterns and apply Basic Auth to some, but regular form login to others. For example, if you have a web service that uses Basic Auth for /webservice/** URLs, you would configure that using the chainMap config attribute:

grails.plugins.springsecurity.filterChain.chainMap = [
   '/webservice/**': 'JOINED_FILTERS,-exceptionTranslationFilter',
   '/**': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]

In this example we're using the JOINED_FILTERS keyword instead of explicitly listing the filter names. Specifying JOINED_FILTERS means to use all of the filters that were configured using the various config options. In each case we also specify that we want to exclude one or more filters by prefixing their names with -.

For the /webservice/** URLs, we want all filters except for the standard ExceptionTranslationFilter since we want to use just the one configured for Basic Auth. And for the /** URLs (everything else) we want everything except for the Basic Auth filter and its configured ExceptionTranslationFilter.

Digest Authentication is similar to Basic but is more secure because it does not send your password in obfuscated cleartext. Digest resembles Basic in practice - you get the same browser popup dialog when you authenticate. But because the credential transfer is genuinely encrypted (instead of just Base64-encoded as with Basic authentication) you do not need SSL to guard your logins.

PropertyDefault ValueMeaning
useDigestAuthfalseWhether to use Digest authentication.
digest.realmName'Grails Realm'Realm name displayed in the browser popup
digest.key'changeme'Key used to build the nonce for authentication; it should be changed but that's not required.
digest.nonceValiditySeconds300How long a nonce stays valid.
digest.passwordAlreadyEncodedfalseWhether you are managing the password encryption yourself.
digest.createAuthenticatedTokenfalseIf true, creates an authenticated UsernamePasswordAuthenticationToken to avoid loading the user from the database twice. However, this process skips the isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() checks, so it is not advised.
digest.useCleartextPasswordsfalseIf true, a cleartext password encoder is used (not recommended). If false, passwords encrypted by DigestAuthPasswordEncoder are stored in the database.

Digest authentication has a problem in that by default you store cleartext passwords in your database. This is because the browser encrypts your password along with the username and Realm name, and this is compared to the password encrypted using the same algorithm during authentication. The browser does not know about your MessageDigest algorithm or salt source, so to encrypt them the same way you need to load a cleartext password from the database.

The plugin does provide an alternative, although it has no configuration options (in particular the digest algorithm cannot be changed). If digest.useCleartextPasswords is false (the default), then the passwordEncoder bean is replaced with an instance of grails.plugins.springsecurity.DigestAuthPasswordEncoder. This encoder uses the same approach as the browser, that is, it combines your password along with your username and Realm name essentially as a salt, and encrypts with MD5. MD5 is not recommended in general, but given the typical size of the salt it is reasonably safe to use.

The only required attribute is useDigestAuth, which you must set to true, but you probably also want to change the realm name:

grails.plugins.springsecurity.useDigestAuth = true
grails.plugins.springsecurity.digest.realmName = "Ralph's Bait and Tackle"

Digest authentication cannot be applied to a subset of URLs like Basic authentication can. This is due to the password encoding issues. So you cannot use the chainMap attribute here - all URLs will be guarded.

9.2 Certificate (X509) Login Authentication

Another authentication mechanism supported by Spring Security is certificate-based, or "mutual authentication". It requires HTTPS, and you must configure the server to require a client certificate (ordinarily only the server provides a certificate). Your username is extracted from the client certificate if it is valid, and you are "pre-authenticated". As long as a corresponding username exists in the database, your authentication succeeds and you are not asked for a password. Your Authentication contains the authorities associated with your username.

The table describes available configuration options.

PropertyDefault ValueMeaning
useX509falseWhether to support certificate-based logins
x509.continueFilterChainOn UnsuccessfulAuthenticationtrueWhether to proceed when an authentication attempt fails to allow other authentication mechanisms to process the request.
x509.subjectDnRegex'CN=(.*?),'Regular expression (regex) for extracting the username from the certificate's subject name.
x509.checkForPrincipalChangesfalseWhether to re-extract the username from the certificate and check that it's still the current user when a valid Authentication already exists.
x509.invalidateSessionOn PrincipalChangetrueWhether to invalidate the session if the principal changed (based on a checkForPrincipalChanges check).

The details of configuring your server for SSL and configuring browser certificates are beyond the scope of this document. If you use Tomcat, see its SSL documentation. To get a test environment working, see the instructions in this discussion at Stack Overflow.

Spring Security supports creating a remember-me cookie so that users are not required to log in with a username and password for each session. This is optional and is usually implemented as a checkbox on the login form; the default auth.gsp supplied by the plugin has this feature.

PropertyDefault ValueMeaning
rememberMe.cookieName'grails_remember_me'remember-me cookie name; should be unique per application.
rememberMe. alwaysRememberfalseIf true, create a remember-me cookie even if no checkbox is on the form.
rememberMe. tokenValiditySeconds1209600 (14 days)Max age of the cookie in seconds.
rememberMe.parameter'_spring_security_remember_me'Login form remember-me checkbox name.
rememberMe.key'grailsRocks'Value used to encode cookies; should be unique per application.
rememberMe.useSecureCookiefalseWhether to use a secure cookie or not
rememberMe.persistentfalseIf true, stores persistent login information in the database.
rememberMe.persistentToken. domainClassName'PersistentLogin'Domain class used to manage persistent logins.
rememberMe.persistentToken. seriesLength16Number of characters in the cookie's series attribute.
rememberMe.persistentToken. tokenLength16Number of characters in the cookie's token attribute.
atr.rememberMeClassRememberMeAuthenticationTokenremember-me authentication class.

You are most likely to change these attributes:

  • rememberMe.cookieName. Purely aesthetic as most users will not look at their cookies, but you probably want the display name to be application-specific rather than "grails_remember_me".
  • rememberMe.key. Part of a salt when the cookie is encrypted. Changing the default makes it harder to execute brute-force attacks.
  • rememberMe.tokenValiditySeconds. Default is two weeks; set it to what makes sense for your application.

Persistent Logins

The remember-me cookie is very secure, but for an even stronger solution you can use persistent logins that store the username in the database. See the Spring Security docs for a description of the implementation.

Persistent login is also useful for authentication schemes like OpenID and Facebook, where you do not manage passwords in your database, but most of the other user information is stored locally. Without a password you cannot use the standard cookie format, so persistent logins enable remember-me cookies in these scenarios.

To use this feature, run the s2-create-persistent-token script. This will create the domain class, and register its name in grails-app/conf/Config.groovy. It will also enable persistent logins by setting rememberMe.persistent to true.

9.4 Ajax Authentication

The typical pattern of using web site authentication to access restricted pages involves intercepting access requests for secure pages, redirecting to a login page (possibly off-site, for example when using OpenID or a Single Sign-on implementation such as CAS), and redirecting back to the originally-requested page after a successful login. Each page can also have a login link to allow explicit logins at any time.

Another option is to also have a login link on each page and to use Ajax and DHTML to present a login form within the current page in a popup. The form submits the authentication request through Ajax and displays success or error messages as appropriate.

The plugin supports Ajax logins, but you need to create your own GSP code. There are only a few necessary changes, and of course the sample code here is pretty basic so you should enhance it for your needs.

The approach here involves editing your template page(s) to show "You're logged in as ..." text if logged in and a login link if not, along with a hidden login form that is shown using DHTML.

Here's the updated grails-app/views/layouts/main.gsp:

<html>

<head> <title><g:layoutTitle default="Grails" /></title> <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" /> <link rel="shortcut icon" type="image/x-icon" href="${resource(dir:'images',file:'favicon.ico')}" /> <g:layoutHead /> </head>

<body>

<div id="spinner" class="spinner" style="display:none;"> <img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" /> </div>

<div id="grailsLogo" class="logo"> <a href="http://grails.org"> <img src="${resource(dir:'images',file:'grails_logo.png')}" alt="Grails" border="0" /> </a>

<span id='loginLink' style='position: relative; margin-right: 30px; float: right'> <sec:ifLoggedIn> Logged in as <sec:username/> (<g:link controller='logout'>Logout</g:link>) </sec:ifLoggedIn> <sec:ifNotLoggedIn> <a href='#' onclick='showLogin(); return false;'>Login</a> </sec:ifNotLoggedIn> </span>

</div>

<g:javascript src='application.js' /> <g:javascript library='prototype' /> <g:javascript src='prototype/scriptaculous.js?load=effects' />

<g:render template='/includes/ajaxLogin'/>

<g:layoutBody />

</body> </html>

Note these changes:

  • The prototype and scriptaculous libraries are included for Ajax support and to hide and show the login form.
  • There is an include of the template /includes/ajaxLogin (see the code below).
  • There is a <span> positioned in the top-right that shows the username and a logout link when logged in, and a login link otherwise.

Here is the content of the login form template (grails-app/views/includes/_ajaxLogin.gsp). The CSS and Javascript are shown inline, but you should extract them to their own static files.

<style>
#ajaxLogin {
   margin: 15px 0px; padding: 0px;
   text-align: center;
   display: none;
   position: absolute;
}
#ajaxLogin .inner {
   width: 260px;
   margin:0px auto;
   text-align:left;
   padding:10px;
   border-top:1px dashed #499ede;
   border-bottom:1px dashed #499ede;
   background-color:#EEF;
}
#ajaxLogin .inner .fheader {
   padding:4px;margin:3px 0px 3px 0;color:#2e3741;font-size:14px;font-weight:bold;
}
#ajaxLogin .inner .cssform p {
   clear: left;
   margin: 0;
   padding: 5px 0 8px 0;
   padding-left: 105px;
   border-top: 1px dashed gray;
   margin-bottom: 10px;
   height: 1%;
}
#ajaxLogin .inner .cssform input[type='text'] {
   width: 120px;
}
#ajaxLogin .inner .cssform label{
   font-weight: bold;
   float: left;
   margin-left: -105px;
   width: 100px;
}
#ajaxLogin .inner .login_message {color:red;}
#ajaxLogin .inner .text_ {width:120px;}
#ajaxLogin .inner .chk {height:12px;}
.errorMessage { color: red; }
</style>

<div id='ajaxLogin'> <div class='inner'> <div class='fheader'>Please Login..</div> <form action='${request.contextPath}/j_spring_security_check' method='POST' id='ajaxLoginForm' name='ajaxLoginForm' class='cssform'> <p> <label for='username'>Login ID</label> <input type='text' class='text_' name='j_username' id='username' /> </p> <p> <label for='password'>Password</label> <input type='password' class='text_' name='j_password' id='password' /> </p> <p> <label for='remember_me'>Remember me</label> <input type='checkbox' class='chk' id='remember_me' name='_spring_security_remember_me'/> </p> <p> <a href='javascript:void(0)' onclick='authAjax(); return false;'>Login</a> <a href='javascript:void(0)' onclick='cancelLogin(); return false;'>Cancel</a> </p> </form> <div style='display: none; text-align: left;' id='loginMessage'></div> </div> </div>

<script type='text/javascript'>

// center the form Event.observe(window, 'load', function() { var ajaxLogin = $('ajaxLogin'); $('ajaxLogin').style.left = ((document.body.getDimensions().width - ajaxLogin.getDimensions().width) / 2) + 'px'; $('ajaxLogin').style.top = ((document.body.getDimensions().height - ajaxLogin.getDimensions().height) / 2) + 'px'; });

function showLogin() { $('ajaxLogin').style.display = 'block'; }

function cancelLogin() { Form.enable(document.ajaxLoginForm); Element.hide('ajaxLogin'); }

function authAjax() { Form.enable(document.ajaxLoginForm); Element.update('loginMessage', 'Sending request ...'); Element.show('loginMessage');

var form = document.ajaxLoginForm; var params = Form.serialize(form); Form.disable(form); new Ajax.Request(form.action, { method: 'POST', postBody: params, onSuccess: function(response) { Form.enable(document.ajaxLoginForm); var responseText = response.responseText || '[]'; var json = responseText.evalJSON(); if (json.success) { Element.hide('ajaxLogin'); $('loginLink').update('Logged in as ' + json.username + ' (<%=link(controller: 'logout') { 'Logout' }%>)'); } else if (json.error) { Element.update('loginMessage', "<span class='errorMessage'>" + json.error + '</error>'); } else { Element.update('loginMessage', responseText); } } }); } </script>

The important aspects of this code are:

  • The form posts to the same URL as the regular form, j_spring_security_check. In fact, the form is identical, including the remember-me checkbox, except that the submit button is replaced with a hyperlink.
  • Error messages are displayed within the popup <div>.
  • Because there is no page redirect after successful login, the Javascript replaces the login link to give a visual indication that the user is logged in.
  • Details of logout are not shown; you do this by redirecting the user to /j_spring_security_logout.

How Does Ajax login Work?

Most Ajax libraries (Prototype, JQuery, and Dojo as of v2.1) include an X-Requested-With header that indicates that the request was made by XMLHttpRequest instead of being triggered by clicking a regular hyperlink or form submit button. The plugin uses this header to detect Ajax login requests, and uses subclasses of some of Spring Security's classes to use different redirect urls for Ajax requests than regular requests. Instead of showing full pages, LoginController has JSON-generating methods ajaxSuccess(), ajaxDenied(), and authfail() that generate JSON that the login Javascript code can use to appropriately display success or error messages.

You can see the Ajax-aware actions in LoginController, specifically ajaxSuccess and ajaxDenied, which send JSON responses that can be used by client JavaScript code. Also authfail will check whether the authentication request used Ajax and will render a JSON error response if it did.

To summarize, the typical flow would be

  • click the link to display the login form
  • enter authentication details and click login
  • the form is submitted using an Ajax request
  • if the authentication succeeds:
    • a redirect to /login/ajaxSuccess occurs (this URL is configurable)
    • the rendered response is JSON and it contains two values, a boolean value success with the value true and a string value username with the authenticated user's login name
    • the client determines that the login was successful and updates the page to indicate the the user is logged in; this is necessary since there's no page redirect like there would be for a non-Ajax login
  • if the authentication fails:
    • a redirect to /login/authfail?ajax=true occurs (this URL is configurable)
    • the rendered response is JSON and it contains one value, a string value error with the displayable error message; this will be different depending on why the login was unsuccessful (bad username or password, account locked, etc.)
    • the client determines that the login was not successful and displays the error message
  • note that both a successful and an unsuccessful login will trigger the onSuccess Ajax callback; the onError callback will only be triggered if there's an exception or network issue

Triggering an Ajax login

So far we've discussed explicit Ajax logins where the user can view some of the site's pages but you've added a link to an in-page login form. An attempt to load a secure page will trigger a redirect to the standard login page. But if you're using Ajax in your pages you should handle the case where the request is secure and requires being logged in. This will also handle session timeouts where the user doesn't have a remember-me cookie; you can pop up a login dialog in the page.

For example consider this Ajax form:

<g:form action="ajaxAdd">
   <g:textArea id='postContent' name="content"
               rows="3" cols="50" onkeydown="updateCounter()" />
   <br/>
   <g:submitToRemote value="Post"
      url="[controller: 'post', action: 'addPostAjax']"
      update="[success: 'firstPost']"
      onSuccess="clearPost(e)"
      onLoading="showSpinner(true)"
      onComplete="showSpinner(false)"
      on401="showLogin();"/>
      <img id="spinner" style="display: none"
           src="<g:createLinkTo dir='/images' file='spinner.gif'/>"
   />
</g:form>

Most of the attributes are typical, but the on401 attribute is the key to making Ajax logins work. As long as the LoginController sends a 401 error code the need to authenticate can be easily handled.

Note that depending on the version of the plugin that you're using, you may need to add the authAjax method to your LoginController:

def authAjax = {
   response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
   response.sendError HttpServletResponse.SC_UNAUTHORIZED
}

and this requires an import for javax.servlet.http.HttpServletResponse.