Spring Security interface authentication getting started Practice Guide

preface

In the Web API interface service scenario, user authentication and authentication are very common requirements. Spring Security is said to be the de facto standard in this field. In practice, there are indeed many commendable points involved in the whole, which also confirms the argument of "too complex" often mentioned by the guys to a certain extent.

Taking a simple SpringBoot Web application as an example, this paper focuses on the following contents:

  • Demonstrate the configuration method of Spring Security interface authentication and authentication;
  • Taking memory and database as examples, this paper introduces the storage and reading mechanism of authentication and authentication data;
  • Custom implementation of several modules, including authentication filter, authentication or authentication failure processor, etc.

SpringBoot example

Create a SpringBoot example to demonstrate the application of Spring Security in the SpringBoot environment. Briefly introduce the four parts: POM xml,application.yml, IndexController, and HelloController.

SpringBoot pom.xml

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

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

Boot example is a spring boot project sub Module for demonstration.

Note: the version of the dependency is already in the project POM Declared in XML dependency management.

SpringBoot application.yml

spring:
  application:
    name: example

server:
  port: 9999

logging:
  level:
    root: info

The SpringBoot application name is example and the instance port is 9999.

SpringBoot IndexController

@RestController
@RequestMapping("/")
public class IndexController {
  @GetMapping
  public String index() {
    return "index";
  }
}

IndexController implements an interface: /.

SpringBoot HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {
  @GetMapping("/world")
  public String world() {
    return "hello world";
  }

  @GetMapping("/name")
  public String name() {
    return "hello name";
  }
}

HelloController implements two interfaces: / hello/world and / hello/name.

Compile and start the SpringBoot application, request the interface through the browser, request path and response result:

http://localhost:9999
index

http://localhost:9999/hello/world
hello world

http://localhost:9999/hello/name
hello name

The SpringBoot sample is ready.

Spring boot integrates Spring Security

Spring boot integrates Spring Security only in POM Add the corresponding dependency to the XML: spring boot starter security, as follows:

  <dependencies>
    ...

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

Compile and start the application. Compared with ordinary SpringBoot applications, we can see two special lines of logs on the command line terminal:

2022-01-09 16:05:57.437  INFO 87581 --- [           main] .s.s.UserDetailsServiceAutoConfiguration :

Using generated security password: 3ef27867-e938-4fa4-b5da-5015f0deab7b

2022-01-09 16:05:57.525  INFO 87581 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@11e355ca, org.springframework.security.web.context.SecurityContextPersistenceFilter@5114b7c7, org.springframework.security.web.header.HeaderWriterFilter@24534cb0, org.springframework.security.web.csrf.CsrfFilter@77c233af, org.springframework.security.web.authentication.logout.LogoutFilter@5853ca50, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@6d074b14, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3206174f, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@70d63e05, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5115f590, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@767f6ee7, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7b6c6e70, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@e11ecfa, org.springframework.security.web.session.SessionManagementFilter@106d77da, org.springframework.security.web.access.ExceptionTranslationFilter@7b66322e, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3e5fd2b1]

Indicates that Spring Security has taken effect in the SpringBoot application. By default, Spring Security automatically helps us complete the following three events:

  1. Enable FormLogin login authentication mode;

    We use the browser request interface /:

    http://localhost:9999/
    

    You will find that the request will be redirected to the page / login:

    http://localhost:9999/login
    

    Prompt to log in with user name and password:

  2. Generate a user name and password for login;

    The user name is user, and the password will be output to the application startup log:

    Using generated security password: 3ef27867-e938-4fa4-b5da-5015f0deab7b
    

    Every time the application starts, the password will be randomly generated again.

  3. Registering a filter for authentication and authentication;

    Spring Security is essentially implemented through filters or filters (chains). Each interface request will be "filtered" by these filters in order. Each filter undertakes its own responsibilities and combines them to complete authentication and authentication.
    The registered filters vary according to the configuration. By default, the loaded filter list can refer to the startup log:

    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    DefaultLoginPageGeneratingFilter
    DefaultLogoutPageGeneratingFilter
    BasicAuthenticationFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
    

Sign in using the user name and password generated by Spring Security by default. After success, it will automatically redirect to /:

index

After that, we can normally request / hello/world and / hello/name through the browser.

By default, Spring Security only supports authentication based on FormLogin. It can only use fixed user names and randomly generated passwords, and does not support authentication. If you want to use richer security features:

  • Other authentication methods, such as HttpBasic
  • Customize user name and password
  • authentication
    We need to customize the configuration of Spring Security. Custom configuration can be implemented in two ways:
  • Java Configuration Configuration using Java code:
  • Security NameSpace Configuration Configuration using XML files:

This article takes Java Configuration as an example. We need to provide a configuration class inherited from WebSecurityConfigurerAdapter, and then override several methods to realize custom configuration.

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    
  }
}

SecurityConfig uses the @ Configuration annotation (Configuration class) and inherits from WebSecurityConfigurerAdapter. This article implements custom Configuration by overriding the configure method.

Note: there are multiple overloaded methods named configure in WebSecurityConfigurerAdapter. Here, the method with parameter type HttpSecurity is used.

Note: Spring Security default automation configuration reference Spring Boot Auto Configuration.

Spring Security uses HttpBasic authentication

  protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize ->
                    authorize
                            .anyRequest()
                            .authenticated())
            .httpBasic();
  }

http.authorizeHttpRequests()

It is used to specify which requests require what kind of authentication or authorization. Here, anyRequest() and authenticated() are used to indicate that all requests require authentication.

http.authorizeHttpRequests()

Indicates that we use HttpBasic authentication.

After compiling and starting the application, you will find that the terminal will still output the password:

Using generated security password: e2c77467-8c46-4fe1-ab32-eb87558b8c0e

Because we only change the authentication method.

To facilitate the demonstration, we use CURL to directly request the interface:

curl http://localhost:9999

{
	"timestamp": "2022-01-10T02:47:20.820+00:00",
	"status": 401,
	"error": "Unauthorized",
	"path": "/"
}

We will be prompted as Unauthorized, i.e. no authentication.

We add the request header parameter Authorization according to the requirements of HttpBasic. Its value:

Basic Base64(user:e2c77467-8c46-4fe1-ab32-eb87558b8c0e)

Namely:

Basic dXNlcjplMmM3NzQ2Ny04YzQ2LTRmZTEtYWIzMi1lYjg3NTU4YjhjMGU=

Request interface again:

curl -H "Authorization: Basic dXNlcjplMmM3NzQ2Ny04YzQ2LTRmZTEtYWIzMi1lYjg3NTU4YjhjMGU=" http://localhost:9999

index

The authentication is successful and the interface responds normally.

Spring Security custom user name and password

The default user name and random password are not flexible enough. In most scenarios, we need to support multiple users and set corresponding passwords for them respectively, which involves two problems:

  • How to read user name and password (query)
  • How user names and passwords are stored (add / delete / modify)

For reading, Spring Security designs the UserDetailsService interface:

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

loadUserByUsername

This function loads the corresponding user information (UserDetails) from a storage medium according to the user name.

username

User name, the user name written when the client sends a request.

UserDetails

User information, including user name, password, authority and other related information.

Note: user information is not only user name and user password.

For storage, Spring Security designs the UserDetailsManager interface:

public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);

    void updateUser(UserDetails user);

    void deleteUser(String username);

    void changePassword(String oldPassword, String newPassword);

    boolean userExists(String username);
}

createUser

Create user information

updateUser

Modify user information

deleteUser

Delete user information

changePassword

Modify the password of the current user

userExists

Check whether the user exists

Note: UserDetailsManager inherits from UserDetailsService.

In other words, we can define the storage and reading logic of user name, password and other information based on a storage medium by providing a class that implements the interface UserDetailsManager * and rewriting some of its methods; Then inject the instance of this class into Spring Security in the form of Bean to customize the user name and password.

In fact, Spring Security only cares about how to read, and the storage can be implemented by the business system itself; It is equivalent to only implementing the interface UserDetailsService.

Spring Security has preset two common storage media implementations for us:

Both InMemoryUserDetailsManager and JdbcUserDetailsManager implement the interface UserDetailsManager, which is essentially CRUD for UserDetails. We first introduce UserDetails, and then introduce the implementation based on memory and database respectively.

UserDetails

UserDetails is an abstract interface for user information:

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();

}

getUsername

Get user name.

getPassword

Get password.

getAuthorities

Obtaining permission can be simply understood as role name (string), which is used to realize role-based authorized access of the interface. See the following for details.

other

Gets whether the user is available or whether the user / password has expired or locked.

Spring Security provides an implementation class User of UserDetails, which is used to represent the instance of User information. In addition, User provides the object construction method of Builder mode.

UserDetails user = User.builder()
    .username("user")
    .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
    .roles("USER")
    .build();

username

Set the user name.

password

To set the password, Spring Security does not recommend using plaintext string to store the password. The password format is:

{id}encodedPassword

Where, id is the encryption algorithm id, and encodedPassword is the string after password encryption. Here, the encryption algorithm bcrypt is taken as an example. For details, please refer to Password Storage.

roles

Set roles and support multiple roles.

After the UserDetails instance is created, it can be stored and read using the specific implementation of UserDetailsManager.

In Memory

InMemoryUserDetailsManager is a memory based UserDetailsManager provided by Spring Security.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  ...

  @Bean
  public UserDetailsManager users() {
    UserDetails user = User.builder()
            .username("userA")
            .password("{bcrypt}$2a$10$CrPsv1X3hM" +
                    ".giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWN/lxS")
            .roles("USER")
            .build();

    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(user);

    return manager;
  }
}
  1. Create a user information instance user with the user name of userA and the password of 123456 (encrypted by Bcrypt algorithm); Authentication and role participation are required, but roles must be set, which is specified here as user;
  2. Create the InMemoryUserDetailsManager instance manager;
  3. Use the createUser method to store the user in the manager; It is equivalent to storing user information in the memory medium;
  4. Return to manager;

Use @ Bean to inject the InMemoryUserDetailsManager instance into Spring Security.

After creating the InMemoryUserDetailsManager instance, you do not have to call createUser immediately to add user information. You can also get the injected InMemoryUserDetailsManager and dynamically store the UserDetails instance in other parts of the business system.

Compile and start the application, and use the user name and password (userA/123456) created by ourselves to access the interface:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999

index

The user name and password customized based on memory media have taken effect, and the interface responds normally.

JDBC

JdbcUserDetailsManager is a database based UserDetailsManager provided by Spring Security. Compared with InMemoryUserDetailsManager, it is slightly more complex to use. We need to create a data table and prepare the data source required for database connection. The creation of an instance of JdbcUserDetailsManager depends on the data source.

JdbcUserDetailsManager can share a database data source instance with the business system. This article does not discuss the related configuration of the data source.

Taking MySQL as an example, create a data table statement:

create table users(
    username varchar(50) not null primary key,
    password varchar(500) not null,
    enabled boolean not null
);

create table authorities (
    username varchar(50) not null,
    authority varchar(50) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);

create unique index ix_auth_username on authorities (username,authority);

Other database statements can be referenced User Schema.

Creation and injection of JdbcUserDetailsManager instance, except

  • Obtain the injected data source instance dataSource;
  • When creating an instance, you need to pass in the data source instance dataSource;

In addition, the overall process is similar to that of inmemoryuserdetails manager and will not be repeated.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  ......

  @Autowired
  private DataSource dataSource;

  @Bean
  public UserDetailsManager users() {
    UserDetails user = User.builder()
            .username("user")
            .password("{bcrypt}$2a$10$CrPsv1X3hM" +
                    ".giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWN/lxS")
            .roles("USER")
            .build();

    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    manager.createUser(user);

    return manager;
  }
}

Get the injected JdbcUserDetailsManager instance in the business system, and you can dynamically store the UserDetails instance.

Compile and start the application, and use the user name and password (userA/123456) created by ourselves to access the interface:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999

index

The user name and password customized based on the database media have taken effect, and the interface responds normally.

Spring Security authentication

Spring Security can provide role-based permission control:

  • Different users can belong to different roles
  • Different roles can access different interfaces

Suppose that there are two roles, USER and ADMIN,

The role USER can access the interface / hello/name,
The role ADMIN can access the interface / hello/world,
All users can access the interface / after authentication.

We need to reset HttpSecurity according to the above requirements:

  protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize ->
                    authorize
                            .mvcMatchers("/hello/name").hasRole("USER")
                            .mvcMatchers("/hello/world").hasRole("ADMIN")
                            .anyRequest().authenticated())
            .httpBasic();
  }

mvcMatchers("/hello/name").hasRole("USER")

Set the role USER to access the interface / hello/name.

mvcMatchers("/hello/world").hasRole("ADMIN")

Set the role ADMIN to access the interface / hello/world.

anyRequest().authenticated()

You can access it after setting other interface authentication.

mvcMatchers supports the use of wildcards.

Create users belonging to the roles USER and ADMIN:

USER name: userA, password: 123456, role: USER
User name: userB, password: abcdef, role: ADMIN

  @Bean
  public UserDetailsManager users() {
    UserDetails userA = User.builder()
            .username("userA")
            .password("{bcrypt}$2a$10$CrPsv1X3hM.giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWN/lxS")
            .roles("USER")
            .build();

    UserDetails userB = User.builder()
            .username("userB")
            .password("{bcrypt}$2a$10$PES8fUdtRrQ9OxLqf4CofOfcXBLQ3lkY2TSIcs1E9A0z2wECmZigG")
            .roles("ADMIN")
            .build();

    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);

    manager.createUser(userA);
    manager.createUser(userB);

    return manager;
  }

For user userA:

Access the interface / using the user name and password of user userA:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999

index

It is authenticated and can be accessed normally.

Use the user name and password of user userA to access the interface / hello/name:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999/hello/name

hello name

Authentication passed, authentication passed, and normal access is available.

Use the user name and password of user userA to access the interface / hello/world:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999/hello/world

{
	"timestamp": "2022-01-10T13:11:18.032+00:00",
	"status": 403,
	"error": "Forbidden",
	"path": "/hello/world"
}

After authentication, user userA does not belong to the role ADMIN and access is prohibited.

Access the interface / using the user name and password of user userA:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999

index

It is authenticated and can be accessed normally.

For user userB:

Access the interface / using the user name and password of user userB:

curl -H "Authorization: Basic dXNlckI6YWJjZGVm" http://localhost:9999

index

It is authenticated and can be accessed normally.

Use the user name and password of user userB to access the interface / hello/world:

curl -H "Authorization: Basic dXNlckI6YWJjZGVm" http://localhost:9999/hello/world

hello world

Authentication passed, authentication passed, and normal access is available.

Use the user name and password of user userB to access the interface / hello/name:

curl -H "Authorization: Basic dXNlckI6YWJjZGVm" http://localhost:9999/hello/name

{
	"timestamp": "2022-01-10T13:18:29.461+00:00",
	"status": 403,
	"error": "Forbidden",
	"path": "/hello/name"
}

After authentication, USER userB does not belong to the role USER and access is prohibited.

It may be a little strange here. Generally, we think that the administrator should have all the permissions of ordinary users, that is, ordinary users can access the interface / hello/name, so the administrator should also access the interface / hello/name. How to achieve it?

Method 1: set USER userB to have roles USER and ADMIN at the same time;

    UserDetails userB = User.builder()
            .username("userB")
            .password("{bcrypt}$2a$10$PES8fUdtRrQ9OxLqf4CofOfcXBLQ3lkY2TSIcs1E9A0z2wECmZigG")
            .roles("USER", "ADMIN")
            .build();

This way is a little "elegant".

Method 2: set the role ADMIN to include USER;

Spring Security has a Hierarchical Roles Can support inclusion operations between roles.

There are two things to pay special attention to when using this feature:

  1. authorizeRequests
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests(authorize ->
                    authorize
                            .mvcMatchers("/hello/name").hasRole("USER")
                            .mvcMatchers("/hello/world").hasRole("ADMIN")
                            .mvcMatchers("/").authenticated())
            .httpBasic();
  }

Httpsecurity was used earlier The authorizehttprequests method needs to be changed to httpsecurity The authorizerequests method.

  1. RoleHierarchy
  @Bean
  RoleHierarchy hierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

    return hierarchy;
  }

Use RoleHierarchy to define the hierarchical relationship between roles in the form of Bean; Where, "ROLE_" Is a fixed prefix required by Spring Security.

Compile and start the application, and use the user name and password of user userB to access the interface / hello/name:

curl -H "Authorization: Basic dXNlckI6YWJjZGVm" http://localhost:9999/hello/name

hello name

Authentication passed, authentication passed, and normal access is available.

If you enable the debug log level of Spring Security, you can see the following log output when accessing the interface:

From the roles [ROLE_ADMIN] one can reach [ROLE_USER, ROLE_ADMIN] in zero or more steps.

It can be seen that Spring Security can deduce from the role ADMIN that the USER actually owns two roles: USER and ADMIN.

Special note

Hierarchical Roles The examples in the document have obvious errors:

@Bean
AccessDecisionVoter hierarchyVoter() {
    RoleHierarchy hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
            "ROLE_STAFF > ROLE_USER\n" +
            "ROLE_USER > ROLE_GUEST");
    return new RoleHierarcyVoter(hierarchy);
}

The method setHierarchy does not exist in the interface RoleHierarchy. The method of combining authorizeRequests and RoleHierarchy mentioned above is obtained by combining network search and its own practice, which is for reference only.

In addition, the combination of authorizeHttpRequests and RoleHierarchy has no effect. The differences between authorizeHttpRequests and authorizeHttpRequests can be referred to separately Authorize HttpServletRequests with AuthorizationFilter and Authorize HttpServletRequest with FilterSecurityInterceptor.

The premise of authentication needs to pass the authentication; The status code of failed authentication is 401, and the status code of failed authentication is 403, which are different.

Spring Security exception handler

Spring Security exceptions are mainly divided into two types: authentication failure exception and authentication failure exception. When an exception occurs, the corresponding default exception handler will be used for processing, that is, authentication failure exception handler and authentication failure exception handler.

Different authentication or authentication mechanisms are used, and different default exception handlers may be used.

Authentication failure exception handler

Spring Security authentication failed. Exception handler:

public interface AuthenticationEntryPoint {
  void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException;
}

As mentioned earlier, when authentication fails, Spring Security uses the default authentication failure processor to return:

{
	"timestamp": "2022-01-10T02:47:20.820+00:00",
	"status": 401,
	"error": "Unauthorized",
	"path": "/"
}

If you want to customize the returned content, you can customize the authentication failure processor:

  AuthenticationEntryPoint authenticationEntryPoint() {
    return (request, response, authException) -> response
            .getWriter()
            .print("401");
  }
  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
            ...
            .httpBasic()
            .authenticationEntryPoint(authenticationEntryPoint());
  }

authenticationEntryPoint() will create and return a custom AuthenticationEntryPoint instance; Where httpservletresponse getWriter(). Print () writes what we want to return: 401.

httpBasic().authenticationEntryPoint(authenticationEntryPoint()) replaces the default basic AuthenticationEntryPoint of HttpBasic with our customized AuthenticationEntryPoint.

Compile and start the application, and use the incorrect user name and password to access the interface /:

curl -H "Authorization: Basic error" http://localhost:9999

401

If the authentication fails, use our customized content 401 to return.

Authentication failure exception handler

Spring Security authentication failed. Exception handler:

public interface AccessDeniedHandler {
  void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException;
}

As mentioned earlier, when authentication fails, Spring Security uses the default authentication failure processor to return:

{
	"timestamp": "2022-01-10T13:18:29.461+00:00",
	"status": 403,
	"error": "Forbidden",
	"path": "/hello/name"
}

If you want to customize the returned content, you can customize the authentication failure processor:

  AccessDeniedHandler accessDeniedHandler() {
    return (request, response, accessDeniedException) -> response
            .getWriter()
            .print("403");
  }
  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
            ...
            .httpBasic()
            .authenticationEntryPoint(authenticationEntryPoint())
            .and()
            .exceptionHandling()
            .accessDeniedHandler(accessDeniedHandler());
  }

The process of user-defined authentication failure processor is similar to that of authentication failure processor and will not be repeated.

Compile and start the application, and use the user name and password of user userA to access the interface / hello/world:

curl -H "Authorization: Basic dXNlckE6MTIzNDU2" http://localhost:9999/hello/world

403

If authentication fails, use our customized content 403 to return.

Particular attention

Exceptionhandling () also has an authenticationentrypoint () method; For HttpBasic, use exceptionHandling() authenticationEntryPoint() failed to set user-defined authentication. The processor will not take effect. The specific reasons need to be studied by ourselves.

Spring Security custom authentication

Two authentication methods are introduced earlier: FormLogin and HttpBasic. Spring Security also provides several other authentication methods. For details, please refer to Authentication Mechanisms.

If we want to implement our own authentication method, it is also relatively simple. Spring Security is essentially a filter. We can implement our own authentication filter and add it to Spring Security.

  Filter preAuthenticatedFilter() {
    return (servletRequest, servletResponse, filterChain) -> {
      ...
      UserDetails user = User
              .builder()
              .username("xxx")
              .password("xxx")
              .roles("USER")
              .build();

      UsernamePasswordAuthenticationToken token =
              new UsernamePasswordAuthenticationToken(
                      user,
                      user.getPassword(),
                      user.getAuthorities());

      SecurityContext context =
              SecurityContextHolder.createEmptyContext();
      context.setAuthentication(token);

      SecurityContextHolder.setContext(context);

      filterChain.doFilter(servletRequest, servletResponse);
    };
  }

Core implementation process of authentication filter:

  1. Use the information in the Http request (servletRequest) to complete the user-defined authentication process (omitted). Possible situations:

    • Check that the user name and password in the request match
    • Check whether the Token in the request is valid
    • other
      If the authentication is successful, continue to the next step; If the authentication fails, you can throw an exception or skip the subsequent steps;
  2. Extract the username from the Http request, use the injected UserDetailsService instance, and load UserDetails (user information) (omitted);
    For simplicity, simulate creating a user information instance user; At this step, the user has been authenticated successfully, and the user name and password can be set at will. In fact, only the role is necessary. We set the role of the authenticated user as user.

  3. Create user authentication ID;
    Spring Security relies on authentication internally Isauthenticated() to determine whether the user has been authenticated. UsernamePasswordAuthenticationToken is a specific implementation of authentication. You need to pay attention to the construction method and parameters used when creating an instance. Authentication will be called inside the construction method setAuthenticated(true).

  4. Create and set the environment context SecurityContext;
    The user authentication ID is saved in the environment context: context setAuthentication(token).

Particular attention

Except for the exception thrown, filterchain doFilter(servletRequest, servletResponse); It must be guaranteed to be implemented.

There will be many concepts involved in understanding the authentication filter. For details, please refer to Servlet Authentication Architecture.

After the authentication filter is created, it can be added to Spring Security:

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
            ......
            .addFilterBefore(preAuthenticatedFilter(),
                    ExceptionTranslationFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint())
            .accessDeniedHandler(accessDeniedHandler());
  }

According to our different configurations, Spring Security will automatically assemble a filter chain for us in a certain order, and complete authentication through several filters on the chain. We need to add the user-defined authentication filter to the appropriate position of the chain. This is the selected position, which is in front of the exception translation filter.

The order of filter chains can be referenced Security Filters.
The function of ExceptionTranslationFilter can be referenced Handling Security Exceptions.

Particular attention

When using a custom authentication filter, you can customize the setting method of authentication failure exception handler and authentication failure exception handler.

After compiling and starting the application, we will find that we can directly access the interface / and / hello/name without filling in any authentication information, because the simulated USER has been authenticated and the role is USER; When accessing the interface / hello/world, a prompt 403 will appear.

epilogue

Spring Security itself contains a lot of content, and the official documents can not well describe the use methods of each functional feature. Many times, we need to practice as much as possible and gradually deepen our understanding according to the documents, examples, source code and the sharing of others.

Added by fighter1430 on Tue, 11 Jan 2022 07:23:50 +0200