Securing web applications - Spring on the web - Spring in Action, 4th Edition: Covers Spring 4 (2015)

Spring in Action, 4th Edition: Covers Spring 4 (2015)

Part 2. Spring on the web

Chapter 9. Securing web applications

This chapter covers

· Introducing Spring Security

· Securing web applications using servlet filters

· Authentication against databases and LDAP

Have you ever noticed that most people in television sitcoms don’t lock their doors? It happens all the time. On Seinfeld, Kramer frequently let himself into Jerry’s apartment to help himself to the goodies in Jerry’s refrigerator. On Friends, the various characters often entered one another’s apartments without warning or hesitation. Once, while in London, Ross even burst into Chandler’s hotel room, narrowly missing Chandler in a compromising situation with Ross’s sister.

In the days of Leave it to Beaver, it wasn’t so unusual for people to leave their doors unlocked. But it seems crazy that in a day when we’re concerned with privacy and security, we see television characters enabling unhindered access to their apartments and homes.

Information is probably the most valuable item we now have; crooks are looking for ways to steal our data and identities by sneaking into unsecured applications. As software developers, we must take steps to protect the information that resides in our applications. Whether it’s an email account protected with a username/password pair or a brokerage account protected with a trading PIN, security is a crucial aspect of most applications.

It’s no accident that I chose to describe application security with the word “aspect.” Security is a concern that transcends an application’s functionality. For the most part, an application should play no part in securing itself. Although you could write security functionality directly into your application’s code (and that’s not uncommon), it’s better to keep security concerns separate from application concerns.

If you’re thinking that it’s starting to sound as if security is accomplished using aspect-oriented techniques, you’re right. In this chapter we’re going to explore ways to secure your applications with aspects. But you won’t have to develop those aspects yourself—we’re going to look at Spring Security, a security framework implemented with Spring AOP and servlet filters.

9.1. Getting started with Spring Security

Spring Security is a security framework that provides declarative security for your Spring-based applications. Spring Security provides a comprehensive security solution, handling authentication and authorization at both the web request level and at the method invocation level. Based on the Spring Framework, Spring Security takes full advantage of dependency injection (DI) and aspect-oriented techniques.

Spring Security got its start as Acegi Security. Acegi was a powerful security framework, but it had one big turn-off: it required a lot of XML configuration. I’ll spare you the intricate details of what such a configuration may have looked like. Suffice it to say that it was common for a typical Acegi configuration to grow to several hundred lines of XML.

With version 2.0, Acegi Security became Spring Security. But the 2.0 release brought more than just a superficial name change. Spring Security 2.0 introduced a new security-specific XML namespace for configuring security in Spring. The new namespace, along with annotations and reasonable defaults, slimmed typical security configuration from hundreds of lines to only a dozen or so lines of XML. Spring Security 3.0 added SpEL to the mix, simplifying security configuration even more.

Now at version 3.2, Spring Security tackles security from two angles. To secure web requests and restrict access at the URL level, Spring Security uses servlet filters. Spring Security can also secure method invocations using Spring AOP, proxying objects and applying advice to ensure that the user has the proper authority to invoke secured methods.

We’ll focus on web-layer security with Spring Security in this chapter. Later, in chapter 14, we’ll revisit Spring Security and see how it can be used to secure method invocations.

9.1.1. Understanding Spring Security modules

No matter what kind of application you want to secure using Spring Security, the first thing you need to do is to add the Spring Security modules to the application’s classpath. Spring Security 3.2 is divided into eleven modules, as listed in table 9.1.

Table 9.1. Spring Security is partitioned into eleven modules

Module

Description

ACL

Provides support for domain object security through access control lists (ACLs).

Aspects

A small module providing support for AspectJ-based aspects instead of standard Spring AOP when using Spring Security annotations.

CAS Client

Support for single sign-on authentication using Jasig’s Central Authentication Service (CAS).

Configuration

Contains support for configuring Spring Security with XML and Java. (Java configuration support introduced in Spring Security 3.2.)

Core

Provides the essential Spring Security library.

Cryptography

Provides support for encryption and password encoding.

LDAP

Provides support for LDAP-based authentication.

OpenID

Contains support for centralized authentication with OpenID.

Remoting

Provides integration with Spring Remoting.

Tag Library

Spring Security’s JSP tag library.

Web

Provides Spring Security’s filter-based web security support.

At the least, you’ll want to include the Core and Configuration modules in your application’s classpath. Spring Security is often used to secure web applications, and that’s certainly the case with the Spittr application, so you’ll also need to add the Web module. We’ll also be taking advantage of Spring Security’s JSP tag library, so you’ll need to add that module to the mix.

9.1.2. Filtering web requests

Spring Security employs several servlet filters to provide various aspects of security. You might be thinking that means you’ll need to configure several filters in a web.xml file, or perhaps in a WebApplicationInitializer class. But thanks to a little Spring magic, you’ll only need to configure one of those filters.

DelegatingFilterProxy is a special servlet filter that, by itself, doesn’t do much. Instead, it delegates to an implementation of javax.servlet.Filter that’s registered as a <bean> in the Spring application context, as illustrated in figure 9.1.

Figure 9.1. DelegatingFilterProxy proxies filter handling to a delegate filter bean in the Spring application context.

If you like configuring servlets and filters in the traditional web.xml file, you can do that with the <filter> element, like this:

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>

org.springframework.web.filter.DelegatingFilterProxy

</filter-class>

</filter>

The most important thing here is that the <filter-name> be set to springSecurityFilterChain. That’s because you’ll soon be configuring Spring Security for web security, and there will be a filter bean named springSecurityFilterChain that DelegatingFilterProxy will need to delegate to.

If you’d rather configure DelegatingFilterProxy in Java with a WebApplicationInitializer, then all you need to do is create a new class that extends Abstract-SecurityWebApplicationInitializer:

package spitter.config;

import org.springframework.security.web.context.

AbstractSecurityWebApplicationInitializer;

public class SecurityWebInitializer

extends AbstractSecurityWebApplicationInitializer {}

AbstractSecurityWebApplicationInitializer implements WebApplication-Initializer, so it will be discovered by Spring and be used to register DelegatingFilterProxy with the web container. Although you can override its appendFilters() or insertFilters()methods to register filters of your own choosing, you need not override anything to register DelegatingFilterProxy.

Whether you configure DelegatingFilterProxy in web.xml or by subclassing AbstractSecurityWebApplicationInitializer, it will intercept requests coming into the application and delegate them to a bean whose ID is springSecurityFilterChain.

As for the springSecurityFilterChain bean itself, it’s another special filter known as FilterChainProxy. It’s a single filter that chains together one or more additional filters. Spring Security relies on several servlet filters to provide different security features, but you should almost never need to know these details, as you likely won’t need to explicitly declare the springSecurityFilterChain bean or any of the filters it chains together. Those filters will be created when you enable web security.

To get the ball rolling with web security, let’s create the simplest possible security configuration.

9.1.3. Writing a simple security configuration

In the early days of Spring Security (way back when it was known as Acegi Security), you’d need to write hundreds of lines of XML configuration just to enable simple security in a web application. Spring Security 2.0 made things better by offering a security-specific XML configuration namespace.

Spring 3.2 introduced a new Java configuration option, altogether eliminating the need for XML security configuration. The following listing shows the simplest possible Java configuration for Spring Security.

Listing 9.1. The simplest configuration class to enable web security for Spring MVC

As its name suggests, the @EnableWebSecurity annotation enables web security. It is useless on its own, however. Spring Security must be configured in a bean that implements WebSecurityConfigurer or (for convenience) extends WebSecurityConfigurer-Adapter. Any bean in the Spring application context that implements WebSecurityConfigurer can contribute to Spring Security configuration, but it’s often most convenient for the configuration class to extend WebSecurityConfigurerAdapter, as shown in listing 9.1.

@EnableWebSecurity is generally useful for enabling security in any web application. But if you happen to be developing a Spring MVC application, you should consider using @EnableWebMvcSecurity instead, as shown in the following listing.

Listing 9.2. The simplest configuration class to enable web security for Spring MVC

Among other things, the @EnableWebMvcSecurity annotation configures a Spring MVC argument resolver so that handler methods can receive the authenticated user’s principal (or username) via @AuthenticationPrincipal-annotated parameters. It also configures a bean that automatically adds a hidden cross-site request forgery (CSRF) token field on forms using Spring’s form-binding tag library.

It may not look like much, but the security configuration class in listings 9.1 and 9.2 packs quite a punch. Either one will lock down an application so tightly that nobody can get in!

Although it’s not strictly required, you’ll probably want to specify the finer points of web security by overriding one or more of the methods from WebSecurity-ConfigurerAdapter. You can configure web security by overriding WebSecurity-ConfigurerAdapter’s threeconfigure() methods and setting behavior on the parameter passed in. Table 9.2 describes these three methods.

Table 9.2. Overriding WebSecurityConfigurerAdapter’s configure() methods

Method

Description

configure(WebSecurity)

Override to configure Spring Security’s filter chain.

configure(HttpSecurity)

Override to configure how requests are secured by interceptors.

configure(AuthenticationManagerBuilder)

Override to configure user-details services.

Looking back to listing 9.2, you can see that it doesn’t override any of these three configure() methods, and that explains why the application is now locked down tight. Although the default filter chain is fine for our needs, the default configure(Http-Security) effectively looks like this:

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.anyRequest().authenticated()

.and()

.formLogin().and()

.httpBasic();

}

This simple default configuration specifies how HTTP requests should be secured and what options a client has for authenticating the user. The call to authorize-Requests() and anyRequest().authenticated() demands that all HTTP requests coming into the application be authenticated. It also configures Spring Security to support authentication via a form-based login (using a predefined login page) as well as HTTP Basic.

Meanwhile, because you haven’t overridden the configure(Authentication-ManagerBuilder) method, there’s no user store backing the authentication process. With no user store, there are effectively no users. Therefore, all requests require authentication, but there’s nobody who can log in.

You’re going to need to add a bit more configuration to bend Spring Security to fit your application’s needs. Specifically, you’ll need to...

· Configure a user store

· Specify which requests should and should not require authentication, as well as what authorities they require

· Provide a custom login screen to replace the plain default login screen

In addition to these facets of Spring Security, you may also want to selectively render certain content in your web views based on security constraints.

First things first, however. Let’s see how to configure user services to access user data during the authentication process.

9.2. Selecting user details services

Suppose you were to go out for a nice dinner at an exclusive restaurant. Of course, you made the reservation several weeks in advance to be assured that you have a table. As you enter the restaurant, you give the host your name. Unfortunately, there’s no record of your reservation. Your special evening is in jeopardy. Not one to give up so easily, you ask the host to check the reservation list again. That’s when things get weird.

The host says that there isn’t a reservation list. Your name isn’t on the list—nobody’s name is on the list—because there isn’t a list. That would explain why you can’t get in the door, despite the fact that the place is empty. Weeks later, you’ll realize that it also explains why the restaurant ended up closing and being replaced with a taqueria.

That’s the scenario you have with your application at this point. There’s no way to get into the application because even if the user thinks they should be allowed in, there’s no record of them having access to the application. For lack of a user store, the application is so exclusive that it’s completely unusable.

What you need is a user store—some place where usernames, passwords, and other data can be kept and retrieved from when making authentication decisions.

Fortunately, Spring Security is extremely flexible and is capable of authenticating users against virtually any data store. Several common user store situations—such as in-memory, relational database, and LDAP—are provided out of the box. But you can also create and plug in custom user store implementations.

Spring Security’s Java configuration makes it easy to configure one or more data store options. We’ll start with the simplest user store: one that maintains its user store in memory.

9.2.1. Working with an in-memory user store

Since your security configuration class extends WebSecurityConfigurerAdapter, the easiest way to configure a user store is to override the configure() method that takes an AuthenticationManagerBuilder as a parameter. AuthenticationManagerBuilder has several methods that can be used to configure Spring Security’s authentication support. With the inMemoryAuthentication() method, you can enable and configure and optionally populate an in-memory user store.

For example, in the following listing, SecurityConfig overrides configure() to configure an in-memory user store with two users.

Listing 9.3. Configuring Spring Security to use an in-memory user store

As you can see, the AuthenticationManagerBuilder given to configure() employs a builder-style interface to build up authentication configuration. Simply calling inMemoryAuthentication() will enable an in-memory user store. But you’ll also need some users in there, or else it’s as if you have no user store at all.

Therefore, you need to call the withUser() method to add a new user to the in-memory user store. The parameter given is the username. withUser() returns a UserDetailsManagerConfigurer.UserDetailsBuilder,which has several methods for further configuration of the user, including password() to set the user’s password and roles() to give the user one or more role authorities.

In listing 9.3, you’re adding two users, “user” and “admin”, both with “password” for a password. The “user” user has the USER role, while the “admin” user has both USER and ADMIN roles. As you can see, the and() method is used to chain together multiple user configurations.

In addition to password(), roles(), and and(), there are several other methods for configuring user details for in-memory user stores. Table 9.3 describes all of the methods available from UserDetailsManagerConfigurer.UserDetailsBuilder.

Table 9.3. Methods for configuring user details

Module

Description

accountExpired(boolean)

Defines if the account is expired or not

accountLocked(boolean)

Defines if the account is locked or not

and()

Used for chaining configuration

authorities(GrantedAuthority...)

Specifies one or more authorities to grant to the user

authorities(List<? extends GrantedAuthority>)

Specifies one or more authorities to grant to the user

authorities(String...)

Specifies one or more authorities to grant to the user

credentialsExpired(boolean)

Defines if the credentials are expired or not

disabled(boolean)

Defines if the account is disabled or not

password(String)

Specifies the user’s password

roles(String...)

Specifies one or more roles to assign to the user

Note that the roles() method is a shortcut for the authorities() methods. Any values given to roles() are prefixed with ROLE_ and assigned as authorities to the user. In effect, the following user configuration is equivalent to that in listing 9.3:

auth

.inMemoryAuthentication()

.withUser("user").password("password")

.authorities("ROLE_USER").and()

.withUser("admin").password("password")

.authorities("ROLE_USER", "ROLE_ADMIN");

Although an in-memory user store is very useful for debugging and developer testing purposes, it’s probably not the most ideal choice for a production application. For production-ready purposes, it’s usually better to maintain user data in a database of some sort.

9.2.2. Authenticating against database tables

It’s quite common for user data to be stored in a relational database, accessed via JDBC. To configure Spring Security to authenticate against a JDBC-backed user store, you can use the jdbcAuthentication() method. The minimal configuration required is as follows:

@Autowired

DataSource dataSource;

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.jdbcAuthentication()

.dataSource(dataSource);

}

The only thing you must configure is a DataSource so that it’s able to access the relational database. The DataSource is provided here via the magic of autowiring.

Overriding the default user queries

Although this minimal configuration will work, it makes some assumptions about your database schema. It expects that certain tables exist where user data will be kept. More specifically, the following snippet of code from Spring Security’s internals shows the SQL queries that will be performed when looking up user details:

public static final String DEF_USERS_BY_USERNAME_QUERY =

"select username,password,enabled " +

"from users " +

"where username = ?";

public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =

"select username,authority " +

"from authorities " +

"where username = ?";

public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =

"select g.id, g.group_name, ga.authority " +

"from groups g, group_members gm, group_authorities ga " +

"where gm.username = ? " +

"and g.id = ga.group_id " +

"and g.id = gm.group_id";

The first query retrieves a user’s username, password, and whether or not they’re enabled. This information is used to authenticate the user. The next query looks up the user’s granted authorities for authorization purposes, and the final query looks up authorities granted to a user as a member of a group.

If you’re okay with defining and populating tables in your database that satisfy those queries, then there’s not much else for you to do. But chances are your database doesn’t look anything like this, and you’ll want more control over the queries. In that case, you can configure your own queries like this:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.jdbcAuthentication()

.dataSource(dataSource)

.usersByUsernameQuery(

"select username, password, true " +

"from Spitter where username=?")

.authoritiesByUsernameQuery(

"select username, 'ROLE_USER' from Spitter where username=?");

}

In this case, you’re only overriding the authentication and basic authorization queries. But you can also override the group authorities query by calling group-AuthoritiesByUsername() with a custom query.

When replacing the default SQL queries with those of your own design, it’s important to adhere to the basic contract of the queries. All of them take the username as their only parameter. The authentication query selects the username, password, and enabled status. The authorities query selects zero or more rows containing the username and a granted authority. And the group authorities query selects zero or more rows each with a group ID, group name, and an authority.

Working with encoded passwords

Focusing on the authentication query, you can see that user passwords are expected to be stored in the database. The only problem with that is that if the passwords are stored in plain text, they’re subject to the prying eyes of a hacker. But if you encode the password in the database, then authentication will fail because it won’t match the plain text password submitted by the user.

To remedy this problem, you need to specify a password encoder by calling the passwordEncoder() method:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.jdbcAuthentication()

.dataSource(dataSource)

.usersByUsernameQuery(

"select username, password, true " +

"from Spitter where username=?")

.authoritiesByUsernameQuery(

"select username, 'ROLE_USER' from Spitter where username=?")

.passwordEncoder(new StandardPasswordEncoder("53cr3t"));

}

The passwordEncoder method accepts any implementation of Spring Security’s PasswordEncoder interface. Spring Security’s cryptography module includes three such implementations: BCryptPasswordEncoder, NoOpPasswordEncoder, and StandardPasswordEncoder.

The preceding code uses StandardPasswordEncoder. But you can always provide your own custom implementation if none of the out-of-the-box implementations meet your needs. The PasswordEncoder interface is rather simple:

public interface PasswordEncoder {

String encode(CharSequence rawPassword);

boolean matches(CharSequence rawPassword, String encodedPassword);

}

No matter which password encoder you use, it’s important to understand that the password in the database is never decoded. Instead, the password that the user enters at login is encoded using the same algorithm and is then compared with the encoded password in the database. That comparison is performed in the PasswordEncoder’s matches() method.

Relational databases are just one storage option for user data. Another very common choice is to keep user data in an LDAP repository.

9.2.3. Applying LDAP-backed authentication

To configure Spring Security for LDAP-based authentication, you can use the ldap-Authentication() method. This method is the LDAP analog to jdbcAuthentication(). The following configure() method shows a simple configuration for LDAP authentication:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchFilter("(uid={0})")

.groupSearchFilter("member={0}");

}

The userSearchFilter() and groupSearchFilter() methods are used to provide a filter for the base LDAP queries, which are used to search for users and groups. By default, the base queries for both users and groups are empty, indicating that the search will be done from the root of the LDAP hierarchy. But you can change that by specifying a query base:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}");

}

The userSearchBase() method provides a base query for finding users. Likewise, the groupSearchBase() specifies the base query for finding groups. Rather than search from the root, this example specifies that users be searched for where the organization unit is people. And groups should be searched for where the organizational unit is groups.

Configuring password comparison

The default strategy for authenticating against LDAP is to perform a bind operation, authenticating the user directly to the LDAP server. Another option is to perform a comparison operation. This involves sending the entered password to the LDAP directory and asking the server to compare the password against a user’s password attribute. Because the comparison is done within the LDAP server, the actual password remains secret.

If you’d rather authenticate by doing a password comparison, you can declare so with the passwordCompare() method:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}")

.passwordCompare();

}

By default, the password given in the login form will be compared with the value of the userPassword attribute in the user’s LDAP entry. If the password is kept in a different attribute, you can specify the password attribute’s name with passwordAttribute():

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}")

.passwordCompare()

.passwordEncoder(new Md5PasswordEncoder())

.passwordAttribute("passcode");

}

In this example, you specify that the "passcode" attribute is what should be compared with the given password. Moreover, you also specify a password encoder. It’s nice that the actual password is kept secret on the server when doing server-side password comparison. But the attempted password is still passed across the wire to the LDAP server and could be intercepted by a hacker. To prevent that, you can specify an encryption strategy by calling the passwordEncoder() method.

In the example, passwords are encrypted using MD5. This assumes that the passwords are also encrypted using MD5 in the LDAP server.

Referring to a remote LDAP server

The one thing I’ve left out until now is where the LDAP server and data actually reside. You’ve happily been configuring Spring to authenticate against an LDAP server, but where is that server?

By default, Spring Security’s LDAP authentication assumes that the LDAP server is listening on port 33389 on localhost. But if your LDAP server is on another machine, you can use the contextSource() method to configure the location:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}")

.contextSource()

.url("ldap://habuma.com:389/dc=habuma,dc=com");

}

The contextSource() method returns a ContextSourceBuilder, which, among other things, offers the url() method that lets you specify the location of the LDAP server.

Configuring an embedded LDAP server

If you don’t happen to have an LDAP server lying around waiting to be authenticated against, Spring Security can provide an embedded LDAP server for you. Instead of setting the URL to a remote LDAP server, you can specify the root suffix for the embedded server via the root()method:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}")

.contextSource()

.root("dc=habuma,dc=com");

}

When the LDAP server starts, it will attempt to load data from any LDIF files that it can find in the classpath. LDIF (LDAP Data Interchange Format) is a standard way of representing LDAP data in a plain text file. Each record is composed of one or more lines, each containing aname:value pair. Records are separated from each other by blank lines.

If you’d rather that Spring not rummage through your classpath looking for just any LDIF files it can find, you can be more explicit about which LDIF file gets loaded by calling the ldif() method:

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.ldapAuthentication()

.userSearchBase("ou=people")

.userSearchFilter("(uid={0})")

.groupSearchBase("ou=groups")

.groupSearchFilter("member={0}")

.contextSource()

.root("dc=habuma,dc=com")

.ldif("classpath:users.ldif");

}

Here you specifically ask the LDAP server to load its content from the users.ldif file at the root of the classpath. In case you’re curious, here’s an LDIF file that you could use to load the embedded LDAP server with user data:

dn: ou=groups,dc=habuma,dc=com

objectclass: top

objectclass: organizationalUnit

ou: groups

dn: ou=people,dc=habuma,dc=com

objectclass: top

objectclass: organizationalUnit

ou: people

dn: uid=habuma,ou=people,dc=habuma,dc=com

objectclass: top

objectclass: person

objectclass: organizationalPerson

objectclass: inetOrgPerson

cn: Craig Walls

sn: Walls

uid: habuma

userPassword: password

dn: uid=jsmith,ou=people,dc=habuma,dc=com

objectclass: top

objectclass: person

objectclass: organizationalPerson

objectclass: inetOrgPerson

cn: John Smith

sn: Smith

uid: jsmith

userPassword: password

dn: cn=spittr,ou=groups,dc=habuma,dc=com

objectclass: top

objectclass: groupOfNames

cn: spittr

member: uid=habuma,ou=people,dc=habuma,dc=com

Spring Security’s built-in user stores are convenient and cover the most common use cases. But if your authentication needs are of the uncommon variety, you may need to create and configure a custom user-details service.

9.2.4. Configuring a custom user service

Suppose that you need to authenticate against users in a non-relational database such as Mongo or Neo4j. In that case, you’ll need to implement a custom implementation of the UserDetailsService interface.

The UserDetailsService interface is rather straightforward:

public interface UserDetailsService {

UserDetails loadUserByUsername(String username)

throws UsernameNotFoundException;

}

All you need to do is implement the loadUserByUsername() method to find a user given the user’s username. loadUserByUsername() then returns a UserDetails object representing the given user. The following listing shows an implementation of UserDetailsService that looks up a user from a given implementation of SpitterRepository.

Listing 9.4. Retrieve a UserDetails object from a SpitterRepository

What’s interesting about SpitterUserService is that it has no idea how the user data is persisted. The SpitterRepository it’s given could look up the Spitter from a relational database, from a document database, from a graph database, or it could just make it up.SpitterUserService doesn’t know or care what underlying data storage is used. It just fetches the Spitter object and uses it to create a User object. (User is a concrete implementation of UserDetails.)

To use SpitterUserService to authenticate users, you can configure it in your security configuration with the userDetailsService() method:

@Autowired

SpitterRepository spitterRepository;

@Override

protected void configure(AuthenticationManagerBuilder auth)

throws Exception {

auth

.userDetailsService(new SpitterUserService(spitterRepository));

}

The userDetailsService() method (like jdbcAuthentication(), ldapAuthentication, and inMemoryAuthentication()) configures a configuration store. But instead of using one of Spring’s provided user stores, it takes any implementation of UserDetailsService.

Another option worth considering is that you could change Spitter so that it implements UserDetailsService. By doing that, you could return the Spitter directly from the loadUserByUsername() method without copying its values into a User object.

9.3. Intercepting requests

Earlier, in section 9.1.3, you saw an extremely simple Spring Security configuration and learned how it falls back to a default configuration where all requests require authentication. Some may argue that too much security is better than too little. But there’s also something to be said about applying the appropriate amount of security.

In any given application, not all requests should be secured equally. Some may require authentication; some may not. Some requests may only be available to users with certain authorities and unavailable to those without those authorities.

For example, consider the requests served by the Spittr application. Certainly, the home page is public and doesn’t need to be secured. Likewise, since all Spittle objects are essentially public, the pages that display Spittles don’t require security. Requests that create a Spittle, however, should only be performed by an authenticated user. Similarly, although user profile pages are public and don’t require authentication, if you were to handle a request for /spitters/me to display the current user’s profile, then authentication is required to know whose profile to show.

The key to fine-tuning security for each request is to override the configure (HttpSecurity) method. The following code snippet shows how you might override configure(HttpSecurity) to selectively apply security to different URL paths.

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers("/spitters/me").authenticated()

.antMatchers(HttpMethod.POST, "/spittles").authenticated()

.anyRequest().permitAll();

}

The HttpSecurity object given to configure() can be used to configure several aspects of HTTP security. Here you’re calling authorizeRequests() and then calling methods on the object it returns to indicate that you want to configure request-level security details. The first call toantMatchers() specifies that requests whose path is /spitters/me should be authenticated. The second call to antMatchers() is even more specific, saying that any HTTP POST request to /spittles must be authenticated. Finally, a call to anyRequests() says that all other requests should be permitted, not requiring authentication or any authorities.

The path given to antMatchers() supports Ant-style wildcarding. Although we’re not using it here, you could specify a path with a wildcard like this:

.antMatchers("/spitters/**").authenticated();

You could also specify multiple paths in a single call to antMatchers():

.antMatchers("/spitters/**", "/spittles/mine").authenticated();

Whereas the antMatchers() method works with paths that may contain Ant-style wildcards, there’s also a regexMatchers() method that accepts regular expressions to define request paths. For example, the following snippet uses a regular expression that’s equivalent to /spitters/** (Ant-style):

.regexMatchers("/spitters/.*").authenticated();

Aside from path selection, we’ve also used authenticated() and permitAll() to define how the paths should be secured. The authenticated() method demands that the user have logged into the application to perform the request. If the user isn’t authenticated, Spring Security’s filters will capture the request and redirect the user to the application’s login page. Meanwhile, the permitAll() method allows the requests without any security demands.

In addition to authenticated() and permitAll(), there are other methods that can be used to define how a request should be secured. Table 9.4 describes all of the options available.

Table 9.4. Configuration methods to define how a path is to be secured

Method

What it does

access(String)

Allows access if the given SpEL expression evaluates to true

anonymous()

Allows access to anonymous users

authenticated()

Allows access to authenticated users

denyAll()

Denies access unconditionally

fullyAuthenticated()

Allows access if the user is fully authenticated (not remembered)

hasAnyAuthority(String...)

Allows access if the user has any of the given authorities

hasAnyRole(String...)

Allows access if the user has any of the given roles

hasAuthority(String)

Allows access if the user has the given authority

hasIpAddress(String)

Allows access if the request comes from the given IP address

hasRole(String)

Allows access if the user has the given role

not()

Negates the effect of any of the other access methods

permitAll()

Allows access unconditionally

rememberMe()

Allows access for users who are authenticated via remember-me

Using methods from table 9.4, you can configure security to require more than just an authenticated user. For example, you could change the previous configure() method to require that the user not only be authenticated, but also have ROLE_SPITTER authority:

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers("/spitters/me").hasAuthority("ROLE_SPITTER")

.antMatchers(HttpMethod.POST, "/spittles")

.hasAuthority("ROLE_SPITTER")

.anyRequest().permitAll();

}

Optionally, you can use the hasRole() method to have the ROLE_ prefix applied automatically:

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers("/spitter/me").hasRole("SPITTER")

.antMatchers(HttpMethod.POST, "/spittles").hasRole("SPITTER")

.anyRequest().permitAll();

}

You can chain as many calls to antMatchers(), regexMatchers(), and anyRequest() as you need to fully establish the security rules around your web application. You should know, however, that they’ll be applied in the order given. For that reason, it’s important to configure the most specific request path patterns first and the least specific ones (such as anyRequest()) last. If not, then the least specific paths will trump the more specific ones.

9.3.1. Securing with Spring Expressions

Most of the methods in table 9.4 are one-dimensional. That is, you can use hasRole() to require a certain role, but you can’t also use hasIpAddress() to require a specific IP address on the same path.

Moreover, there’s no way to work in any conditions that aren’t defined by the methods in table 9.4. What if you wanted to restrict access to certain roles only on Tuesday?

In chapter 3, you saw how to use the Spring Expression Language (SpEL) as an advanced technique for wiring bean properties. Using the access() method, you can also use SpEL as a means for declaring access requirements. For example, here’s how you could use a SpEL expression to require ROLE_SPITTER access for the /spitter/me URL pattern:

.antMatchers("/spitter/me").access("hasRole('ROLE_SPITTER')")

This security constraint placed on /spitter/me is equivalent to the one we started with, except that now it uses SpEL to express the security rules. The hasRole() expression evaluates to true if the current user has been granted the given authority.

What makes SpEL a more powerful option here is that hasRole() is only one of the security-specific expressions supported. Table 9.5 lists all of the SpEL expressions available in Spring Security.

Table 9.5. Spring Security extends the Spring Expression Language with several security-specific expressions

Security expression

What it evaluates to

authentication

The user’s authentication object

denyAll

Always evaluates to false

hasAnyRole(list of roles)

True if the user has any of the given roles

hasRole(role)

True if the user has the given role

hasIpAddress(IP address)

True if the request comes from the given IP address

isAnonymous()

True if the user is anonymous

isAuthenticated()

True if the user is authenticated

isFullyAuthenticated()

True if the user is fully authenticated (not authenticated with remember-me)

isRememberMe()

True if the user was authenticated via remember-me

permitAll

Always evaluates to true

principal

The user’s principal object

With Spring Security’s SpEL expressions at your disposal, you can do more than just limit access based on a user’s granted authorities. For example, if you wanted to lock down the /spitter/me URLs to not only require ROLE_SPITTER, but to also only be allowed from a given IP address, you might call the access() method like this:

.antMatchers("/spitter/me")

.access("hasRole('ROLE_SPITTER') and hasIpAddress('192.168.1.2')")

With SpEL-based security constraints, the possibilities are virtually endless. I’ll bet that you’re already dreaming up interesting security constraints based on SpEL.

But for now, let’s look at another way that Spring Security intercepts requests to enforce channel security.

9.3.2. Enforcing channel security

Submitting data across HTTP can be a risky proposition. It may not be a big deal to send a spittle message in the clear over HTTP. But if you’re passing sensitive information such as passwords and credit card numbers across HTTP, then you’re asking for trouble. Data is sent over HTTP unencrypted, leaving an open opportunity for a hacker to intercept the request and see information you don’t want them to see. That’s why sensitive information should be sent encrypted over HTTPS.

Working with HTTPS seems simple enough. All you have to do is add an s after the http in a URL and you’re set. Right?

That’s true, but it places responsibility for using the HTTPS channel in the wrong place. Just as it’s easy to make a page secure by adding an s, it’s just as easy to forget to add that s. If you have several links in your app that require HTTPS, chances are good that you’ll forget to add an s or two.

On the other hand, you might overcorrect and use HTTPS in places where it’s unnecessary.

In addition to the authorizeRequests() method, the HttpSecurity object passed into configure() has a requiresChannel() method that lets you declare channel requirements for various URL patterns.

For example, consider the Spittr application’s registration form. Although Spittr doesn’t ask for credit card numbers or social security numbers or anything terribly sensitive, users may want their registration information to be kept private. To ensure that the registration form is sent over HTTPS, you can add requiresChannel() to the configuration, as in the following listing.

Listing 9.5. The requiresChannel() method enforces HTTPS for select URLs

Any time a request comes in for /spitter/form, Spring Security will see that it requires a secure channel (per the call to requiresSecure()) and automatically redirect the request to go over HTTPS.

Conversely, some pages don’t need to be sent over HTTPS. The home page, for example, doesn’t carry any sensitive information and should be sent over HTTP. You can declare that the home page always be sent over HTTP by using requires-Insecure() instead of requiresSecure:

.antMatchers("/").requiresInecure();

If a request for / comes in over HTTPS, Spring Security will redirect the request to flow over the insecure HTTP.

Notice that the path selection options for channel enforcement are the same as for authorizeRequests(). In listing 9.5 you’re using antMatches(), but regex-Matchers() is also available for selecting path patterns with regular expressions.

9.3.3. Preventing cross-site request forgery

As you’ll recall, our SpittleController will create a new Spittle for a user when a POST request is submitted to /spittles. But what if that POST request comes from another website? And what if that POST request is the result of submitting the following form on that other site?

<form method="POST" action="http://www.spittr.com/spittles">

<input type="hidden" name="message" value="I'm stupid!" />

<input type="submit" value="Click here to win a new car!" />

</form>

Let’s say that you’re tempted by the offer of winning a new car and you click the button—you’ll submit the form to http://www.spittr.com/spittles. If you’re already logged in to spittr.com, you’ll be broadcasting a message that tells everyone that you made a bad decision.

This is a simple example of a cross-site request forgery (CSRF). Basically, a CSRF attack happens when one site tricks a user into submitting a request to another server, possibly having a negative outcome. Although posting “I’m stupid!” to a microblogging site is hardly the worst example of CSRF, you can easily imagine more serious exploits, perhaps performing some undesired operation on your bank account.

Starting with Spring Security 3.2, CSRF protection is enabled by default. In fact, unless you take steps to work with CSRF protection or disable it, you’ll probably have trouble getting the forms in your application to submit successfully.

Spring Security implements CSRF protection with a synchronizer token. State-changing requests (for example, any request that is not GET, HEAD, OPTIONS, or TRACE) will be intercepted and checked for a CSRF token. If the request doesn’t carry a CSRF token, or if the token doesn’t match the token on the server, the request will fail with a CsrfException.

This means that any forms in your application must submit a token in a _csrf field. And that token must be the same as the one calculated and stored by the server so that it matches up when the form is submitted.

Fortunately, Spring Security makes this easy for you by putting the token into the request under the request attributes. If you’re using Thymeleaf for your page template, you’ll get the hidden _csrf field automatically, as long as the <form> tag’s action attribute is prefixed to come from the Thymeleaf namespace:

<form method="POST" th:action="@{/spittles}">

...

</form>

If you’re using JSP for page templates, you can do something very similar:

<input type="hidden"

name="${_csrf.parameterName}"

value="${_csrf.token}" />

Even better, if you’re using Spring’s form-binding tag library, the <sf:form> tag will automatically add the hidden CSRF token tag for you.

Another way of dealing with CSRF is to not deal with it at all. You can disable Spring Security’s CSRF protection by calling csrf().disable() in the configuration, as shown in the next listing.

Listing 9.6. You can disable Spring Security’s CSRF protection

Be warned that it’s generally not a good idea to disable CSRF protection. If you do, you leave your application open to a CSRF attack. Use the configuration in listing 9.6 only after careful deliberation.

Now that you’ve configured a user store and configured Spring Security to intercept requests, you should turn your attention to prompting the user for their credentials.

9.4. Authenticating users

When you were still using the extremely simple Spring Security configuration in listing 9.1, you got a login page for free. In fact, up until you overrode configure(Http-Security), you could count on a plain-vanilla, yet fully functional login page. But as soon as you overrideconfigure(HttpSecurity), you lose that simple login page.

Fortunately, it’s easy enough to get it back. All you need to do is call formLogin() in the configure(HttpSecurity) method, as shown in the following listing.

Listing 9.7. The formLogin() method enables a basic login page

Notice that, as before, and() is called to chain together different configuration instructions.

If you link to /login in the application, or if the user navigates to a page that requires authentication, then the login page will be shown in the browser. As you can see in figure 9.2, the page isn’t very exciting aesthetically, but it does the job it needs to do.

Figure 9.2. The default login page is simple aesthetically, but fully functional

I’ll bet you’d prefer that your application’s login page look nicer than the default login page. It’d be a shame to have such a plain login page ruin your otherwise beautifully designed website. No problem. Let’s see how you can add a custom login page to your application.

9.4.1. Adding a custom login page

The first step toward creating a custom login page is knowing what you need to include in the login form. Look no further than the HTML source of the default login page to see what’s required:

<html>

<head><title>Login Page</title></head>

<body onload='document.f.username.focus();'>

<h3>Login with Username and Password</h3>

<form name='f' action='/spittr/login' method='POST'>

<table>

<tr><td>User:</td><td>

<input type='text' name='username' value=''></td></tr>

<tr><td>Password:</td>

<td><input type='password' name='password'/></td></tr>

<tr><td colspan='2'>

<input name="submit" type="submit" value="Login"/></td></tr>

<input name="_csrf" type="hidden"

value="6829b1ae-0a14-4920-aac4-5abbd7eeb9ee" />

</table>

</form>

</body>

</html>

The key thing to note is where the <form> submits to. And make note of the username and password fields; you’ll need those same fields on your login page. Finally, assuming that you’ve not disabled CSRF, you’ll need to be sure to include a _csrf field with the CSRF token.

The following listing shows a Thymeleaf template that provides a login page within the style of the Spittr application.

Listing 9.8. A custom login page for the Spittr application (as a Thymeleaf template)

Notice that the Thymeleaf template has both username and password fields, just like the default login page. It also submits to the context-relative /login page. And since this is a Thymeleaf template, the hidden _csrf field will automatically be added to the form.

9.4.2. Enabling HTTP Basic authentication

Form-based authentication is ideal for human users of an application. But in chapter 16, you’ll see how to turn some of your web application’s pages into a RESTful API. When the user of the application is another application, prompting for login with a form just won’t do.

HTTP Basic authentication is one way to authenticate a user to an application directly in the HTTP request itself. You may have seen HTTP Basic authentication before. When encountered by a web browser, it prompts the user with a plain modal dialog box.

But that’s just how it’s manifested in a web browser. In reality, it’s an HTTP 401 response, indicating that a username and password must be presented with the request. This makes it suitable as a means for REST clients to authenticate against the services they’re consuming.

Enabling HTTP Basic authentication is as simple as calling httpBasic() on the HttpSecurity object passed into configure(). Optionally, you can specify a realm by calling realmName(). Here’s a rather typical example of Spring Security configuration to enable HTTP Basic:

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.formLogin()

.loginPage("/login")

.and()

.httpBasic()

.realmName("Spittr")

.and()

...

}

Notice that once again the and() method is used to chain together different configuration directives in configure().

Not much customization is available or even required with httpBasic(). HTTP Basic authentication is either turned on or it’s not. So rather than dwell on the topic any further, let’s move on to see how to have a user automatically authenticated via remember-me functionality.

9.4.3. Enabling remember-me functionality

It’s important for an application to be able to authenticate users. But from the user’s perspective, it’d be nice if the application didn’t always prompt them with a login every time they use it. That’s why many websites offer remember-me functionality, so that you can log in once and then be remembered by the application when you come back to it later.

Spring Security makes it easy to add remember-me functionality to an application. To turn on remember-me support, all you need to do is call rememberMe() on the HttpSecurity passed into configure():

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.formLogin()

.loginPage("/login")

.and()

.rememberMe()

.tokenValiditySeconds(2419200)

.key("spittrKey")

...

}

Here, in addition to turning on remember-me functionality, a bit of special configuration has also been added. By default, a remember-me token is stored in a cookie that’s valid for up to two weeks. But this example specifies that the token should stay valid for up to four weeks (2,419,200 seconds).

The token that’s stored in the cookie is made up of the username, password, an expiration date, and a private key—all encoded in an MD5 hash before being written to the cookie. By default, the private key is SpringSecured, but this example sets it to spitterKey to make it specific to the Spittr application.

Simple enough. Now that the remember-me functionality is enabled, you’ll need a way for users to indicate that they’d like the application to remember them. For that, the login request will need to include a remember-me parameter. A simple check box in the login form ought to do the job:

<input id="remember_me" name="remember-me" type="checkbox"/>

<label for="remember_me" class="inline">Remember me</label>

Just as important as being able to log in to an application is the ability to log out. This is especially true if you’ve enabled remember-me; otherwise the user would be logged into the application forever. Let’s see how you can add the ability to log out.

9.4.4. Logging out

As it turns out, logout capability is already enabled by your configuration without you having to do anything else. All you need to do is add a link that uses it.

Logout is implemented as a servlet filter that (by default) intercepts requests to /logout. Therefore, adding logout to an application is as easy as adding the following link (shown here as a Thymeleaf snippet):

<a th:href="@{/logout}">Logout</a>

When the user clicks on the link, the request for /logout will be handled by Spring Security’s LogoutFilter. The user will be logged out and any remember-me tokens cleared. After the logout is complete, the user’s browser will be redirected to /login?logout to give the user an opportunity to log in again.

If you’d like to have the user redirected to some other page, such as the application’s home page, you can configure that in configure() like this:

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.formLogin()

.loginPage("/login")

.and()

.logout()

.logoutSuccessUrl("/")

...

}

Here, as before, and() chains a call to logout(). The logout() method offers methods for configuring logout behavior. In this case, the call to logoutSuccessUrl() indicates that the browser should be redirected to / after a successful logout.

In addition to logoutSuccessUrl(), you may want to also override the default path that LogoutFilter intercepts. You can do that with a call to logoutUrl():

.logout()

.logoutSuccessUrl("/")

.logoutUrl("/signout")

So far you’ve seen how to secure web applications as requests are made. The assumption has been that security would involve stopping a user from accessing a URL that they’re not authorized to use. But it’s also a good idea to never show links that a user won’t be able to follow. Let’s see how to add view layer security.

9.5. Securing the view

When rendering HTML to be served in the browser, you may want the view to reflect the security constraints and information. A simple example may be that you want to render the authenticated user’s principal (for example, “You are logged in as...”). Or you may want to conditionally render certain view elements, depending on what authorities have been granted to the user.

In chapter 6, we looked at two significant options for rendering views in a Spring MVC application: JSP and Thymeleaf. It doesn’t matter which of these options you choose, there’s a way to work with security in the view. Spring Security itself provides a JSP tag library, whereas Thymeleaf offers Spring Security integration through a special dialect.

Let’s see how to work Spring Security into our views, starting with Spring Security’s JSP tag library.

9.5.1. Using Spring Security’s JSP tag library

Spring Security’s JSP tag library is small and includes only three tags, listed in table 9.6.

Table 9.6. Spring Security supports security in the view layer with a JSP tag library

JSP tag

What it does

<security:accesscontrollist>

Conditionally renders its body content if the user is granted authorities by an access control list

<security:authentication>

Renders details about the current authentication

<security:authorize>

Conditionally renders its body content if the user is granted certain authorities or if a SpEL expression evaluates to true

To use the JSP tag library, we’ll need to declare it in any JSP file where it will be used:

<%@ taglib prefix="security"

uri="http://www.springframework.org/security/tags" %>

Once the tag library has been declared in the JSP file, you’re ready to use it. Let’s look at each of the three JSP tags that come with Spring Security and see how they work.

Accessing authentication details

One of the simplest things that the Spring Security JSP tag library can do is provide convenient access to the user’s authentication information. For example, it’s common for websites to display a “welcome” or “hello” message in the page header, identifying the user by their username. That’s precisely the kind of thing that <security:authentication> can do for us. Here’s an example:

Hello <security:authentication property="principal.username" />!

The property attribute identifies a property of the user’s authentication object. The properties available will vary depending on how the user was authenticated, but you can count on a few common properties being available, including those listed in table 9.7.

Table 9.7. You can access several of the user’s authentication details using the <security:authentication> JSP tag

Authentication property

Description

authorities

A collection of GrantedAuthority objects that represent the privileges granted to the user

credentials

The credentials that were used to verify the principal (commonly, this is the user’s password)

details

Additional information about the authentication (IP address, certificate serial number, session ID, and so on)

principal

The user’s principal

In our example, the property being rendered is actually the nested username property of the principal property.

When used as shown in the previous example, <security:authentication> will render the property’s value in the view. But if you’d rather assign it to a variable, then simply specify the name of the variable in the var attribute. For example, here’s how you could assign it to a property named loginId:

<security:authentication property="principal.username"

var="loginId"/>

The variable is created in page scope by default. But if you’d rather create it in some other scope, such as request or session (or any of the scopes available from javax.servlet.jsp.PageContext), you can specify it via the scope attribute. For example, to create the variable in request scope, use the <security:authentication> tag like this:

<security:authentication property="principal.username"

var="loginId" scope="request" />

The <security:authentication> tag is useful, but it’s just the start of what Spring Security’s JSP tag library can do. Let’s see how to conditionally render content depending on the user’s privileges.

Conditional rendering

Sometimes portions of the view should or shouldn’t be rendered, depending on what the user is privileged to see. There’s no point in showing a login form to a user who’s already logged in or in showing a personalized greeting to a user who’s not logged in.

Spring Security’s <security:authorize> JSP tag conditionally renders a portion of the view depending on the user’s granted authorities. For example, in the Spittr application you don’t want to show the form for adding a new spittle unless the user has the ROLE_SPITTER role. Listing 9.9 shows how to use the <security:authorize> tag to display the spittle form if the user has ROLE_SPITTER authority.

Listing 9.9. <sec:authorize> conditionally renders content based on SpEL

The access attribute is given a SpEL expression whose result determines whether <security:authorize>’s body is rendered. Here you’re using the hasRole ('ROLE_SPITTER') expression to ensure that the user has the ROLE_SPITTER role. But you have the full power of SpEL at your disposal when setting the access attribute, including the Spring Security-provided expressions listed in table 9.5.

With these expressions available, you can cook up some interesting security constraints. For example, imagine that the application has some administrative functions that are only available to the user whose username is “habuma”. Maybe you’d use the isAuthenticated() andprincipal expressions like this:

<security:authorize

access="isAuthenticated() and principal.username=='habuma'">

<a href="/admin">Administration</a>

</security:authorize>

I’m sure you can dream up even more interesting expressions than that. I’ll leave it up to your imagination to concoct more security constraints. The options are virtually limitless with SpEL.

But one thing about the example that I dreamt up still bugs me. Though I might want to restrict the administrative functions to “habuma”, perhaps doing it with a JSP tag isn’t ideal. Sure, it’ll keep the link from being rendered in the view. But nothing’s stopping anyone from manually entering the /admin URL in the browser’s address line.

Drawing on what you learned earlier in this chapter, that should be an easy thing to fix. Adding a new call to the antMatchers() method in the security configuration will tighten security around the /admin URL:

.antMatchers("/admin")

.access("isAuthenticated() and principal.username=='habuma'");

Now the admin functionality is locked down. The URL is secured and the link to the URL won’t appear unless the user is authorized to use it. But to do that, you had to declare the SpEL expression in two places—in the security configuration and in the <security:authorize> tag’saccess attribute. Is there any way to eliminate that duplication and still prevent the link to the administrative functions from being rendered unless the rule is met?

That’s what the <security:authorize> tag’s url attribute is for. Unlike the access attribute where the security constraint is explicitly declared, the url attribute indirectly refers to the security constraints for a given URL pattern. Since you’ve already declared security constraints for /admin in the Spring Security configuration, you can use the url attribute like this:

<security:authorize url="/admin">

<spring:url value="/admin" var="admin_url" />

<br/><a href="${admin_url}">Admin</a>

</security:authorize>

Since the /admin URL is restricted to only authenticated users whose principal’s username is “habuma”, the body of the <security:authorize> tag will only be rendered if those same conditions are met. The expression was configured in one place (in the security configuration), but used in two places.

Spring Security’s JSP tag library comes in very handy, especially when it comes to conditionally rendering view elements to only those users who are allowed to see them. But if you’ve chosen Thymeleaf instead of JSP for your views, then you’re not out of luck. You’ve already seen how Thymeleaf’s Spring dialect will automatically add a hidden CSRF token field to your forms. Now let’s look at how Thymeleaf supports Spring Security.

9.5.2. Working with Thymeleaf’s Spring Security dialect

Much like Spring Security’s JSP tag library, Thymeleaf’s security dialect offers conditional rendering and the ability to render authentication details. Table 9.8 lists the attributes provided by the security dialect.

Table 9.8. Thymeleaf’s security dialect offers attributes that mirror much of Spring Security’s tag library

Attribute

What it does

sec:authentication

Renders properties of the authentication object. Similar to Spring Security’s <sec:authentication/> JSP tag.

sec:authorize

Conditionally renders content based on evaluation of an expression. Similar to Spring Security’s <sec:authorize/> JSP tag.

sec:authorize-acl

Conditionally renders content based on evaluation of an expression. Similar to Spring Security’s <sec:accesscontrollist/> JSP tag.

sec:authorize-expr

An alias for the sec:authorize attribute.

sec:authorize-url

Conditionally renders content based on evaluation of security rules associated with a given URL path. Similar to Spring Security’s <sec:authorize/> JSP tag when using the url attribute.

In order to use the security dialect, you’ll need to make sure that the Thymeleaf Extras Spring Security module is in your application’s classpath. Then you’ll need to register the SpringSecurityDialect with the SpringTemplateEngine in your configuration. Listing 9.10 shows the@Bean method that declares the SpringTemplate-Engine bean, including the SpringSecurityDialect.

Listing 9.10. Registering Thymeleaf’s Spring Security dialect

With the security dialect, you’re almost ready to start using its attributes in your Thymeleaf templates. First, declare the security namespace in the templates where you’ll be using those attributes:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"

xmlns:th="http://www.thymeleaf.org"

xmlns:sec=

"http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">

...

</html>

Here the standard Thymeleaf dialect is assigned to the th prefix as before, and the security dialect is assigned to the sec prefix.

Now you can use the Thymeleaf attributes however you see fit. For example, suppose that you want to render text saying “Hello” to the user if the user is authenticated. The following snippet from a Thymeleaf template will do the trick:

<div sec:authorize="isAuthenticated()">

Hello <span sec:authentication="name">someone</span>

</div>

The sec:authorize attribute takes a SpEL expression. If that expression evaluates to true, then the body of the element will be rendered. In this case, the expression is isAuthenticated(), so the body of the <div> tag will be rendered only if the user is authenticated. With regard to the body, it says “Hello” to the authentication’s name property.

As you’ll recall, Spring Security’s <sec:authorize> JSP tag has a url attribute that causes its body to be conditionally rendered based on the authorizations associated with a given URL path. With Thymeleaf, you can accomplish the same thing with the sec:authorize-url attribute. For example, the following Thymeleaf snippet accomplishes the same thing you previously used the <sec:authorize> JSP tag and url attribute for:

<span sec:authorize-url="/admin">

<br/><a th:href="@{/admin}">Admin</a>

</span>

Assuming that the user has authorization to access /admin, then a link to the admin page will be rendered; otherwise it won’t.

9.6. Summary

Security is a crucial aspect of many applications. Spring Security provides a mechanism for securing your application that’s simple, flexible, and powerful.

Using a series of servlet filters, Spring Security can control access to web resources, including Spring MVC controllers. But thanks to Spring Security’s Java configuration model, you don’t need to deal with those filters directly. Web security can be declared concisely.

When it comes to authenticating users, Spring Security offers several options. You saw how to configure authentication against an in-memory user store, a relational database, and LDAP directory servers. And when your authentication needs don’t fit any of those options, you saw how to create and configure a custom user-details service.

Over the past few chapters, you’ve seen how Spring fits into the front end of an application. Coming up in the next section, we’ll move a bit deeper down the stack and see how Spring plays a part in the back end. That exploration will start in the next chapter with a look at Spring’s JDBC abstraction.