[SpringBoot3] Spring Security detailed usage examples (simple usage, JWT mode)

Note: This article is based on Spring Boot 3.2.1 and Spring Security 6.2.1

Spring Security is very simple to use. Just introduce the relevant dependency packages and then add the annotation @EnableWebSecurity. It also provides a wealth of extension points, allowing you to customize permission verification strategies.

Common usage scenarios are divided into two categories:

  1. There is a session mode, which is usually a project that is not separated from the front end. It uses cookie + session mode to store and verify user permissions;
  2. Sessionless mode, usually a front-end and back-end separation project, using Token in the form of Jwt to verify permissions

Example 1: Basic usage

1. Add jar package dependencies

<dependencies>
    <!-- ... other dependency elements ... -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies> 

The default configuration of Spring Boot and Spring Security provides the following behavior at runtime:

  1. An authenticated user is required for any endpoint (including Boot's /error endpoint).

  2. At startup, use the generated password to register the default user user (the password is recorded to the console; such as 8e557245-73e2-4286-969a-ff57fe326336).

Can be modified through the following configuration:
spring.security.user.name=admin
spring.security.user.password=admin

  1. Protect password storage using BCrypt and other methods.

  2. Provide form-based login and logout processes. (/login and /logout respectively)

  3. Authentication is based on form login and HTTP Basic.

  4. Provide content negotiation; for web requests, redirect to the login page; for service requests, return 401 Unauthorized.

  5. Mitigate CSRF attacks.

  6. Mitigate Session fixation attacks.

  7. Write Strict-Transport-Security to ensure HTTPS.

  8. Write X-Content-Type-Options to reduce sniffing attacks.

  9. Write cache control headers to protect authenticated resources.

  10. Write X-Frame-Options to reduce clickjacking.

  11. Integrate with the authentication method of HttpServletRequest.

  12. Publish authentication success and failure events.

It's helpful to understand how Spring Boot coordinates with Spring Security to achieve this. Looking at Boot's secure autoconfiguration, it does the following (simplified for illustration):

@EnableWebSecurity
@Configuration
public class DefaultSecurityConfig {
    @Bean
    @ConditionalOnMissingBean(UserDetailsService.class)
    InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        String generatedPassword = // ...;
        return new InMemoryUserDetailsManager(User.withUsername("user")
                .password(generatedPassword).roles("USER").build());
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
    DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(ApplicationEventPublisher delegate) {
        return new DefaultAuthenticationEventPublisher(delegate);
    }
} 
  • Added @EnableWebSecurity annotation. (Besides this, this also publishes Spring Security's default filter chain as a @Bean)
  • Publish a user details service @Bean, the user name is user, and use a randomly generated password to log to the console
  • Publish an AuthenticationEventPublisher @Bean for publishing authentication events

2. Configure Spring Security

Just add the annotation @EnableWebSecurity to enable permission verification

@EnableWebSecurity
@Configuration
public class BasicSecurityConfig {

} 

Configure login account password:

spring.security.user.name=admin
spring.security.user.password=admin 

3. Login test

1) When you start the project, the default login page will automatically open.
Insert image description here
Enter your account password to log in.

After successful login, obtain the current user information through SecurityContextHolder.getContext().getAuthentication();

2) Enter the address http://localhost:8080/logout to open the default exit page, and click the button Log Out to exit.

Insert image description here

4. Configure a custom login page (optional)

Here we take the thymeleaf template as an example to create a login page.

1) Add thymeleaf dependency

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

2) Add the login page login.html and the homepage index.html respectively, and place them in the directory resources/templates

Login page login.html, send post request to /login address

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <style> .sr-only{width:80px;display:inline-block;text-align: match-parent}
        .form-control{width: 120px}
        .form-signin{margin: 0 auto;width:220px;} </style>
</head>
<body>
<div class="container" style="">
    <form class="form-signin" method="post" th:action="@{/login}">
        <h2>User login</h2>
        <p>
            <label for="username" class="sr-only">username</label>
            <input type="text" id="username" name="username" class="form-control" placeholder="Username">
        </p>
        <p>
            <label for="password" class="sr-only">password</label>
            <input type="password" id="password" name="password" class="form-control" placeholder="Password">
        </p>
        <button type="submit">Log in</button>
    </form>
</div>
</body>
</html> 

Home page index.html, the exit function sends a post request to the /logout address

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <style> .form-signin{margin: 0 auto;width:220px;} </style>
</head>
<body>
<form class="form-signin" method="post" th:action="@{/logout}">
    <h2>confirm exit?</h2>
    <button class="btn btn-lg btn-primary btn-block" type="submit">quit</button>
</form>
</body>
</html> 

3) Configure HttpSecurity

@EnableWebSecurity
@Configuration
public class BasicSecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        // Release login page
                        .requestMatchers("/login").permitAll()
                        // Block all other requests
                        .anyRequest().authenticated()
                )
                // When exiting,letsessionInvalid
                .logout(logout -> logout.invalidateHttpSession(true))
                // Configure login page and Page after successful login
                .formLogin(form -> form.loginPage("/login").permitAll()
                        .loginProcessingUrl("/login").defaultSuccessUrl("/index"));
        // turn oncsrf Protect
        http.csrf(Customizer.withDefaults());
        return http.build();
    }
} 

4) Start engineering testing

log in page
Insert image description here
Home page, including exit button
Insert image description here

Example 2: Front-end and back-end separation project based on JWT

When the user logs in successfully, the token is returned, and the token is brought with every subsequent request. The token sets the expiration time and provides the token refresh function.

The background server parses the token to determine whether it is valid. If it is valid, it obtains the user information stored in the token and calls SecurityContextHolder.getContext().setAuthentication(authentication) to store the user information.

1. Introduce JWT dependency

There are many JWT and related jar packages. Here we directly use the hutool tool class.

<dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.8.25</version>
 </dependency> 

2. Customized login interface

Create a custom login interface service class and implement the interface UserDetailsService

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity entity = new UserEntity();
        entity.setId(100L);
        entity.setPassword("{noop}123456");
        entity.setUsername("admin");
        entity.setAuthorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
        return entity;
    }
} 

Create a custom UserEntity class, implement the interface UserDetails, and add the attributes you need according to the situation.

@Data
public class UserEntity implements UserDetails {
    private Long id;
    private String password;
    private String username;
    private List<GrantedAuthority> authorities;
    private boolean accountNonExpired = true;
    private boolean accountNonLocked = true;
    private boolean credentialsNonExpired = true;
    private boolean enabled = true;
} 

Configure userDetailsService in httpSecurity

http.userDetailsService(userDetailService) 

In this way, when logging in, the MyUserDetailsServiceImpl.loadUserByUsername() method will be called.

3. User login returns token

Configure logical processing when the user logs in successfully and return the required token.

http.formLogin(form -> form.loginPage("/login").permitAll()
       .loginProcessingUrl("/login")
       .successHandler((request, response, authentication) -> {
           log.info("login successful:{}", authentication);
           UserEntity principal = (UserEntity) authentication.getPrincipal();
           String secret = "0123456789";
           Map<String, Object> payload = new HashMap<>();
           payload.put("id", principal.getId());
           payload.put("username", principal.getUsername());
           String token = JWTUtil.createToken(payload, secret.getBytes());
           response.setContentType("application/json;charset=UTF-8");
           Map<String, Object> map = new HashMap<>();
           map.put("token", token);
           response.getWriter().write(JSONUtil.toJsonStr(map));
       }).failureHandler((request, response, authentication) -> {
           log.info("Login failed:{}", authentication);
       })); 

4. Add Jwt verification filter

Configure disabled session

// CSRF Disable,because not using Session
http.csrf(AbstractHttpConfigurer::disable);
// based on token mechanism,So no need Session
http.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); 

Create a custom Jwt validation filter and configure

@Component
public class JwtTokenFilter extends OncePerRequestFilter {
    @Autowired
    private MyUserDetailsServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // verifytokenis it effective
        String token = request.getHeader("token");
        if (StrUtil.isNotEmpty(token)) {
            String secret = "0123456789";
            boolean verify = JWTUtil.verify(token, secret.getBytes());
            if (!verify) {
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("{\"code\":401,\"msg\":\"tokeninvalid\"}");
                return;
            } else {
                //Authentication successful,Set user information
                UserEntity user = JWTUtil.parseToken(token).getPayloads().toBean(UserEntity.class);
                // Simulate obtaining user information,The actual situation should be to query from the database
                UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
                        userDetails.getPassword(),
                        userDetails.getAuthorities());
                //Set user information
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
} 

Configure execution location, before UsernamePasswordAuthenticationFilter

http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 

5. Test

At this point, the spring security configuration based on JWT is completed. The actual project should be to query users and role permissions from the database.

1) Use postman to test, first use the login method (post request) to obtain the token

2) Then copy the token and pass it to other interfaces as header parameters.

**The complete code of JwtSecurityConfig configuration class is as follows: **

@EnableWebSecurity
@Configuration
@Slf4j
public class JwtSecurityConfig {
    @Autowired
    private MyUserDetailsServiceImpl userDetailService;
    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated()
                )
                .userDetailsService(userDetailService)
                .exceptionHandling(ex -> ex.authenticationEntryPoint((request, response, authException) -> {
                    log.error("[commence][access URL({}) hour,Not logged in]", request.getRequestURI(), authException);
                    ServletUtils.writeJSON(response, authException.getMessage());
                }).accessDeniedHandler((request, response, accessDeniedException) -> {
                    log.warn("[commence][access URL({}) hour,user({}) Insufficient permissions]", request.getRequestURI(),
                            "", accessDeniedException);
                }))
                .logout(logout -> logout.invalidateHttpSession(true))
                // Configure login page
                .httpBasic(Customizer.withDefaults())
                .formLogin(form -> form.loginPage("/login").permitAll()
                        .loginProcessingUrl("/login")
                        .successHandler((request, response, authentication) -> {
                            log.info("login successful:{}", authentication);
                            UserEntity principal = (UserEntity) authentication.getPrincipal();
                            // login successful,returntokento front-end
                            String secret = "0123456789";
                            Map<String, Object> payload = new HashMap<>();
                            payload.put("id", principal.getId());
                            payload.put("username", principal.getUsername());
                            String token = JWTUtil.createToken(payload, secret.getBytes());
                            response.setContentType("application/json;charset=UTF-8");
                            Map<String, Object> map = new HashMap<>();
                            map.put("token", token);
                            response.getWriter().write(JSONUtil.toJsonStr(map));
                        }).failureHandler((request, response, authentication) -> {
                            log.info("Login failed", authentication);
                        }));
        // CSRF Disable,because not using Session
        http.csrf(AbstractHttpConfigurer::disable);
        // based on token mechanism,So no need Session
        http.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

} 

reference

  • https://docs.spring.io/spring-security/reference/index.html