Spring Security provides a wide range of features for securing Java/Spring-based enterprise applications. At first glance, the security features of Servlets or EJB look an alternative of Spring Security; however, these solutions lack certain requirements for developing enterprise applications. The server's environment dependency could be another drawback of these solutions.
Authentication and authorization are the main areas of application security. Authentication is the verification of a user's identity, whereas authorization is the verification of the privileges of a user.
Spring Security integrates with a variety of authentication models, most of which are provided by third-party providers. In addition, Spring Security has developed its own authentication models, based upon major security protocols. Here are some of these protocols:
Since there is a big list of Spring Security models, we can only detail the most popular of them in this chapter.
Spring Security is quite strong on authorization features. We can categorize these features into three groups: web, method, and domain object authorization. Later, in the Authorization section, we will explain these categories.
In order to use Spring Security features in a web application, you need to include the following dependencies in your project:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.0.2.RELEASE</version> </dependency>
The open standard for authorization (OAuth) concept, introduced in late 2006, aimed to allow third-party limited access to users' resources on Microsoft, Google, Facebook, Twitter, or similar accounts, without sharing their usernames and passwords.
In 2010, OAuth was standardized as the OAuth 1.0a protocol in RFC 5849. Later in 2012, it evolved to the OAuth 2.0 framework in RFC 6749. In this chapter, we explain Spring's OAuth 2.0 framework implementation.
The OAuth 2.0 Authorization Framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf (http://tools.ietf.org/html/rfc6749).
Spring provides a separate module (spring-security-oauth2
) for its OAuth 2.0 implementation, which relies on Spring Security features. In this chapter, we explain authentication and how Spring facilitates the process by providing its own easy-to-use features as well as giving you options to plug in your customized implementation. Authorization is the second topic included in this chapter, in which we explain how to configure separate security models within the same application. In the last section, we explain Spring's OAuth 2.0 feature.
In an application's security domain, the first thing that comes to mind is authentication. During the authentication process, an application compares a user's credentials (for example, a username and password or a token) with the information available to it. If these two match, it allows the process to enter the next step. We will follow the next step in the Authorization section.
Spring Security provides features to support a variety of security authentication protocols. In this section, we will focus on basis and form-based authentication.
Spring provides a built-in form for the purpose of form-based authentication. In addition, it lets you define your own customized login form.
Spring gives you the option to use in-memory authentication, in which the username and password will be hardcoded in the application.
An alternative option is to use a customized authentication provider that lets you decide how to authenticate users by program, for example, calling a data layer service to validate users. It also lets you integrate Spring Security with your existing security framework.
The first thing you need in order to configure Spring Security to authenticate users is to define a Servlet filter known as springSecurityFilterChain
. This filter is responsible for applying security measures (for example, validating users, navigating to different pages after login bases on the user's role, and protecting application URLs) in a web application.
WebSecurityConfigurerAdapter
is a convenient Spring template for configuring springSecurityFilterChain
:
@Configuration @EnableWebSecurity @ComponentScan(basePackages = "com.springessentialsbook.chapter5") public class WebSecurityConfigurator extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("operator").password("password").roles("USER"); auth.inMemoryAuthentication().withUser("admin").password("password").roles("ADMIN"); auth.inMemoryAuthentication().withUser("accountant").password("password").roles("ACCOUNTANT"); }
@Configuration
registers this class as a configuration class. The method's name, configureGlobalSecurity
, is not important, as it only configures an AuthenticationManagerBuilder
instance through autowire. The only important thing is annotating the class with @EnableWebSecurity
, which registers Spring web security in the application. As you can see, we used in-memory authentication for simplicity, which hardcoded the user's username, password, and role used for user authentication. In real enterprise applications, LDAP, databases or the cloud provide services for validating user credentials.
We don't code all that much in the config class, but it really does a lot behind the scenes. Here are some of the features implemented by the class. Apart from user authentication and role assignment, we will explain other features next in this chapter.
As we explained, in real-world enterprise applications, one never hardcodes user credentials within the application's code. You may have an existing security framework that calls a service in order to validate users. In this case, you can configure Spring Security in a customized service to authenticate the user.
The authentication interface implementation is what carries user credentials within the Spring Security context. You can obtain the authentication object anywhere within the application using SecurityContextHolder.getContext().getAuthentication()
.
When a user is authenticated, Authentication
will be populated. If you don't specify AuthenticationProvider
(for example, if you use in-memory authentication), Authentication
will be populated automatically. Here, we look at how to customize AuthenticationProvider
and populate the Authentication
object.
The following code shows how Spring's AuthenticationProvider
implementation class integrates with a customized user detail service (which returns user credentials from a data source):
@Component public class MyCustomizedAuthenticationProvider implements AuthenticationProvider { @Autowired UserDetailsService userService; public Authentication authenticate(Authentication authentication) throws AuthenticationException { User user=null; Authentication auth=null; String username=authentication.getName(); String password=authentication.getCredentials().toString(); user= (User) userService.loadUserByUsername(username); if(password ==null || ! password.equals(user.getPassword())) throw new UsernameNotFoundException("wrong user/password"); if(user !=null){ auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); } else throw new UsernameNotFoundException("wrong user/password"); return auth; } public boolean supports(Class<?> aClass) { return true; } }
Your customized authentication provider should implement AuthenticationProvider
and its authenticate
method.
Note that the userService
instance here should implement the Spring UserDetailsService
interface and its loadUserByUserName
method. The method returns the data model of a user. Note that you can extend Spring's User
object and create your own customized user. We mocked the UserService
integration part with a data service. In a real application, there could be a service call to fetch and return user data, and your UserServiceImpl
class will only wrap the user in the UserDetails
data model, as follows:
@Service public class UserServiceImpl implements UserDetailsService { public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { // suppose we fetch user data from DB and populate it into // User object // here we just mock the service String role=null; if(userName.equalsIgnoreCase("admin")){ role ="ROLE_ADMIN"; }else if(userName.equalsIgnoreCase("accountant") ){ role="ROLE_ACCOUNTANT"; }else if(userName.equalsIgnoreCase("operator")){ role="ROLE_USER"; }else{ throw new UsernameNotFoundException("user not found in DB"); } List<GrantedAuthority> authorities=new ArrayList<GrantedAuthority>(); authorities.add(new GrantedAuthorityImpl(role)); return new User(userName, "password", true, true, true, true, authorities); } }
After this, you can set your customized provider in the configuration class, as shown in the following code. When a user is authenticated, the authentication object should be populated programmatically. Later in this chapter, in the Authorization section, we will explain this object.
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true) @ComponentScan(basePackages = "com.springessentialsbook.chapter5") public class MultiWebSecurityConfigurator { @Autowired private AuthenticationProvider authenticationProvider; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider); }
We defined the springSecurityFilterChain
filter in the first step. To make it work, we need to register it in the web application, like so:
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { }
The class doesn't need any code, as the superclass (AbstractSecurityWebApplicationInitializer
) registers the Spring Security filter. This happens while the Spring context starts up.
If we don't use Spring MVC, we should pass the following to the constructor:
super(WebSecurityConfigurator);
The class AnnotatedConfigDispatcherServletInitializer
extends Spring's Servlet initializer AbstractAnnotationConfigDispatcherServletInitializer
. This class allows Servlet 3 containers (for example, Tomcat) to detect the web application automatically, without needing web.xml
. This is another step of simplifying the setting up of a web application, and it registers DispatcherServlet
and Servlet mapping programmatically. By setting the WebSecurityConfigurator
class in getRootConfigClasses
, you tell the parent class method that creates the context of the application to use your annotated and customized Spring Security configuration class. The following is the code for the AnnotatedConfigDispatcherServletInitializer
class:
public class AnnotatedConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { MultiWebSecurityConfigurator.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
What we have configured so far in Spring Security is for checking whether the username and password are correct. If we want to configure other security features, such as defining a login page and the web application URL request to be authenticated, we need to override the configure(HttpSecurity http)
method of WebSecurityConfigurerAdapter
.
In our customized security configurator, we define a login page (login.jsp
) and an authorization failure page (nonAuthorized.jsp
), as follows:
@Configuration @EnableWebSecurity public class WebSecurityConfigurator extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; ... @Override public void configure(HttpSecurity http) throws Exception { ... .and().formLogin() .loginPage("/login").successHandler(authenticationSuccessHandler) .failureUrl("/nonAuthorized") .usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login") ...
This code tells Spring to process a submitted HTTP request form (with the POST method) with the expected username and password as parameters and "/login"
as the action. Here is the login form:
<form role="form" action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <div> <label for="username">Username</label> <input type="text" name="username" id="username" required autofocus> </div> <div> <label for="password">Password</label> <input type="password" name="password" id="password" required> </div> <button type="submit">Sign in</button> </form>
If you don't specify a username, password, and loginProcessingUrl
parameter in the configuration file, Spring Security expects j_username
, j_password
, and j_spring_security_check
from the client browser. By overriding Spring's default values, you can hide the Spring Security implementation from the client browser.
A cross-site request forgery (CSRF) attack happens, for example, when a malicious link clicked by an authenticated web client performs an unwanted action, such as transferring funds, obtaining contact e-mails, or changing passwords. Spring Security provides a randomly generated CSRF to protect the client from CSRF attacks.
If you omit .loginPage
in the configure
method, Spring uses its default login page, which is a very plain HTML login page. In this case, Spring Security uses the expected j_username
, j_password
, and j_spring_security_check
parameters for the username, password, and action, and you should not configure them in the method.
For example, here we ask Spring to provide its own default login form:
@Override public void configure(HttpSecurity http) throws Exception { ... .and().formLogin() .successHandler(authenticationSuccessHandler) .failureUrl("/nonAuthorized") ... }
Spring Security supports HTTP Basic authentication, in which the client browser opens a popup (for the initial time) when you want to access a resource that matches a pattern ("/adResources*/**"
in this case):
protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/adResources*/**").authorizeRequests().anyRequest().hasRole("ADMIN") .and() .httpBasic(); }
Server-side navigation could be the next step after authentication. Even though routing information is provided from the client side in modern client-side frameworks such as AngularJS, you may still want to keep routing logic on the server side. A success handler is a Spring Security feature that lets you define navigation logic after authentication in a web application.
Spring Security lets you configure customized server-side navigation after authentication. You can configure it inside the configure
method (using successHandler
):
@Override public void configure(HttpSecurity http) throws Exception { ... .loginPage("/login").successHandler(authenticationSuccessHandler) .... }
Your customized navigation handler should implement the interface AuthenticationSuccessHandler
. OnAuthenticationSuccess
is the method that will be called when a user is authenticated. Within this method, we should define the target URL. In the sample implementation class shown here, the user's role is just used to define the target URL:
@Component public class MyCustomizedAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException { handle(request, response, authentication); final HttpSession session = request.getSession(false); if (session != null) { session.setMaxInactiveInterval(3600);//1 hour } clearAttributes(request); } protected void handle(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException { final String url = getUrl(authentication); if (response.isCommitted()) { return; } redirectStrategy.sendRedirect(request, response, url); } private String getUrl(final Authentication authentication) { String url=null; final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (final GrantedAuthority grantedAuthority : authorities) { if (grantedAuthority.getAuthority().equals("ROLE_USER")) { url= "/user" ; break; } else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) { url= "/admin" ; break; } else if (grantedAuthority.getAuthority().equals("ROLE_ACCOUNTANT")) { url= "/accountant" ; break; }else { throw new IllegalStateException(); } } return url; } protected void clearAttributes(final HttpServletRequest request) { final HttpSession session = request.getSession(false); if (session == null) { return; } session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); } public void setRedirectStrategy(final RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; } protected RedirectStrategy getRedirectStrategy() { return redirectStrategy; } }
Spring Security lets you configure your security configuration in multiple methods, and in each method, you can define a different category of resources. Here, we have separated the security configuration for form-based and basic authentication into these two classes:
@EnableWebSecurity @ComponentScan(basePackages = "com.springessentialsbook.chapter5") public class MultiWebSecurityConfigurator { @Autowired private AuthenticationProvider authenticationProvider; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider); } @Configuration protected static class LoginFormBasedWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() ... .permitAll(); } } @Configuration @Order(1) public static class HttpBasicWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/adResources*/**").authorizeRequests().anyRequest().hasRole("ADMIN") .and() .httpBasic(); } }}
For example, in one method, we configure resources in the adResources
path to be viewed by the admin role in an HTTP-based authentication (the browser opens a popup and asks for a username and password). In the second method, we apply form login authorization and limit access to resources based on user roles.