preface

In Web API interface services scenarios, user authentication and authentication is a common requirement. Spring Security is said to be the de facto standard in this field. In practice, the overall design does have many commendable points. It also proves to some extent that what the guys often say is “too complicated” is also true.

This article takes a simple SpringBoot Web application as an example and focuses on the following:

  • Demonstrate the configuration method of Spring Security interface authentication and authentication.

  • This section uses memory and database as examples to describe the storage and reading mechanisms of authentication and authentication data.

  • Custom implementation of several modules, including: authentication filter, authentication or authentication failure handler, etc.

SpringBoot sample

Create a SpringBoot example to demonstrate the application of Spring Security in the SpringBoot environment. There are 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>Copy the code

Boot-example is the SpringBoot project submodule (Module) for demonstration purposes.

Note: The version of the dependency is declared in the project POM.xml dependencyManagement.

SpringBoot application.yml


spring:

application:

name: example

server:

port: 9999

logging:

level:

root: info

Copy the code

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"; }}Copy the code

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"; }}Copy the code

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

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


http://localhost:9999

index

http://localhost:9999/hello/world

hello world

http://localhost:9999/hello/name

hello name

Copy the code

The SpringBoot example is ready.

SpringBoot integrates Spring Security

To integrate Spring Security with SpringBoot, you only need to add the dependency spring-boot-starter-security to pop. XML, as follows:


<dependencies>

...

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

</dependencies>

Copy the code

To compile the startup application, we can see two special lines of log on the command line terminal, as opposed to the normal SpringBoot application:

The 2022-01-09 16:05:57. 87581-437 the INFO [main]. S.S.U serDetailsServiceAutoConfiguration: Using generated security password: 3 ef27867 - e938 f0deab7b 16:05:57 2022-01-09-4 fa4 - b5da - 5015. 87581-525 the INFO [main] O.S.S.W eb. 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]Copy the code

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

  1. Enable the FormLogin login authentication mode.

We use the browser request interface / :


http://localhost:9999/

Copy the code

You’ll see that the request is redirected to the page /login:


http://localhost:9999/login

Copy the code

Prompt to login with username and password:

  1. Generate a user name and password for login;

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


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

Copy the code

Every time an application is started, the password is regenerated randomly.

  1. Register filters for authentication and authentication;

Spring Security is essentially implemented through filters, or chains of filters, through which each interface request is sequentially “filtered” and each filter takes on its own responsibilities, which are combined to complete authentication and authentication.

The registered filters will vary depending on the configuration. By default, the list of loaded filters can refer to the startup log:


WebAsyncManagerIntegrationFilter

SecurityContextPersistenceFilter

HeaderWriterFilter

CsrfFilter

LogoutFilter

UsernamePasswordAuthenticationFilter

DefaultLoginPageGeneratingFilter

DefaultLogoutPageGeneratingFilter

BasicAuthenticationFilter

RequestCacheAwareFilter

SecurityContextHolderAwareRequestFilter

AnonymousAuthenticationFilter

SessionManagementFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

Copy the code

Sign in using the user name and password Spring Security generated for us by default. Upon success, we are automatically redirected to / :


index

Copy the code

We can then request /hello/world and /hello/name via the browser as normal.

By default, Spring Security only supports formLogin-based authentication. Only fixed user names and randomly generated passwords can be used. Authentication is not supported. If you want to use richer security features:

  • Other authentication modes, such as HttpBasic

  • Customize the user name and password

  • authentication

We need to customize Spring Security configuration. Custom configuration can be implemented in two ways:

  • Java Configuration: Configuration using Java code

  • Security NameSpace Configuration: Configures the NameSpace in AN XML file

Based on Java as an example to introduce the Configuration, we need to provide a class inherits from WebSecurityConfigurerAdapter Configuration, and then by rewriting some methods for implementing custom configurations.


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 {

}

}

Copy the code

SecurityConfig using the @ Configuration annotations (Configuration), inherited from WebSecurityConfigurerAdapter, custom Configuration was achieved by rewriting the configure method in this paper.

Note: WebSecurityConfigurerAdapter have multiple names for the configure overloading method, used here is of type HttpSecurity method.

Note: For the default automatic Configuration of Spring Security, see Spring Boot Auto Configuration.

Spring Security uses HttpBasic authentication


protected void configure(HttpSecurity http) throws Exception {

http

.authorizeHttpRequests(authorize ->

authorize

.anyRequest()

.authenticated())

.httpBasic();

}

Copy the code

http.authorizeHttpRequests()

Used to specify which requests require what authentication or authorization, anyRequest() and authenticated() mean that all requests require authentication.

http.authorizeHttpRequests()

Indicates that we use HttpBasic authentication.

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


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

Copy the code

Because, we are only changing the authentication method.

CURL CURL CURL CURL CURL CURL CURL CURL CURL

Curl http://localhost:9999 {"timestamp": "2022-01-10t2:47:20.820 +00:00", "status": 401, "error": "Unauthorized", "path": "/" }Copy the code

We are prompted to Unauthorized, that is, there is no authentication.

Add the request header parameter Authorization as required by HttpBasic.


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

Copy the code

That is:


Basic dXNlcjplMmM3NzQ2Ny04YzQ2LTRmZTEtYWIzMi1lYjg3NTU4YjhjMGU=

Copy the code

Request the interface again:


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

index

Copy the code

Authentication succeeds, and the interface responds normally.

Spring Security customizes user names and passwords

The default username and random password are not flexible enough. Most scenarios require us to support multiple users and set corresponding passwords for them respectively, which involves two problems:

  • How to read user name and password (query)

  • How to store username and password (add/Delete/change)

For reading, Spring Security designs the UserDetailsService interface:


public interface UserDetailsService {

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

Copy the code

loadUserByUsername

The implementation loads the corresponding UserDetails from a storage medium according to the username (username).

username

User name. The user name written by the client when sending a request.

UserDetails

User information, including the user name, password, and permission.

Note: The user information is more than the username and 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);

}

Copy the code

createUser

Creating User Information

updateUser

Modifying User Information

deleteUser

Deleting User Information

changePassword

Example Change the password of the current user

userExists

Check whether the user exists

Note: UserDetailsManager inherits from UserDetailsService.

In other words, we can provide a class that has implemented the interface UserDetailsManager*, rewrite some of its methods, and define the storage and reading logic of user name, password and other information based on some storage media. You can then inject an instance of this class into Spring Security as a Bean to customize the user name and password.

In fact, Spring Security is only concerned with reading; the storage can be implemented by the business system itself; It is equivalent to implementing only the interface UserDetailsService.

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

  • InMemoryUserDetailsManager, based on the realization of the memory

  • JdbcUserDetailsManager, database based implementation

InMemoryUserDetailsManager JdbcUserDetailsManager and implementing an interface UserDetailsManager, nature is populated UserDetails CRUD. We’ll start with UserDetails, then move on to the memory-based and database-based implementations, respectively.

UserDetails

UserDetails is an abstract interface to user information:


public interface UserDetails extends Serializable {

Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();

}

Copy the code

getUsername

Gets the user name.

getPassword

Get the password.

getAuthorities

Obtain permission, which can be simply defined as role name (string), used to implement role-based authorized access on the interface. For details, see the following section.

other

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

Spring Security provides an implementation class of UserDetails, User, for instance representation of User information. In addition, User provides a way to build objects in Builder mode.


UserDetails user = User.builder()

.username("user")

.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")

.roles("USER")

.build();

Copy the code

username

Set the user name.

password

Spring Security does not recommend storing passwords in plain text. The password format is as follows:


{id}encodedPassword

Copy the code

Id is the id of the encryption algorithm, and encodedPassword is the character string after the password is encrypted. This section uses the encryption algorithm bcrypt as an example. For details, see Password Storage.

roles

Set roles. Multiple roles are supported.

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

In Memory

InMemoryUserDetailsManager is Spring Security for our UserDetailsManager based on memory implementation.

@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; }}Copy the code
  1. Create user instance user with user name userA and password 123456 (encrypted using Bcrypt algorithm). Roles are required for authentication, but roles must be set, specified here as USER;

  2. Create InMemoryUserDetailsManager instance manager;

  3. Use the createUser method to store the user to manager; Equivalent to the user information stored in memory medium;

  4. Return to the manager;

Using the @ Bean will InMemoryUserDetailsManager instance into Spring Security.

Create InMemoryUserDetailsManager instance, does not have to immediately call createUser add user information, Also available in other parts of the business system has injected InMemoryUserDetailsManager dynamic storage populated UserDetails instance.

Compile and start the application using the user name and password we created ourselves (userA/123456) to access the interface:


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

index

Copy the code

The user-defined user name and password based on the memory medium are valid, and the interface responds normally.

JDBC

JdbcUserDetailsManager is Spring Security for our UserDetailsManager based on database implementation, compared with InMemoryUserDetailsManager use slightly complicated, We need to create the data table and prepare the DataSource for the database connection. The creation of the JdbcUserDetailsManager instance depends on the DataSource.

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

MySQL > create table clause;


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);

Copy the code

For other database statements, see User Schema.

JdbcUserDetailsManager instance creation and injection, except

  • Get the injected dataSource instance dataSource;

  • To create an instance, pass in the dataSource instance dataSource.

, the whole process is similar to InMemoryUserDetailsManager, no longer here.

@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; }}Copy the code

Obtain the injected JdbcUserDetailsManager instance from the service system to dynamically store the UserDetails instance.

Compile and start the application using the user name and password we created ourselves (userA/123456) to access the interface:


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

index

Copy the code

The user-defined user name and password based on the database media are valid, 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

Assume that there are two roles, USER (common USER) and ADMIN (administrator),

The role USER can access the interface /hello/name,

The ADMIN role can access the interface /hello/world,

After authentication, all users can access the interface /.

We need to reset HttpSecurity as described above:

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

mvcMatchers(“/hello/name”).hasRole(“USER”)

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

mvcMatchers(“/hello/world”).hasRole(“ADMIN”)

Set role ADMIN to access interface /hello/world.

anyRequest().authenticated()

You can access the interface only after other interfaces are authenticated.

MvcMatchers supports the use of wildcards.

Create users belonging to 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;

}

Copy the code

For user userA:

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


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

index

Copy the code

The user is authenticated and can be accessed normally.

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


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

hello name

Copy the code

The authentication is successful and the access is normal.

Use user name and password to access 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"}Copy the code

If the authentication succeeds, user userA does not belong to the ADMIN role and cannot be accessed.

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


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

index

Copy the code

The user is authenticated and can be accessed normally.

For user userB:

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


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

index

Copy the code

The user is authenticated and can be accessed normally.

Use userB’s username and password to access the interface /hello/world:


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

hello world

Copy the code

The authentication is successful and the access is normal.

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

curl -H "Authorization: Basic dXNlckI6YWJjZGVm" http://localhost:9999/hello/name { "timestamp": "The 2022-01-10 T13: disguise. 461 + 00:00", "status" : 403, "error" : "who", "path" : "/ hello/name}"Copy the code

If the authentication succeeds, userB does not belong to the role USER and cannot be accessed.

This might be a bit strange, but in general we would expect an administrator to have full access to the interface /hello/name, so the administrator should also have access to the interface /hello/name. How do you do that?

Method 1: Set USER userB to have roles USER and ADMIN.


UserDetails userB = User.builder()

.username("userB")

.password("{bcrypt}$2a$10$PES8fUdtRrQ9OxLqf4CofOfcXBLQ3lkY2TSIcs1E9A0z2wECmZigG")

.roles("USER", "ADMIN")

.build();

Copy the code

This approach is a little less “elegant”.

Method 2: Set the role ADMIN to include USER.

Spring Security has a Hierarchical Roles feature that supports inclusion 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();

}

Copy the code

Above are using HttpSecurity authorizeHttpRequests method, the need to change to HttpSecurity. AuthorizeRequests method.

  1. RoleHierarchy

@Bean

RoleHierarchy hierarchy() {

RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();

hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

return hierarchy;

}

Copy the code

RoleHierarchy is used to define hierarchical relationships between roles as beans. Where “ROLE_” is the fixed prefix required by Spring Security.

Use userB’s username and password to access the interface /hello/name:


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

hello name

Copy the code

The authentication is successful and the access is normal.

If the debug log level of Spring Security is enabled, 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.

Copy the code

As you can see, Spring Security can infer from the role ADMIN that the USER actually has both USER and ADMIN roles.

Special instructions

The example in the Hierarchical Roles documentation is obviously wrong:


@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);

}

Copy the code

The method setHierarchy does not exist in interface RoleHierarchy. The method of combining authorizeRequests and RoleHierarchy mentioned above is based on the combination of Internet search and personal practice, which is only for reference.

In addition, authorizeHttpRequests with RoleHierarchy do not work, AuthorizeRequests and authorizeHttpRequests are identified by reference to Authorize HttpServletRequests with AuthorizationFilter and authorizeHttpRequests respectively The Authorize it with FilterSecurityInterceptor.

Before authentication, you must pass the authentication. The status code of the authentication failure is 401, and the status code of the authentication failure is 403.

Spring Security exception handler

Spring Security exceptions are classified into two types: authentication failure exceptions and authentication failure exceptions. When an exception occurs, the corresponding default exception handler is used to handle the exception, namely, authentication failure exception handler and authentication failure exception handler.

The default exception handler may be used depending on the authentication or authentication implementation mechanism used.

Authentication failure exception handler

Spring Security authentication failed


public interface AuthenticationEntryPoint {

void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException;

}

Copy the code

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

{" timestamp ":" the 2022-01-10 T02:47:20. 820 + 00:00 ", "status" : 401, "error" : "Unauthorized", "path" : "/"}Copy the code

If you want to customize what is returned, you can do this by customizing the authentication failure handler:


AuthenticationEntryPoint authenticationEntryPoint() {

return (request, response, authException) -> response

.getWriter()

.print("401");

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http

...

.httpBasic()

.authenticationEntryPoint(authenticationEntryPoint());

}

Copy the code

AuthenticationEntryPoint () creates and returns a custom instance of authenticationEntryPoint; Among them, use the HttpServletResponse. GetWriter (). Print () write we want to return the content: 401.

HttpBasic (). AuthenticationEntryPoint (authenticationEntryPoint ()) to use our custom authenticationEntryPoint replace httpBasic default BasicAuthenticationEntryPoint.

Compiling and starting the application using incorrect username and password to access interface / :


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

401

Copy the code

Authentication failed, return with our custom content 401.

Authentication failed exception handler

Spring Security authentication failed


public interface AccessDeniedHandler {

void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException;

}

Copy the code

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

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

If you want to customize what is returned, you can customize the authentication failure handler:


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());

}

Copy the code

The process of customizing the authentication failure handler is similar to that of the authentication failure handler.

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


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

403

Copy the code

Authentication failed, use our custom content 403 return.

Pay special attention to

ExceptionHandling () also has an authenticationEntryPoint() method; Use for HttpBasic exceptionHandling (.) authenticationEntryPoint () sets the custom authentication failed processor is not effective, need everybody to study specific reasons.

Spring Security custom authentication

Spring Security also provides a number of other Authentication Mechanisms. For details, see Authentication Mechanisms.

If we want to implement our own authentication mode, it is relatively simple. Spring Security is essentially a filter, and we can implement our own authentication filters and add them 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);

};

}

Copy the code

Authentication filter core implementation process:

  1. Complete a custom authentication process (omitted) using the information in the Http request (servletRequest), where:
  • Check that the username and password in the request match

  • Check whether the Token in the request is valid

  • other

If the authentication succeeds, go to the next step. If the authentication fails, you can throw an exception or skip subsequent steps.

  1. Extract username from the Http request and load the UserDetailsService (omitted) using the injected UserDetailsService instance;

For simplicity, simulate creating a user information instance user; At this point, the USER has been authenticated, and the USER name and password can be set at will. In fact, only the role is required. We set the role of the authenticated USER to USER.

  1. Create a user authentication id;

Spring Security depend on internal Authentication. IsAuthenticated () to determine whether the user has certification, UsernamePasswordAuthenticationToken Authentication is a kind of specific implementation, need to pay attention to create an instance of construction methods and parameters, Inside the constructor is called Authentication. SetAuthenticated (true).

  1. Create and set the environment context SecurityContext;

Environmental context holds the user authentication identity: context. SetAuthentication (token).

Pay special attention to

Filterchain.dofilter (servletRequest, servletResponse); It has to be carried out.

To understand the concepts involved in Authentication filters, see Servlet Authentication Architecture for details.

Once the authentication filters are created, they can be added to Spring Security:


@Override

protected void configure(HttpSecurity http) throws Exception {

http

......

.addFilterBefore(preAuthenticatedFilter(),

ExceptionTranslationFilter.class)

.exceptionHandling()

.authenticationEntryPoint(authenticationEntryPoint())

.accessDeniedHandler(accessDeniedHandler());

}

Copy the code

According to our configuration, Spring Security will automatically assemble a chain of filters for us in a certain order, and complete authentication through several filters on this chain. We need to add a custom authentication filters to the right position, this chain this is selected location is in front of ExceptionTranslationFilter.

See Security Filters for the order of the filter chain.

ExceptionTranslationFilter can refer to the action of Handling Security Exceptions.

Pay special attention to

This section describes how to set a custom authentication failure exception handler and authentication failure exception handler when a custom authentication filter is used.

When we compile and start the application, we see that we can access the interface/and /hello/name directly without filling in any authentication information, because the emulated USER is authenticated and the role is USER; When accessing interface /hello/world, prompt 403 appears.

conclusion

Spring Security itself contains a lot of content, and the official documentation is not very clear about how to use each feature. Most of the time, we need to deepen our understanding by practicing as much as possible according to the documents, examples, source code and others’ sharing.