Part 5: Adding Security to the Services

In a series of blogs we will look at how to create a REST service using the latest version of Spring. Our service will have validation so we can make sure the data stored meets the requirements and it will be secured so only the correct people can make updates.

In the last entry we looked at adding JSR-303 validation to our services. To continue this series of blogs we will now add authentication and authorization to our services.

Spring Security

Spring security is a big project and to much to go into in this blog. Let's just say that it's a comprehensive framework for adding both authentication and authorization to any web based project. We will use only a part of it in this blog and it offers so much more than this little part.

Using a Header for Authentication

In our application we will look for a header to see if the calling client is authenticated to use the service in question. In order to keep with the stateless mandate of REST we will not store anything in the session. Our header will contain an encrypted username and a timestamp. As each request comes in to the application it will extract the username to use in verifying the user and it's permissions. The timestamp is there to protect from replay attacks by limiting how long the header token is valid. Each new request that is valid will get a new header with an updated timestamp in the response for use on the next request.

public class HeaderAuthenticationFilter extends GenericFilterBean {
 
 private UserDetailsService userDetailsService;
 
 private HeaderUtil headerUtil;
 
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
 
 UserDetails userDetails = loadUserDetails((HttpServletRequest) request);
 SecurityContext contextBeforeChainExecution = createSecurityContext(userDetails);
 
 try {
 SecurityContextHolder.setContext(contextBeforeChainExecution);
 if (contextBeforeChainExecution.getAuthentication() != null && contextBeforeChainExecution.getAuthentication().isAuthenticated()) {
 String userName = (String) contextBeforeChainExecution.getAuthentication().getPrincipal();
 headerUtil.addHeader((HttpServletResponse) response, userName);
 }
 filterChain.doFilter(request, response);
 }
 finally {
 // Clear the context and free the threadlocal
 SecurityContextHolder.clearContext();
 }
 }
 
 private SecurityContext createSecurityContext(UserDetails userDetails) {
 if (userDetails != null) {
 SecurityContextImpl securityContext = new SecurityContextImpl();
 Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
 securityContext.setAuthentication(authentication);
 return securityContext;
 }
 return SecurityContextHolder.createEmptyContext();
 }
 
 private UserDetails loadUserDetails(HttpServletRequest request) {
 String userName = headerUtil.getUserName(request);
 
 return userName != null
 ? userDetailsService.loadUserByUsername(userName)
 : null;
 }
 
}

This filter will look for the header, check it for validity and then add the new header to the response. It will also look up the principal (user information) and set it in a threadlocal based SecurityContextHolder that is internal to Spring Security. This will allow Spring Security to validate what the user is allowed to do. We are not doing any authorization here, this is only the preparation for authentication. The actual authentication check is done by Spring Security against the UserDetailsService which we will look at next.

Initial Configuration

Just like configuring the normal Spring services we will use Java config to set up our security layer.

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
 auth.inMemoryAuthentication().
 
 withUser("user").password("password").roles("USER").
 
 and().
 
 withUser("admin").password("password").roles("USER", "ADMIN");
 }
 
}

In the above example we create an in memory database of users. This is obviously not the way to go in a real application but it works for our intents.

The @EnableWebMvcSecurity Annotation

This will set up a basic web security layer. This would work fine almost out of the box if we were building a regular web based application with HTML views. As we are not we need to customize it a bit, more on that later.

Roles and Permissions

One of the basic ways to implement security is to assign roles to users. Each role comes with certain permissions. This does not give you data layer security which is more complicated, instead it gives you security for certain operations. This means if a role includes permission to view data there is no built in way to filter what data can be viewed. If the role does not include permission to update data there is no automatic way for the system to allow the user to update data. There are of course work arounds for all these problems but they require more coding.
Spring Security does support data layer security through the use of Access Control Lists (ACL) but that is the topic for a whole blog on it's own.
In our example we created two users, "admin" and "user" with separate roles. Our "user" will later on only have view access whereas "admin" will get full create, update and delete permissions.

Conclusion

In this entry we looked at how to do basic configuration of Spring Security and how to customize the authentication to use a header instead of the session.

Each part of this blog will add functionality to our application.
Part 1: Creating the Basic Application
Part 2: Configuring The Application
Part 3: Adding the REST Services
Part 4: Adding Validation
Part 5: Adding Security to the Services
Part 6: Customising the Security for REST

In the next entry which will conclude the series we will look at how to customise the security layer to fit better with a REST service instead of the basic web application.

The code examples are all heavily edited excerpts but the complete source code can be found on GitHub.