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!