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:
-
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:
-
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.
-
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:
- InMemoryUserDetailsManager , memory based implementation
- JdbcUserDetailsManager , database based implementation
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; } }
- 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;
- Create the InMemoryUserDetailsManager instance manager;
- Use the createUser method to store the user in the manager; It is equivalent to storing user information in the memory medium;
- 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:
- 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.
- 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:
-
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;
-
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. -
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). -
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.