Skip to main content
查利博客

Spring Security with Spring Boot

If you are thinking building a web application with login system, you should definitely try the Spring Security. It provides lots of amazing features like the default login form, various protection features like one way Password Storage, Cross Site Request Forgery (CSRF) prevention and Session Fixation protection and provide full support to third-party authentication providers. With the power of Spring Boot Auto Configuration, it does a lot of configurations for us including let the user login and logout, stores user passoword with BCrypt, a default user account created with password printed on console, etc. In this article, we're going to taste the Spring Security with Spring Boot first and try using Spring Security configured from scratch.

With Boot #

To enable Spring Security, you just need to add the following dependencies in your pom.xml file.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
<dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
<dependency>

Now start your Spring Boot application, open your browser go to http://localhost:8080 or http://localhost:8080/login, by default Spring Security creates a login page for you. At the moment, any interaction with the application requires authentication. Type the username user and the password 088c8ef7-0ce7-4d38-a16a-1ca9fde2b974 (password is printed on the console) and click Sign in. Normally, an error page shows up after successful login because there is no mapping specified for your context path /. We can add a RestController and the browser will display a Login Successfully! message.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
  @GetMapping("/")
  public String test() {
    return "Login Successfully!";
  }
}

Redirection to a HTML page #

We can also define a HTML page after a successful login by overriding the configure(HttpSecurity http) method of WebSecurityConfigurerAdapter class.

Web Security Config:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
    // The "alwayUses" flag is false meaning if we visit localhost:8080/test first,
    // Spring will remember this page and redirect to it after successful authentication.
    // If this flag is true, Spring will always redirect to the "success.html" page
    http.formLogin().defaultSuccessUrl("/success.html", false);
  }
}

Simple Get Request:

  //...
	@GetMapping("/test")
  public String test123() {
    return "Testing 123! Testing 123!";
  }
  //...

Create a simple HTML file named success.html and place it in src/main/resources/static directory:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
  <p>Hi! Welcome to Zhenhua Cai's Blog!</p>
</body>
</html>

Logout #

Spring Security already implements the sign out feature for us, just type localhost:8080/logout in your browser and click Log Out button to log out. Or we can simply append a logout button in above success.html file.

<input type="button" onclick="location.href='./logout';" value="Log Out" />

Access from terminal #

Using the default user and generated password is troublesome, let's create a in-memory user with AuthenticationManagerBuilder class.

//...
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Autowired
  public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123")).authorities("ROLE_ADMIN");
  }
//...
}

Now, consume our secured application by using the curl command while option i display the HTTP response headers in the console, -L follows the redirect and --user specified the username and password to use for the server authentication.

curl -i -L --user admin:123 http://localhost:8080/test

It does print out the Testing 123! Testing 123! message as expected because we have not instructed Spring to configure the HTTP Basic Authentication. To configure HTTP Basic Authentication:

//...
http.formLogin().defaultSuccessUrl("/success.html", false);
http.httpBasic();	// add this line
//...

Roles #

Spring Security also provides a flexible authorization API. Let's create a protected resource API only users having the ROLE_ADMIN can access.

Add a new user with ROLE_READER for testing:

auth.inMemoryAuthentication().withUser("caizhenhua").password(passwordEncoder().encode("abc")).authorities("ROLE_READER");

In your controller, add a new get method for admin only:

@GetMapping("/api/test")
public String adminOnly() {
  return "You are accessing the protected resources!";
}

Now we need to secure all the protected resources under the /api/ directory. To do that, we need to modify the http inside the configure method block:

http.authorizeRequests()
  .antMatchers("/api/**").hasAuthority("ROLE_ADMIN")
  .anyRequest().authenticated();

Test admin user:

curl -i -L --user admin:123 http://localhost:8080/api/test

Response:

HTTP/1.1 200 
Set-Cookie: JSESSIONID=EBF231A60D1E696944ABB66DC04F8E1D; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 42
Date: Wed, 20 Oct 2021 16:27:05 GMT

You are accessing the protected resources!%  

Test reader user:

curl -i -L --user caizhenhua:abc http://localhost:8080/api/test

Response:

HTTP/1.1 403 
Set-Cookie: JSESSIONID=16223165AF30D6C42E7C65F757E84ADE; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 20 Oct 2021 16:27:35 GMT

{"timestamp":"2021-10-20T16:27:35.520+00:00","status":403,"error":"Forbidden","path":"/api/test"}

Roles with @PreAuthorize #

Apart from setting the url pattern in web security config class to acheive role-based authorization, the @PreAuthorize annotation can authorize requests at method-level.

Pre Auth Controller Example:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PreAuthExample {
  @GetMapping("/preauth")
  @PreAuthorize("hasRole('ROLE_ADMIN')")
  public String adminOnly() {
    return "You are accessing the protected resources!";
  }
}

Enable the @PreAuthorize annotation:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}

Test admin user:

curl -i -L --user admin:123 http://localhost:8080/preauth

Response:

HTTP/1.1 200 
Set-Cookie: JSESSIONID=D64E47F959D4AACF33C58C9C9F2DFDCB; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 56
Date: Wed, 20 Oct 2021 16:40:07 GMT

You are accessing the protected resources from pre-auth!

Test reader user:

curl -i -L --user caizhenhua:abc http://localhost:8080/preauth

Response:

HTTP/1.1 403 
Set-Cookie: JSESSIONID=12D6346F824F8C3CEF18AA8C4478210F; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 20 Oct 2021 16:40:57 GMT

{"timestamp":"2021-10-20T16:40:57.476+00:00","status":403,"error":"Forbidden","path":"/preauth"}

Codes for this article is available over the GitHub, enjoy!