In this chapter, we'll learn how to secure our web application and also how to cope with the security challenges of modern, distributed web applications.
This chapter will be broken up into five parts:
The simplest possible authentication mechanism is basic authentication (http://en.wikipedia.org/wiki/Basic_access_authentication). In a nutshell, our pages will not be available without username and password.
Our server will indicate our resources are secured by sending the 401 Not Authorized
HTTP status code and generate a WWW-Authenticate
header.
To successfully pass the security check, the client must send an Authorization
header containing the Basic
value followed by a base 64 encoding of the user:password
string. A browser window will prompt the user for a username and a password, granting them access to the secured pages if authentication is successful.
Let's add Spring Security to our dependencies:
compile 'org.springframework.boot:spring-boot-starter-security'
Relaunch your application and navigate to any URL in your application. You will be prompted for a username and a password:
If you fail to authenticate, you will see that a 401
error is thrown. The default username is user
. The correct password for authentication will be randomly generated each time the application launches and will be displayed in the server log:
Using default security password: 13212bb6-8583-4080-b790-103408c93115
By default, Spring Security secures every resource except a handful of classic routes such as /css/
, /js/
, /images/
, and **/favicon.ico
.
If you wish to configure the default credentials, you can add the following properties to the application.properties
file:
security.user.name=admin security.user.password=secret
Having only one user in our application does not allow fine-grained security. If we wanted more control over the user credentials, we could add the following SecurityConfiguration
class in the config
package:
package masterSpringMvc.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired public void configureAuth(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password("user").roles("USER").and() .withUser("admin").password("admin").roles("USER", "ADMIN"); } }
This snippet will set up an in-memory system containing our application's users as well as their roles. It will override the security name and password previously defined in the application's properties.
The @EnableGlobalMethodSecurity
annotation will allow us to annotate our application's method and classes to define their security level.
For example, let's say that only the administrators of our application can access the user API. In this case, we just have to add the @Secured
annotation to our resource to allow access only to ADMIN roles:
@RestController
@RequestMapping("/api")
@Secured("ROLE_ADMIN")
public class UserApiController {
// ... code omitted
}
We can easily test that with httpie by using the -a
switch to use basic authentication and the -p=h
switch, which will only display the response headers.
Let's try this with a user without the admin profile:
> http GET 'http://localhost:8080/api/users' -a user:user -p=h HTTP/1.1 403 Forbidden Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Type: application/json;charset=UTF-8 Date: Sat, 23 May 2015 17:40:09 GMT Expires: 0 Pragma: no-cache Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=2D4761C092EDE9A4DB91FA1CAA16C59B; Path=/; HttpOnly Transfer-Encoding: chunked X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block
> http GET 'http://localhost:8080/api/users' -a admin:admin -p=h HTTP/1.1 200 OK Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Type: application/json;charset=UTF-8 Date: Sat, 23 May 2015 17:42:58 GMT Expires: 0 Pragma: no-cache Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=CE7A9BF903A25A7A8BAD7D4C30E59360; Path=/; HttpOnly Transfer-Encoding: chunked X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block
You will also notice that Spring Security automatically added some common security headers:
Cache Control
: This prevents the user from caching secured resourcesX-XSS-Protection
: This tells the browser to block what looks like CSSX-Frame-Options
: This disallows our site from being embedded in an IFrameX-Content-Type-Options
: This prevents browsers from guessing the MIME types of malicious resources used to forge XSS attacksA comprehensive list of these headers is available at http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers.
Annotating our controller is very easy but isn't always the most viable option. Sometimes, we just want total control over our authorization.
Remove the @Secured
annotation; we will come up with something better.
Let's see what Spring Security will allow us to do by modifying the SecurityConfiguration
class:
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired public void configureAuth(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password("user").roles("USER").and() .withUser("admin").password("admin").roles("USER", "ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http .httpBasic() .and() .csrf().disable() .authorizeRequests() .antMatchers("/login", "/logout").permitAll() .antMatchers(HttpMethod.GET, "/api/**").hasRole("USER") .antMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN") .antMatchers(HttpMethod.PUT, "/api/**").hasRole("ADMIN") .antMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN") .anyRequest().authenticated(); } }
In the preceding code sample, we configured our application's security policy by using Spring Security's fluent API.
This API allows us to configure Spring Security globally by invoking methods associated with different security concerns and chaining with the and()
method.
What we just defined is a basic authentication, without CSRF protection. Requests on /login
and /logout
will be allowed for all users. GET
requests on the API will only be permitted for users with the USER
role, whereas POST
, PUT
, and DELETE
requests on the API will only be accessible to users with the ADMIN roles. Finally, every other request will require authentication with any role.
CSRF stands for Cross Site Request Forgery and refers to an attack where a malicious website would display a form on its website and post the form data on yours. If the user of your site is not signed out, the POST
request would retain the user cookies and would therefore be authorized.
CSRF protection will generate short-lived tokens that will be posted along with the form data. We will see how to properly enable it in the next section; for now, let's just disable it. See http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf for more details.
To learn more about the authorize request API, have a look at http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#authorize-requests.
Sometimes, you will need to display data coming from the authentication layer, for example the user's name and roles, or hide and display part of a web page according to users' authorities. The thymeleaf-extras-springsecurity
module will allow us to do so.
Add the following dependency to your build.gradle
file:
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity3'
With this library, we can add a little block under our navigation bar in layout/default.html
to display the logged-in user:
<!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <!-- content trimmed --> </head> <body> <!-- content trimmed --> <nav> <div class="nav-wrapper indigo"> <ul class="right"> <!-- content trimmed --> </ul> </div> </nav> <div> You are logged as <b sec:authentication="name" /> with roles <span sec:authentication="authorities" /> - <form th:action="@{/logout}" method="post" style="display: inline-block"> <input type="submit" value="Sign Out" /> </form> <hr/> </div> <section layout:fragment="content"> <p>Page content goes here</p> </section> <!-- content trimmed --> </body> </html>
Note the new namespace in the HTML declaration and the sec:authentication
attributes. It allows access to the properties of the org.springframework.security.core.Authentication
object, which represents the user who is currently logged in, as shown in the following screenshot:
Don't click on the logout link just yet as it doesn't work with basic authentication. We will get it to work in the next part.
The lib
tag also has a handful of other tags, such as the one to check user authorizations:
<div sec:authorize="hasRole('ROLE_ADMIN')"> You are an administrator </div>
Please refer to the documentation available at https://github.com/thymeleaf/thymeleaf-extras-springsecurity to learn more about the library.