Authenticating Users with Spring Security - Securing Your Application with Spring Security - PROFESSIONAL JAVA FOR WEB APPLICATIONS (2014)

PROFESSIONAL JAVA FOR WEB APPLICATIONS (2014)

Part IV Securing Your Application with Spring Security

Chapter 26 Authenticating Users with Spring Security

IN THIS CHAPTER

· Adding Spring Security Authentication to your application

· Using Form Login, JDBC, LDAP, and OpenID

· Protecting against session fixation and limiting user sessions

· Remembering users between sessions

· Creating a custom authentication provider

· Mitigating cross-site request forgery attacks

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

You can find the wrox.com code downloads for this chapter at http://www.wrox.com/go/projavaforwebapps on the Download Code tab. The code for this chapter is divided into the following major examples:

· Authentication-App Project

· Customer-Support-v19 Project

NEW MAVEN DEPENDENCIES FOR THIS CHAPTER

In addition to the Maven dependencies introduced in previous chapters, you also need the following Maven dependencies:

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-web</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-config</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-crypto</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

You no longer need the following dependency because Spring Security includes a BCrypt implementation in its distribution.

<dependency>

<groupId>org.mindrot</groupId>

<artifactId>jbcrypt</artifactId>

<version>0.3m</version>

</dependency>

CHOOSING AND CONFIGURING AN AUTHENTICATION PROVIDER

One of the first things you must do is choose which mechanism to use to authenticate your users. As you saw in the previous chapter, Spring Security has many built-in AuthenticationProvider implementations that support several different mechanisms. You can also create your own implementation, making the possibilities limitless. After you choose a mechanism, you simply need to configure it and set up your security context. In this section, you explore the various built-in providers and configuration of Spring Security authentication. In the next section, you create your own AuthenticationProvider implementation to perform more customized authentication steps.

NOTE If you have not read Chapter 5 and you are unfamiliar with the concepts of HttpSessions, session identifiers, or session fixation attacks and other session vulnerabilities, it is strongly recommended that you go back and read Chapter 5 before continuing. There are concepts mentioned in this chapter that you may not understand otherwise.

Configuring a User Details Provider

One of the simplest AuthenticationProvider implementations you can use is the org.springframework.security.authentication.dao.DaoAuthenticationProvider. The core concept of this provider is that it uses a Data Access Object, in the form of anorg.springframework.security.core.userdetails.UserDetailsService implementation, to retrieve org.springframework.security.core.userdetails.UserDetails objects by username.

The UserDetails object contains information about a user, such as the username and password, the GrantedAuthoritys, and whether the user is enabled, expired, and locked out. These objects working together form a generic authentication provider. Instead of worrying about the details of authenticating a user, you simply provide a mechanism for retrieving details about your users, allowing Spring Security to manage the authentication process. Of course, you can also choose from various default UserDetailsServiceimplementations.

The easiest way to get started with the DaoAuthenticationProvider is to employ the org.springframework.security.provisioning.InMemoryUserDetailsManager. This simple implementation is not intended for production use, but it’s perfect for simple test applications and demonstrations — such as the Authentication-App project available for download from the wrox.com code download site.

There’s a key difference in how you configure Spring Security versus how you configure Spring Framework. Spring Security operates through a series of Filter implementations that handle the various implementation details behind the scenes. This is necessary to ensure that all requests to your application are properly intercepted and secured as appropriate. Because of this, you do not configure Spring Security in your separate DispatcherServlet application contexts. Instead, you always configure it in your root application context, even if you intend to secure your different DispatcherServlet application contexts with different security principles. Configuring Spring Security consists of two key steps: registering the filters and setting up the security rules.

Setting Up the Spring Security Filters

For registering the filters using a Java configuration, Spring Security comes with an abstract WebApplicationInitializer implementation that takes care of this for you. Remember, WebApplicationInitializer is the Spring Framework interface that it uses to initialize Servlet 3.0 and newer applications. Spring’s ServletContainerInitializer implementation discovers all WebApplicationInitializer implementations and initializes them. In previous chapters, you created a Bootstrap class that implemented WebApplicationInitializer to register all your Spring Framework listeners and Servlets.

When using Spring Security’s Java configuration, all you have to do is extend org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer. It takes care of registering all the proper security-related filters. However, you must be careful to order all the filters in your application appropriately. The filters in your application must execute in the following order:

· Filters that handle logging of nearly any type, such as logging requests or adding fish tags to the Log4j 2 ThreadContext

· All Spring Security filters

· Filters that handle security-sensitive logging, such as adding the current user to the Log4j 2 ThreadContext

· Filters that handle multitenancy decisions (In a multitenant application, you need to determine which tenant a request belongs to as early as possible.)

· All other filters in their appropriate orders

In the simple Authentication-App project, you aren’t using any filters, so you don’t have to worry about ordering just yet. You look at that in the next section. For now, just create a SecurityBootstrap class that extends AbstractSecurityWebApplicationInitializer.

public class SecurityBootstrap extends AbstractSecurityWebApplicationInitializer

{

}

Notice that the class doesn’t have any fields or methods; that’s because it doesn’t need any. This is enough to register Spring Security’s filters. It also sets the session tracking mode to cookies only. If you choose a different way of tracking sessions (URLs or SSL session IDs), you need to override the getSessionTrackingModes method to indicate this:

public class SecurityBootstrap extends AbstractSecurityWebApplicationInitializer

{

@Override

protected Set<SessionTrackingMode> getSessionTrackingModes()

{

return EnumSet.of(SessionTrackingMode.SSL);

}

}

However, using session cookies is the simplest solution and works fine for this purpose, so accepting the default is all you need to do. If you aren’t using Java configuration, simply add Spring Security’s DelegatingFilterProxy to your deployment descriptor. This class is a filter proxy that wraps around all Spring Security’s filters and orders them internally.

<filter>

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

<filter-class>

org.springframework.web.filter.DelegatingFilterProxy

</filter-class>

</filter>

<filter-mapping>

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

<url-pattern>/*</url-pattern>

<dispatcher>ERROR</dispatcher>

<dispatcher>REQUEST</dispatcher>

</filter-mapping>

Remember, if you choose to go this route, the DelegatingFilterProxy must still appear in the order previously mentioned, where the Spring Security filters belong. If you are programmatically registering filters anywhere else, you probably need to stick to extendingAbstractSecurityWebApplicationInitializer.

Configuring the Login Mechanism and Protected URLs

Now that the Spring Security filters have been registered, you can proceed with configuring Spring Security for your application. Placing this in the root application context would normally involve adding more code to the RootContextConfiguration you created and have used since Part II. However, that class is getting a little crowded, and the security configuration could eventually get quite large, so an alternative and attractive approach is to create a new configuration class and import it from the RootContextConfiguration.

...

@ComponentScan(

basePackages = "com.wrox.site",

excludeFilters =

@ComponentScan.Filter({Controller.class, ControllerAdvice.class})

)

@Import({ SecurityConfiguration.class })

public class RootContextConfiguration implements

AsyncConfigurer, SchedulingConfigurer, TransactionManagementConfigurer

{

...

}

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder

.inMemoryAuthentication()

.withUser("John")

.password("password")

.authorities("USER")

.and()

.withUser("Margaret")

.password("green")

.authorities("USER", "ADMIN");

}

@Override

public void configure(WebSecurity security)

{

security.ignoring().antMatchers("/resource/**");

}

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.antMatchers("/signup", "/about", "/policies").permitAll()

.antMatchers("/secure/**").hasAuthority("USER")

.antMatchers("/admin/**").hasAuthority("ADMIN")

.anyRequest().authenticated()

.and().formLogin()

.loginPage("/login").failureUrl("/login?error")

.defaultSuccessUrl("/secure/")

.usernameParameter("username")

.passwordParameter("password")

.permitAll()

.and().logout()

.logoutUrl("/logout").logoutSuccessUrl("/login?loggedOut")

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().csrf().disable();

}

}

A lot is going on in this code, so let’s take it apart and examine it piece by piece.

There are two annotations that you should know about here that are very similar: @org.springframework.security.config.annotation.web.configuration.EnableWebSecurity and@org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity. The first annotation enables Spring Security web authentication and authorization features. The second annotation additionally enables integration with Spring Web MVC controllers. Unless you aren’t using Spring Web MVC, you should always use @EnableWebMvcSecurity. Any time you annotate a @Configuration class with @EnableWebSecurity or @EnableWebMvcSecurity, that class must either implementorg.springframework.security.config.annotation.web.WebSecurityConfigurer or extend org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter. Extending WebSecurityConfigurerAdapter is a much simpler approach, and there’s no reason you should ever need to do otherwise.

The configure(AuthenticationManagerBuilder) method sets up the AuthenticationProvider that you should use for authenticating users. You don’t have to actually use the InMemoryUserDetailsManager directly to use an in-memory user database. Both the Java and XML configuration options provide shortcuts for configuring the InMemoryUserDetailsManager. In this method, you create two default users in memory and assign the appropriate GrantedAuthoritys. Yes, this is an authorization detail, but sometimes it’s hard to keep these details from spilling over.

NOTE You probably noticed that the configure(AuthenticationManagerBuilder) method refers to an authentication manager, whereas the book has been referring to an authentication provider. Technically, Spring Security’s authentication power resides within an org.springframework.security.authentication.AuthenticationManager implementation. Although you could technically implement this interface, you never should. The default and only implementation,org.springframework.security.authentication.ProviderManager, delegates to one or more AuthenticationProvider implementations. This enables you to have multiple ways to authenticate users. Later in this section, you look at an example of this: “remember me” authentication.

The configure(WebSecurity) method is fairly simple. In this case, all it does is keep Spring Security from evaluating access to resources (JavaScript, style sheets, images, and so on) for security concerns. configure(HttpSecurity) does most of the hard work. First, it defines several URL patterns and how they are protected. Again, this is an authorization detail, but it’s necessary to set up some minimal level of authorization to make Spring Security require users to log in. The permitAll invocation instructs Spring Security to allow all access to the /signup, /about, and /policies URLs. Everything under the /secure/ URL requires the user to have the USER permissions, whereas access to /admin/ and everything under it requires the ADMIN permission. Any other request simply requires authentication, regardless of permissions.

Next, calling formLogin begins the process of configuring username and password authentication by way of a login form. It sets up the URL where the login form should reside; the URL where users go when authentication fails; and the name of the request parameters for the username and password in the submitted login form. Finally, calling logout configures the URL that should trigger a logout and the URL to send the user to after they have logged out. The permitAll invocations in these two places ensure that users can access the login and logout URLs without first authenticating (for obvious reasons). Cross-Site Request Forgery (CSRF) protection is enabled in Java configuration by default. (It is disabled in XML configuration by default.) CSRF protection is a complex topic covered in the next section, so it’s disabled for now using the call to csrf followed by the call to disable.

NOTE You probably wondered about the difference between ignoring URLs in configure(WebSecurity) and permitting all access to URLs in configure(HttpSecurity). Ignoring URLs makes those URLs skip most of Spring Security’s internal filters. This is important because you want access to static resources to be as fast as possible, and skipping the Spring Security internal filters ensures this speed. Permitting all access to URLs still sends requests to those URLs through the Spring Security internal filters, which adds necessary overhead for secured URLs.

The available options when configuring these settings are too numerous to list them all. The API documentation is your friend, and you should reference it often. The process that Spring Security follows here is fairly simple. If Spring Security detects users attempting to access a secured URL, it redirects them to the configured login URL.

You must supply the view that implements the login form and ensure that it submits using POST to the same URL. However, you do not have to implement the code that handles the submitted form. When Spring Security detects a POST to the configured URL, it extracts the username and password using the configured parameters, finds a configured AuthenticationProvider that supports authentication using an org.springframework.security.authentication.UsernamePasswordAuthenticationToken (in this case, that provider is theDaoAuthenticationProvider with the InMemoryUserDetailsManager), and authenticates the user with that provider. If authentication fails, Spring Security redirects the user to the failure URL. If authentication succeeds, it sends the user to the original intended URL (or the default URL if users came straight to the login screen).

Finally, if Spring Security detects a request to the configured logout URL, it clears the authentication, invalidates the HttpSession, deletes the session cookie, and redirects the user to the configured success URL.

As you can see here, the Spring Security Java configuration uses a fluent API, which is in stark contrast to the Spring Framework Java configuration. This is by necessity, not just by design. It makes configuring the various (and sometimes duplicate) components of security fairly simple, and your IDE’s code hinting can help you figure out what your options are for each component. However, not everyone likes fluent APIs, and some developers prefer to take a different approach. The following XML configuration is identical to the Java configuration in the Authentication-App. You could import it from your RootContextConfiguration using @ImportResource instead of @Import.

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-3.2.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<authentication-manager>

<authentication-provider>

<user-service>

<user name="John" authorities="USER" password="password" />

<user name="Margaret" authorities="USER,ADMIN" password="green" />

</user-service>

</authentication-provider>

</authentication-manager>

<http security="none" pattern="/resource/**" />

<http use-expressions="true">

<intercept-url pattern="/signup" access="permitAll" />

<intercept-url pattern="/about" access="permitAll" />

<intercept-url pattern="/policies" access="permitAll" />

<intercept-url pattern="/login" access="permitAll" />

<intercept-url pattern="/logout" access="permitAll" />

<intercept-url pattern="/secure/**" access="hasAuthority('USER')" />

<intercept-url pattern="/admin/**" access="hasAuthority('ADMIN')" />

<form-login login-page="/login"

login-processing-url="/login"

authentication-failure-url="/login?error"

default-target-url="/secure/"

username-parameter="username"

password-parameter="password" />

<logout logout-url="/logout"

logout-success-url="/login?loggedOut"

invalidate-session="true"

delete-cookies="JSESSIONID" />

</http>

</beans:beans>

Setting Up Session Fixation Protection

You should be familiar with the problem with HTTP session fixation attacks and how they can leave your users vulnerable to having their accounts on your site compromised. In Chapter 5 you explored ways to mitigate these attacks. Thankfully, Spring Security comes with built-in utilities to mitigate these attacks as well. Prior to Servlet 3.1, developers had to resort to creating new sessions, copying the data from one session to the next, and then invalidating the old session. Spring Security made this task very easy — just a simple configuration switch controlled the behavior. By default, session fixation protection is enabled in Spring Security because session fixation attacks are a major problem in web applications. Servlet 3.1 added the changeSessionId method to HttpServletRequest to make session fixation protection vastly simpler, and Spring Security 3.2 includes support for this method. You can fine-tune this process in your configuration.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(HttpSecurity security) throws Exception

{

...

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().sessionManagement()

.sessionFixation().changeSessionId()

.and().csrf().disable();

}

...

}

The previous code retrieves the session management configuration and instructs Spring Security to use the Servlet 3.1 changeSessionId method for protecting against session fixation attacks. To be clear, this isn’t actually necessary. By default, Spring Security uses thechangeSessionId mechanism for applications running on Servlet 3.1 containers and newer. You can also select

· newSession, which creates a new session without copying existing session attributes.

· migrateSession, which creates a new session and copies all existing attributes.

· none, which disables session fixation protection.

migrateSession is the default when your application runs in a Servlet 3.0 or older container. Configuring session fixation protection in XML is just as easy:

<http use-expressions="true">

...

<session-management session-fixation-protection="migrateSession" />

</http>

Limiting the Number of User Sessions

Sometimes it’s desirable to limit the number of sessions a single user can hold at once. This prevents them from using your site from multiple computers, browsers, or locations at once, and is typically used as a security feature to ensure that only one person uses a given set of credentials. Using the session management configuration, you can restrict the number of simultaneous sessions. The following Java and XML configurations enable this feature.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(HttpSecurity security) throws Exception

{

...

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().sessionManagement()

.sessionFixation().changeSessionId()

.maximumSessions(1).expiredUrl("/login?maxSessions")

.and().and().csrf().disable();

}

...

}

<http use-expressions="true">

...

<session-management session-fixation-protection="changeSessionId">

<concurrency-control max-sessions="1"

expired-url="/login?maxSessions" />

</session-management>

</http>

When configured with a maximum number of sessions, the default behavior is to expire an existing session if a user logs in again. In this case, when the holder of the original session attempts to access that session again, they will be redirected to /login?maxSessions. This isn’t always the desired behavior. Instead, you can instruct Spring Security to prevent the second login and return an unauthorized response, allowing the original session to continue unabated.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(HttpSecurity security) throws Exception

{

...

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().sessionManagement()

.sessionFixation().changeSessionId()

.maximumSessions(1).maxSessionsPreventsLogin(true)

.and().and().csrf().disable();

}

...

}

<http use-expressions="true">

...

<session-management session-fixation-protection="changeSessionId">

<concurrency-control max-sessions="1"

error-if-maximum-exceeded="true" />

</session-management>

</http>

Note that the configuration option for this behavior has a different name in the new Java configuration than it has in the XML configuration. Of course, you can enable session concurrency control without changing the default session fixation protection. Just leave off the sessionFixation().option() code or the session-fixation-protection XML attribute.

To enable concurrency control, you must also configure a special Spring Security listener that publishes HttpSession-related events. This allows Spring Security to build a session registry that it can use to detect concurrent sessions. The simplest way to enable this listener is to override the enableHttpSessionEventPublisher method in your SecurityBootstrap class.

public class SecurityBootstrap extends AbstractSecurityWebApplicationInitializer

{

@Override

protected boolean enableHttpSessionEventPublisher()

{

return true;

}

}

If you use an XML configuration instead, you can just manually add the listener to your deployment descriptor.

<listener>

<listener-class>

org.springframework.security.web.session.HttpSessionEventPublisher

</listener-class>

</listener>

Whichever approach you take to configure this listener, if you deploy your application in a clustered environment, you must take care to properly synchronize sessions among all the nodes in your cluster. Otherwise, the listener will not be aware of all the sessions and it will not properly limit the number of sessions a user can hold.

The Authentication-App contains a basic controller that responds to the simple URLs that Spring Security is protecting. The details of this controller are unimportant and contain nothing that you aren’t already used to. To test it out, simply follow these steps:

1. Compile the project and start Tomcat from your IDE.

2. Go to http://localhost:8080/authentication/about, http://localhost:8080/authentication/signup, and http://localhost:8080/authentication/policies. You can access these pages without logging in.

3. Now go to http://localhost:8080/authentication/secure/ or any URL under it, and Spring Security asks you to log in. Enter in bad credentials and Spring Security returns you to the login screen and tells you the login failed.

4. Enter in correct credentials and you should be granted access.

5. Click the Log Out link to log out and then log in with a different set of credentials. Logged in as John, try to go to http://localhost:8080/authentication/admin/, and Spring Security prevents access.

6. Log out and log back in as Margaret and Spring Security should let you access the admin pages.

Using the JDBC User Details Service

As mentioned earlier, the InMemoryUserDetailsManager is just a test implementation that you should never use in a production application. It was useful for this demonstration, but what other options are available? If you store your users in a database (typical for most applications), the org.springframework.security.provisioning.JdbcUserDetailsManager provides a simple mechanism for obtaining UserDetails objects from your database. You simply give it a DataSource and configure SQL queries for obtaining users and their permissions from the database.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Inject DataSource dataSource;

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder

.jdbcAuthentication()

.dataSource(this.dataSource)

.usersByUsernameQuery("SELECT Username, Password, Enabled " +

"FROM User WHERE Username = ?")

.authoritiesByUsernameQuery("SELECT Username, Permission " +

"FROM UserPermission WHERE Username = ?")

.passwordEncoder(new BCryptPasswordEncoder());

}

...

}

Most of this configuration should be self-explanatory. The “users by username” SQL query must return a result set with the columns containing the username, password, and enabled/disabled flag, in that order. The “authorities by username” SQL query must return a result set with the columns containing the username and authority name, in that order. Both queries must require exactly one parameter, the username of the user to locate.

Of course, you should never store users’ passwords in the database in plain text, and Spring Security’s org.springframework.security.crypto.password.PasswordEncoder interface provides a mechanism for comparing passwords to hashed passwords. All you have to do is return the hashed password column in your configured “users by username” query and provide the appropriate PasswordEncoder implementation to handle the hashed password format. The org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder provides a standard implementation for using the BCrypt hashing algorithm to compare user passwords.

The following XML configuration is identical to the Java configuration for the JDBC user details service.

<authentication-manager>

<authentication-provider>

<jdbc-user-service data-source-ref="dataSource"

users-by-username-query=

"SELECT Username, Password, Enabled FROM User WHERE Username = ?"

authorities-by-username-query=

"SELECT Username, Permission FROM UserPermission WHERE Username = ?" />

<password-encoder hash="bcrypt" />

</authentication-provider>

</authentication-manager>

Using Other User Details Services

In addition to the JdbcUserDetailsManager, there is also an org.springframework.security.ldap.userdetails.LdapUserDetailsManager and an LdapUserDetailsService, but these classes are purely for information retrieval. You cannot use them for the actual authentication process because they cannot obtain a password from an LDAP provider. You learn more about these next. There are no other bundled UserDetailsService implementations, but you may define your own fairly simply. Just create a UserDetailsService implementation, obtain an instance of it in your SecurityConfiguration class (either by creating it manually or injecting it), and add it to the security configuration.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Inject MyUserDetailsService myUserDetailsService;

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder.userDetailsService(this.myUserDetailsService)

.passwordEncoder(new BCryptPasswordEncoder());

}

...

}

Configuring your custom UserDetailsService using XML is essentially the same.

<authentication-manager>

<authentication-provider user-service-ref="myUserDetailsService">

<password-encoder hash="bcrypt" />

</authentication-provider>

</authentication-manager>

Working with LDAP and Active Directory Providers

To be sure, the DaoAuthenticationProvider class and corresponding UserDetailsService interface are very handy for quickly getting Spring Security up and running in simple situations. But real life is rarely that simple, and often much more complex authentication mechanisms are called for. Claims authentication using the Lightweight Directory Access Protocol is one example. For this, Spring Security comes with an org.springframework.security.ldap.authentication.LdapAuthenticationProvider.

This provider is necessary because you can’t always obtain a password (hashed or otherwise) from an LDAP server. For example, the most common authentication strategy is called Bind, where users essentially “log in” to the LDAP server, thereby establishing their identity. The LdapAuthenticationProvider authenticates directly with the LDAP server rather than redirecting users to an authentication server. It has several options, but you should never need to configure it directly. Instead, you can use the Java configuration shortcuts or XML namespace.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder

.ldapAuthentication()

.contextSource()

.url("ldap://ldap1.example.org:389/dc=example,dc=org

ldap://ldap2.example.org:389/dc=example,dc=org")

.managerDn("uid=admin,ou=system")

.managerPassword("bindPassword")

.and()

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

.userSearchBase("ou=people")

.groupSearchBase("ou=groups");

}

...

}

<ldap-server manager-dn="uid=admin,ou=system" manager-password="bindPassword"

url="ldap://ldap1.example.org:389/dc=example,dc=org

ldap://ldap2.example.org:389/dc=example,dc=org" />

<authentication-manager>

<ldap-authentication-provider user-search-filter="(uid={0})"

user-search-base="ou=people"

group-search-base="ou=groups" />

</authentication-manager>

These configurations assume that anonymous access is not enabled on the server, and thus provides a manager distinguished name and password with which to bind other users. If your server allows anonymous access, you could omit these values. Multiple servers for redundancy and high availability are specified by separating the URLs with a space, as is the LDAP convention. These configurations also assume that the users could be located in multiple nodes in the directory (a typical scenario), and specify a user search base and filter for locating users anywhere within the base node. If your users are always located on the same node, you could omit the search filter and base properties and instead use the userDnPatterns/user-dn-pattern properties. The group search base properties instruct Spring Security how to locate the LDAP groups to which your users belong. This is necessary if you also want to take advantage of the directory for claims-based authorization, but you can omit it otherwise. To further restrict the groups returned, you can specify a groupSearchFilter/group-search-filter.

WARNING LDAP is a complex topic that is outside the scope of this book. It is very easy to misconfigure an LDAP client of any type, and doing so can make your application insecure or inaccessible, depending on the mistakes made. You shouldalways consult your LDAP server administrator before attempting to integrate LDAP authentication into your application.

Earlier you read about the LdapUserDetailsManager and LdapUserDetailsService classes. These can obtain UserDetails from the LDAP server, but they cannot obtain password information for authenticating users. So what are they for? Well, you don’t strictly need them. The previous configuration works for authenticating users against an LDAP server. However, if you want to provide a “remember me” checkbox on your login screen in an LDAP-enabled application and use the XML configuration, you must also configure anLdapUserDetailsManager or LdapUserDetailsService. (There is a slight difference in how these classes resolve users that is outside the scope of this book.)

When a “remembered” user returns and is authenticated automatically, this service obtains the user’s details from the LDAP server (and ensures the user is not deleted, disabled, or locked out). You should configure the service with the same search and filter details as the LdapAuthenticationProvider. The Java configuration takes care of this for you automatically. You learn more about “remember me” authentication later in this section.

Spring Security also has built-in support for Windows Domain Active Directory authentication. This support uses LDAP and, thus, requires that you enable LDAP on your domain controllers. (It is enabled by default, but some domain administrators disable it to simplify security measures). It also means that the org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider is closely related to the LdapAuthenticationProvider — both extendorg.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.

Configuring the LdapAuthenticationProvider to properly connect to a Windows domain controller is a very complex task. Because the LDAP structure is always the same across all Windows domain controllers, Spring Security provides theActiveDirectoryLdapAuthenticationProvider to greatly simplify this task. It also translates the Microsoft-proprietary error codes returned by domain controllers into more useful error messages than the generic LdapAuthenticationProvider can provide. All you need to do is provide the default Windows domain name (used when the username does not contain an explicit domain) and the URL or URLs for the domain controller’s LDAP server(s), as demonstrated in the following Java and XML configurations.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder.authenticationProvider(

new ActiveDirectoryLdapAuthenticationProvider(

"example.com",

"ldap://dc1.example.com:389/ ldap://dc2.example.com:389/"

)

);

}

...

}

<beans:bean id="activeDirectoryProvider"

class="org.springframework.security.ldap.authentication.ad.

ActiveDirectoryLdapAuthenticationProvider">

<beans:constructor-arg value="example.com" />

<beans:constructor-arg value="ldap://dc1.example.com:389/

ldap://dc2.example.org:com/"/>

</beans:bean>

<authentication-manager>

<authentication-provider ref="activeDirectoryProvider" />

</authentication-manager>

NOTE To use Spring Security’s LDAP support, you need to add the following Maven dependency to your project, which also pulls in an external dependency on the Spring LDAP project.

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-ldap</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

Authenticating with OpenID

Spring Security also provides built-in support for OpenID authentication. If you aren’t interested in OpenID authentication, you can skip to the section “Remembering Users.” This part of the section assumes that you have some knowledge of how OpenID authentication works and refers to several OpenID concepts without elaborating on what they are. Because OpenID is a claims-based authentication system that requires users to be redirected to the provider, you don’t need an AuthenticationProviderimplementation, and you won’t configure a form login mechanism. Instead, you configure an OpenID login mechanism that redirects the user to the OpenID provider’s login form when necessary.

You also need a page to handle the first stage of OpenID login — presenting your users with the list of OpenID providers you support and a field for them to enter their OpenID identifier. When your users submit this form, Spring Security takes over, redirecting them to the proper provider URL and completing the callback authentication process when they return from the provider.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

@Override

public void configure(WebSecurity security)

{

security.ignoring().antMatchers("/resource/**");

}

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.antMatchers("/signup", "/about", "/policies").permitAll()

.antMatchers("/secure/**").hasAuthority("USER")

.antMatchers("/admin/**").hasAuthority("ADMIN")

.anyRequest().authenticated()

.and().openidLogin()

.loginPage("/login")

.failureUrl("/login?error")

.defaultSuccessUrl("/secure/")

.authenticationUserDetailsService(new MyUserDetailsService())

.attributeExchange("https://www.google.com/.*")

.attribute("firstname").required(true)

.type("http://axschema.org/namePerson/first")

.and()

.attribute("lastname").required(true)

.type("http://axschema.org/namePerson/last")

.and()

.attribute("email").required(true)

.type("http://axschema.org/contact/email")

.and()

.and()

.attributeExchange(".*yahoo.com.*")

.attribute("fullname").required(true)

.type("http://axschema.org/namePerson")

.and()

.attribute("email").required(true)

.type("http://axschema.org/contact/email")

.and()

.and()

.and().logout()

.logoutUrl("/logout").logoutSuccessUrl("/login?loggedOut")

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().sessionManagement()

.sessionFixation().changeSessionId()

.maximumSessions(1).maxSessionsPreventsLogin(true)

.and().and().csrf().disable();

}

}

This configuration sets up the OpenID login mechanism and configures attribute exchanges with two common providers. If you need to configure attribute exchanges with many providers, it probably makes more sense to create private methods to handle the setup for each. The configuration also sets up the familiar login page, failure URL, and default success URL, which you previously configured for the form login mechanism.

The loginPage is the view you provide containing the OpenID identifier field (the field name must be openid_identifier) and buttons for all of the providers you support. The MyUserDetailsService is a theoreticalorg.springframework.security.core.userdetails.AuthenticationUserDetailsService implementation of your design capable of accepting an org.springframework.security.openid.OpenIDAuthenticationToken and returning a corresponding UserDetails object. You must provide a UserDetailsService implementation if you decide to also enable “remember me” authentication. You can use the same UserDetailsService for both of these purposes.

As you can tell, the details involved in configuring OpenID in Spring Security are numerous and could fill an entire chapter. If you want to see more examples, the sample code in the Spring Security GitHub repository is a great resource.

NOTE To use Spring Security’s OpenID support, you need to add the following Maven dependencies to your project (the first as a direct dependency, the others to force usage of more recent transient dependencies with many bug fixes and security improvements). This dependency pulls in many transient dependencies.

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-openid</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

<dependency>

<groupId>commons-codec</groupId>

<artifactId>commons-codec</artifactId>

<version>1.9</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpclient</artifactId>

<version>4.3.1</version>

<scope>runtime</scope>

</dependency>

Remembering Users

Many sites offer remember-me authentication. The concept typically involves a check box on the login screen where users can indicate that the site should remember their browser and log them in automatically next time. This works by saving a cookie in the users’ browser to identify them on their next visit.

To be clear, this is a security vulnerability waiting to happen. All an attacker must do is obtain access to the user’s cookies and he can then access your application on behalf of the user. You can somewhat lessen the security concerns by having remember-me tokens that expire quickly or can be used only once, but in reality the vulnerability still exists. You should never use remember-me authentication when protecting any kind of sensitive information, such as healthcare data, financial or tax records, or employment information. However, for low-security situations (such as user forums and news sites with comment capability) this compromise is sometimes acceptable in the name of user convenience.

Spring Security offers remember-me authentication by way of a special org.springframework.security.authentication.RememberMeAuthenticationProvider. If you decide you want to enable remember-me services, add a check box to your login screen with the field nameremember-me (or _spring_security_remember_me when using XML configuration), and then simply switch on remember-me services in your configuration:

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Override

protected void configure(HttpSecurity security) throws Exception

{

...

.maximumSessions(1).maxSessionsPreventsLogin(true)

.and().and().csrf().disable()

.rememberMe().key("myApplicationName");

}

...

}

<http use-expressions="true">

...

<remember-me key="myApplicationName"/>

</http>

If your login process takes place over SSL (as it should), set useSecureCookie (or use-secure-cookie) to true in order to increase security. This eliminates one avenue with which attackers can obtain your users’ remember-me cookies. You can also usetokenValiditySeconds (or token-validity-seconds) to control how long remember-me tokens are valid before they expire, further limiting a stolen token’s usefulness. As discussed previously, remember-me services require a UserDetailsService implementation in order to work properly. Without this, remember-me services cannot function. If you have multiple UserDetailsServices, you must pick the one that you want to support remember-me services for and specify it with userDetailsService (or user-service-ref).

There is also a slightly more secure version of the remember-me services that requires a DataSource and a table with a specific schema. Use of this feature is detailed in the Spring Security documentation, but in general this author recommends that you never use remember-me services.

Exploring Other Authentication Providers

Several other built-in AuthenticationProviders come with Spring Security. org.springframework.security.cas.authentication.CasAuthenticationProvider manages authentication using Jasig Central Authentication Service. Bothorg.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider and org.springframework.security.authentication.jaas.JaasAuthenticationProvider use a Java Authentication and Authorization Service provider; however, JaasAuthenticationProviderrelies on a particular JAAS implementation that might not be available on all Java Virtual Machines, whereas the DefaultJaasAuthenticationProvider can work with any JAAS implementation.

org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider is an interesting implementation. It operates on the basis that you’ll encounter some situations in which you cannot or do not want to use Spring Security for authentication purposes, but you do want to take advantage of its authorization capabilities. Such scenarios could include client certificate authentication (which only the Servlet container can handle), SiteMinder authentication, and other authentication handled directly within the Servlet container. When configuring pre-authentication, you must carefully tell Spring Security how to properly and securely recognize pre-authenticated requests.

org.springframework.security.authentication.rcp.RemoteAuthenticationProvider is a very similar provider that applies to slightly different situations in which authentication is also handled externally and Spring Security is still responsible for authorization.

The final built-in provider is org.springframework.security.access.intercept.RunAsImplAuthenticationProvider. You can use this to temporarily replace the current Authentication with an Authentication for a different Principal with (potentially) differentGrantedAuthoritys. One example use case for this is to run certain code in privileged mode while running the majority of your code with a user holding fewer permissions.

The configuration details for these providers are numerous, and trying to print them all in this book would not make sense. You will find that the Spring Security API documentation and GitHub repository samples mentioned earlier are very useful for learning about these providers. You should also consult the Spring Security reference documentation for more information.

WRITING YOUR OWN AUTHENTICATION PROVIDER

As useful as all these built-in authentication mechanisms are, sometimes they’re not enough. Thankfully, writing your own AuthenticationProvider is fairly simple, and in this section you do just that. You can follow along in the Customer-Support-v19 project, available for download from the wrox.com code download site. This is the project you have been progressively improving and refactoring since Chapter 3, and the only tasks that remain to complete it are adding authentication and authorization. If you have not been working on this project since the beginning, don’t worry! The project is self-contained and you can run it as soon as you download it. This section covers only the changes made to the project to add authentication to the Customer Support application.

Early on, you added a rudimentary system for username and password form authentication to the application. This was necessary to complete some of the more basic features required in the project. Now, you can replace this home-baked authentication with the enterprise features of Spring Security. This section shows you how to do this and also introduces you to Cross-Site Request Forgery attacks and the mitigation features that Spring Security 3.2 introduces.

Bootstrapping in the Correct Order

As you may recall, the Spring Framework, Servlet, and filter configuration in the Customer Support application is much more complex than what you created in the sample Authentication-App. One important problem is that the LoggingFilter performs two tasks: adding a fish tag (in the form of a UUID) and the logged-in username to the Log4j 2 ThreadContext. The problem with this is that the username does not become available until after the Spring Security filter chain executes, meaning that filter code must run last. However, running the fish tagging code last would exclude Spring Security logging from including the fish tag, which is not desirable. Because of this, you must split the logging filter into two filters and bootstrap everything in the proper order.

Splitting the Logging Filter

Splitting the logging filter is not a complicated task. Replace the LoggingFilter with a PreSecurityLoggingFilter and a PostSecurityLoggingFilter. The PreSecurityLoggingFilter is responsible for setting up the fish tag and clearing both the fish tag and the username when the request is complete.

public class PreSecurityLoggingFilter implements Filter

{

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException

{

String id = UUID.randomUUID().toString();

ThreadContext.put("id", id);

try

{

((HttpServletResponse)response).setHeader("X-Wrox-Request-Id", id);

chain.doFilter(request, response);

}

finally

{

ThreadContext.remove("id");

ThreadContext.remove("username");

}

}

...

}

The PostSecurityLoggingFilter is even simpler. It uses a static method on Spring Security’s org.springframework.security.core.context.SecurityContextHolder class to obtain the current org.springframework.security.core.context.SecurityContext. This context is request-local,and holds the Authentication that belongs to the current request and HTTP session. The PostSecurityLoggingFilter delegates removal of the username from the ThreadContext to the PreSecurityLoggingFilter so that the username can exist on the ThreadContext for as long as possible.

public class PostSecurityLoggingFilter implements Filter

{

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException

{

SecurityContext context = SecurityContextHolder.getContext();

if(context != null && context.getAuthentication() != null)

ThreadContext.put("username", context.getAuthentication().getName());

chain.doFilter(request, response);

}

...

}

Ordering Multiple Bootstrap Classes

Now that you have split up the logging filter, it’s important that all your application components are initialized in the proper order. The PreSecurityLoggingFilter must be registered first, followed by the Spring Security filter chain and finally thePostSecurityLoggingFilter. Because Spring Framework registers only a ServletContextListener and several Servlets, it doesn’t really matter where in that process you initialize Spring Framework. It won’t interfere with the filter execution order.

Ordering multiple Spring Framework WebApplicationInitializers is really easy. All you have to do is annotate the classes with @org.springframework.core.annotation.Order, providing a value corresponding to the precedence of the initializer. The lowest value corresponds to the highest precedence, so -2,147,483,648 is the highest precedence possible. The highest value corresponds to the lowest precedence, so the lowest precedence possible is 2,147,483,647. For your purposes, it’s easiest just to number the three bootstrap classes you need as 1, 2 and 3. The classes are grouped in the com.wrox.config.bootstrap package, and FrameworkBootstrap replaces the old Bootstrap class from previous versions. The ellipsis represents the Spring Framework bootstrap code that hasn’t changed. The only major differences are that the previous AuthenticationFilter is gone and the PreSecurityLoggingFilter, not the previous LoggingFilter, is registered here.

@Order(1)

public class FrameworkBootstrap implements WebApplicationInitializer

{

private static final Logger log = LogManager.getLogger();

@Override

public void onStartup(ServletContext container) throws ServletException

{

log.info("Executing framework bootstrap.");

...

FilterRegistration.Dynamic registration = container.addFilter(

"preSecurityLoggingFilter", new PreSecurityLoggingFilter()

);

registration.addMappingForUrlPatterns(null, false, "/*");

}

}

Next, you need to bootstrap Spring Security’s filter chain. Other than the addition of the @Order annotation and a logging statement (to demonstrate that ordering is correct), this is identical to the SecurityBootstrap you created for the Authentication-App project.

@Order(2)

public class SecurityBootstrap extends AbstractSecurityWebApplicationInitializer

{

private static final Logger log = LogManager.getLogger();

@Override

protected boolean enableHttpSessionEventPublisher()

{

log.info("Executing security bootstrap.");

return true;

}

}

The final step in the bootstrapping process is to register the PostSecurityLoggingFilter in the new LoggingBootstrap class. Now when Spring Framework starts up, it executes these bootstrap classes in the order they appear here.

@Order(3)

public class LoggingBootstrap implements WebApplicationInitializer

{

private static final Logger log = LogManager.getLogger();

@Override

public void onStartup(ServletContext container) throws ServletException

{

log.info("Executing logging bootstrap.");

FilterRegistration.Dynamic registration = container.addFilter(

"postSecurityLoggingFilter", new PostSecurityLoggingFilter()

);

registration.addMappingForUrlPatterns(null, false, "/*");

}

}

Creating and Configuring a Provider

Now that logging and Spring Security are set up to bootstrap in the correct order, it’s time to create your own AuthenticationProvider implementation. Because you use the UserPrincipal entity as an identity throughout the application, the easiest thing to do is simply update UserPrincipal to implement Authentication. Then you can return it from your AuthenticationProvider implementation.

NOTE Making UserPrincipal extend Authentication is just one approach. In the next chapter you explore another approach — extending UserDetails.

Converting the User Principal and Authentication Service

For the most part, the UserPrincipal remains largely unchanged. Its mapping is the same, and it still implements Principal and Serializable, but indirectly by way of implementing Authentication. The only major change is the addition of the getAuthorities, getPrincipal, getDetails, getCredentials, isAuthenticated, and setAuthenticated methods specified in Authentication. Their implementation is boilerplate. In the next chapter you map user authorities to a new database table that stores user permissions.

The most important changes are to the AuthenticationService interface, and its DefaultAuthenticationService implementation. AuthenticationService now extends AuthenticationProvider, overriding its authenticate method to clarify that this provider returns onlyUserPrincipals.

@Validated

public interface AuthenticationService extends AuthenticationProvider

{

@Override

UserPrincipal authenticate(Authentication authentication);

void saveUser(

@NotNull(message = "{validate.authenticate.saveUser}") @Valid

UserPrincipal principal,

String newPassword

);

}

The implementation of the saveUser method hasn’t changed any. authenticate has obviously changed a great deal, and the supports method indicates that this AuthenticationProvider can authenticate using only UsernamePasswordAuthenticationTokens. After casting theAuthentication to a UsernamePasswordAuthenticationToken and retrieving the username and password, authenticate erases the plain-text password stored in the token so that it can’t accidentally leak anywhere. It then retrieves the UserPrincipal and runs through the standard checks it previously ran through. After the user identity has been confirmed, it sets the authenticated flag to true (in bold) to confirm the authentication succeeded.

@Service

public class DefaultAuthenticationService implements AuthenticationService

{

...

@Override

@Transactional

public UserPrincipal authenticate(Authentication authentication)

{

UsernamePasswordAuthenticationToken credentials =

(UsernamePasswordAuthenticationToken)authentication;

String username = credentials.getPrincipal().toString();

String password = credentials.getCredentials().toString();

credentials.eraseCredentials();

UserPrincipal principal = this.userRepository.getByUsername(username);

if(principal == null)

{

log.warn("Authentication failed for non-existent user {}.", username);

return null;

}

if(!BCrypt.checkpw(

password,

new String(principal.getPassword(), StandardCharsets.UTF_8)

))

{

log.warn("Authentication failed for user {}.", username);

return null;

}

principal.setAuthenticated(true);

log.debug("User {} successfully authenticated.", username);

return principal;

}

@Override

public boolean supports(Class<?> c)

{

return c == UsernamePasswordAuthenticationToken.class;

}

...

}

Now you just need to configure Spring Security to use the AuthenticationService implementation as the AuthenticationProvider. This is demonstrated in the SecurityConfiguration class in Listing 26-1, which is @Imported from the RootContextConfiguration class. Notice that it lets Spring Framework inject the AuthenticationService bean automatically and then simply wires it in from the configure(AuthenticationManagerBuilder) method. The configure(WebSecurity) method excludes static resources and a possible favicon from Spring Security’s filter chain, whereas configure(HttpSecurity) requires authentication for all requests and sets up the form login and logout mechanisms, similar to the code in the Authentication-App. It does a few other things, too, which you’ll look at soon.

LISTING 26-1: SecurityConfiguration.java

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

@Inject AuthenticationService authenticationService;

@Bean

protected SessionRegistry sessionRegistryImpl()

{

return new SessionRegistryImpl();

}

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder.authenticationProvider(this.authenticationService);

}

@Override

public void configure(WebSecurity security)

{

security.ignoring().antMatchers("/resource/**", "/favicon.ico");

}

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.anyRequest().authenticated()

.and().formLogin()

.loginPage("/login").failureUrl("/login?loginFailed")

.defaultSuccessUrl("/ticket/list")

.usernameParameter("username")

.passwordParameter("password")

.permitAll()

.and().logout()

.logoutUrl("/logout").logoutSuccessUrl("/login?loggedOut")

.invalidateHttpSession(true).deleteCookies("JSESSIONID")

.permitAll()

.and().sessionManagement()

.sessionFixation().changeSessionId()

.maximumSessions(1).maxSessionsPreventsLogin(true)

.sessionRegistry(this.sessionRegistryImpl())

.and().and().csrf()

.requireCsrfProtectionMatcher((r) -> {

String m = r.getMethod();

return !r.getServletPath().startsWith("/services/") &&

("POST".equals(m) || "PUT".equals(m) ||

"DELETE".equals(m) || "PATCH".equals(m));

});

}

}

Replacing the Session Registry

You probably wondered why the SecurityConfiguration class in Listing 26-1 manually creates the Spring Security SessionRegistryImpl bean and then (in bold) injects it into the session management configuration. Won’t Spring Security create this bean automatically? Yes, Spring Security will do that, but in doing so the SessionRegistry won’t be exposed to the entire application as a bean. Early in the book you created your own com.wrox.site.SessionRegistry using SessionListener (an implementation of HttpSessionListener andHttpSessionIdListener). Spring Security’s SessionRegistry can replace this functionality completely. By manually creating the SessionRegistryImpl bean, you expose the bean to your application so that you can use it in other application beans outside of Spring Security classes. The SessionListController now uses Spring Security’s SessionRegistry instead of the legacy com.wrox.site.SessionRegistry.

@WebController

@RequestMapping("session")

public class SessionListController

{

@Inject SessionRegistry sessionRegistry;

@RequestMapping(value = "list", method = RequestMethod.GET)

public String list(Map<String, Object> model)

{

List<SessionInformation> sessions = new ArrayList<>();

for(Object principal : this.sessionRegistry.getAllPrincipals())

sessions.addAll(this.sessionRegistry.getAllSessions(principal, true));

model.put("timestamp", System.currentTimeMillis());

model.put("numberOfSessions", sessions.size());

model.put("sessionList", sessions);

return "session/list";

}

}

Using Spring Framework’s publish-subscribe messaging, Spring Security publishes a variety of messages when certain authentication events occur. These different events are all detailed in the API documentation, but the ones that are of the most use to you include the following:

· org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent, published when authentication fails for some reason. Its subclasses indicate more detailed reasons for the failure.

· org.springframework.security.authentication.event.AuthenticationSuccessEvent, published when authentication succeeds. However, this might also include automatic authentication events, such as remember-me authentication. If you want to know only about interactive authentication, use org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.

· org.springframework.security.web.authentication.session.SessionFixationProtectionEvent, published when session fixation protection causes the session to change (for example, session migration or changeSessionId).

· org.springframework.security.core.session.SessionDestroyedEvent, published when a session is destroyed (either on log out or session timeout).

Previously, the ChatEndpoint used the legacy com.wrox.site.SessionRegistry to subscribe to events regarding the destruction of sessions. You can learn about these events now simply by implementing ApplicationListener<SessionDestroyedEvent>. However, only singleton beans can implement ApplicationListener<?>. Because ChatEndpoint is not a singleton bean (instead, a new instance is created for every new WebSocket connection), it cannot implement this method. (Spring Framework logs a warning and ignores it.) To solve this problem, the com.wrox.site.chat.SessionDestroyedListener class acts as a proxy, listening for destroyed sessions and forwarding the events on to all active WebSocket connections.

Speaking of ChatEndpoint, it now uses the SecurityContextHolder to obtain the UserPrincipal in the EndpointConfigurator.

public static class EndpointConfigurator extends SpringConfigurator

{

...

@Override

public void modifyHandshake(ServerEndpointConfig config,

HandshakeRequest request,

HandshakeResponse response)

{

...

config.getUserProperties().put(

PRINCIPAL_KEY,

SecurityContextHolder.getContext().getAuthentication()

);

...

}

...

}

Modifying the Authentication Controller

The AuthenticationController is vastly simplified now. It doesn’t have to handle login or logout commands, so those methods have been removed. Spring Security takes care of all that for you. The only thing AuthenticationController must still do is render the login view (and only after checking the SecurityContextHolder to make sure the user isn’t already logged in). Although it never actually uses the LoginForm (because it doesn’t process a submitted login anymore), it still adds the LoginForm to the model so that you can use the Spring Framework <form:form> tag in the login view. You cannot use <form:form> without a command object.

@WebController

public class AuthenticationController

{

@RequestMapping(value = "login", method = RequestMethod.GET)

public ModelAndView login(Map<String, Object> model)

{

if(SecurityContextHolder.getContext().getAuthentication() instanceof

UserPrincipal)

return new ModelAndView(new RedirectView("/ticket/list", true, false));

model.put("loginForm", new LoginForm());

return new ModelAndView("login");

}

public static class LoginForm { ... }

}

Mitigating Cross-Site Request Forgery Attacks

Cross-Site Request Forgery (CSRF) is one of the worst known web vulnerabilities. In a CSRF attack, the attacker takes advantage of a user’s existing login on a site the attacker wants access to. The attack typically takes one of two forms. In a login attack, the malicious site is often disguised as the site the user wants to log in to. (Many times this is combined with a phishing attack.) Users enter their credentials and attempt to log in. The malicious site first captures the credentials and then forwards the credentials on to the real site. Because users successfully log in to the real site, they never notice that they have given their credentials to an attacker. These attacks are tough to pull off on sites that use HTTPS to secure their login forms, assuming users are conditioned to look for a valid certificate.

To understand the other type of attack, consider a scenario in which the user logs into his online banking software. Still logged in, he opens another browser tab where he sees an alert saying he won $1,000. Unfortunately, when he clicks that button, it doesn’t give him $1,000. Instead, an invisible form submits to his banking software behind the scenes and, in a cruel twist of irony, transfers $1,000 out of his account and into the attacker’s account.

You can mitigate both kinds of attacks using the synchronizer token pattern. In this pattern, a securely random token string is generated for each request to the application. That token is persisted as an attribute within the HttpSession and added as a hidden form field to any forms on the screen. If one of those forms is submitted, the application checks to make sure the submitted token matches the token in the session and permits form processing to continue only if the token matches. The attacker cannot know this token in advance, so he cannot include it in the submitted form used to trick the user.

For login attacks, the synchronizer token pattern cannot prevent the attack completely. All it can do is let users know that the attack occurred and that the attacker has their credentials. In this case, users must change their password immediately. As a best practice, the application should require this. For all other CSRF attacks, this pattern can prevent the attacks. Because the form is never processed, the damage is never done. Of course, if the submitted form contains sensitive data, some risk still exists, and the user should be notified.

Configuring CSRF Protection

Spring Security can protect your users from CSRF attacks automatically. By default, this protection is enabled when you use Java configuration, but it’s disabled by default for XML configuration (to maintain existing behavior from previous versions). In its standard configuration, it requires the CSRF token (either as a request parameter or request header) for all POST, PUT, DELETE, and PATCH requests (that is, those requests that can have side effects) that Spring Security’s filter chain processes (even those that are permitAll). Only requests ignored and excluded from the chain (for example, in configure(WebSecurity)) are unprotected. However, this is not always desirable for all secured URLs. In the Customer Support application, for example, you should not include CSRF protection on the RESTful and SOAP web services because they don’t use web forms. The requireCsrfProtectionMatcher call in Listing 26-1 overrides the default request matcher for CSRF protection, excluding the web services from CSRF protection.

NOTE Although the lambda expression certainly helps, the CSRF configuration in Listing 26-1 is awkward at best because you have to define a new matcher. In Chapter 28 you learn a better way to configure this when you add authentication and authorization to your web services.

Securing Web Forms

After you configure CSRF protection to your needs, how do you protect your forms? Well, to a certain extent you don’t have to do anything. As long as you configured Spring Security with @EnableWebMvcSecurity instead of @EnableWebSecurity, anywhere you use the<form:form> tag with verb POST, PUT, PATCH, or DELETE, Spring Security automatically adds a hidden CSRF token field. This is why you still want to use <form:form> for the login view — so CSRF protection happens automatically. (If you configured Spring Security with only @EnableWebSecurity, you must create a bean of type org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor to enable this feature.) Spring Security automatically looks for the CSRF token for any requests that match the configured request matcher.

The only time you must take extra steps to add CSRF protection is when you use standard HTML <form>s without Spring’s <form:form>, when you submit hidden forms generated by JavaScript, and when you submit Ajax calls to the server. In these cases, it is your responsibility to add the CSRF field or header to the form or request. Spring Security also makes this easy by registering an EL variable _csrf of type org.springframework.security.web.csrf.CsrfToken in all your JSP views. All you have to do is use the token properties to manually add the proper values to your form or JavaScript. The postInvisibleForm JavaScript function in the Customer Support application starts and joins chat sessions and logs out of the application. A simple tweak to this function protects any code that submits forms in that manner.

var postInvisibleForm = function(url, fields) {

var form = $('<form id="mapForm" method="post"></form>')

.attr({ action: url, style: 'display: none;' });

for(var key in fields) {

if(fields.hasOwnProperty(key))

form.append($('<input type="hidden">').attr({

name: key, value: fields[key]

}));

}

form.append($('<input type="hidden">').attr({

name: '${_csrf.parameterName}', value: '${_csrf.token}'

}));

$('body').append(form);

form.submit();

};

While you’re securing forms, you should know that the “best practice” today is to disable autocomplete for login forms. This protects users who share computers with other people, such as in a public library. Making this change is as simple as addingautocomplete="off" to the <form:form>, <form:input>, and <form:password> tags in login.jsp. With CSRF protection enabled, Spring Security requires the logout to happen over POST. It does not respond to logout requests over GET. This is why the login link in the Customer Support application now submits an invisible form.

After reviewing all these changes, compile the project and start Tomcat from your IDE. Log in to the application at http://localhost:8080/support/ and you shouldn’t really notice a difference creating, listing, and searching tickets and chatting with support. From the user’s perspective the application performs essentially the same; however, it is much more secure and robust now. You still need to handle users with different permissions — something that is coming up in the next chapter.

HTTP SECURITY HEADERS IN SPRING SECURITY 3.2

Over the past few years, industry experts and working groups have proposed and adopted several security-related HTTP headers meant to instruct browsers to enable certain security mechanisms. These headers were formulated to combat different web vulnerabilities, such as Cross-Site Scripting (XSS) and Clickjacking (User Interface Redress) attacks.

Spring Security 3.2 adds support to automatically set these headers to “best practices” values, making your application as secure as possible. As with CSRF protection, these headers are added to the responses only for any URLs that Spring Security’s filter chain processes. If you use XML configuration, these headers are disabled by default to maintain the same behavior from previous versions. However, if you use Java configuration, these headers are enabled by default, meaning you should understand how they work and what they do to determine whether you need to disable any of them.

· Cache-Control is set to no-cache, no-store, max-age=0, must-revalidate and Pragma is set to no-cache. This tells client browsers and proxies to never cache the data your site returns, protecting confidential information that may be contained therein.

· X-Content-Type-Options is set to nosniff, instructing browsers to never guess the MIME content type of pages and resources. Instead, browsers must rely solely on the Content-Type header. This is much more secure than guessing and helps prevent XSS attacks, but it means you must ensure your server always returns a valid Content-Type header for all requests.

· Strict-Transport-Security is set to max-age=31536000 ; includeSubDomains, which helps protect users from man-in-the-middle attacks during future redirects from HTTP to HTTPS. This header is set only on responses delivered over HTTPS and tells browsers to always use HTTPS to access the site and its subdomains for the next year.

· X-Frame-Options is set to DENY, preventing the responses from displaying within a frame. This helps prevent Clickjacking attacks.

· X-XSS-Protection is set to 1; mode=block. As a result, if a browser detects a suspicious script that could contain XSS, it disables the script entirely instead of trying to disable only the suspect parts.

To enable these security headers in XML configuration, simply add <headers /> to the <http> configuration to which you want it to apply. To disable these headers in Java configuration, do something like the following:

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

...

.and().headers().disable();

}

You can also control each header type individually. The following is the equivalent to enabling them all (or not disabling them):

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

...

.and().headers().cacheControl().contentTypeOptions()

.frameOptions().httpStrictTransportSecurity()

.xssProtection();

}

SUMMARY

In this chapter, you explored authentication with Spring Security. You also learned about some ways that Spring Security is a total security solution, such as through CSRF mitigation and the new HTTP security headers added in Spring Security 3.2. You experimented with various authentication mechanisms and created your own authentication provider for the Customer Support application. Finally, you learned about the XML and Java configuration options and how Spring Security Java configuration differs significantly from Spring Framework Java configuration.

In the next chapter you complete the authentication-authorization story, learning about the various ways to check user permissions and ensure only authorized users perform secured actions.