Spring Security Learning Notes

1. Introduction to spring security framework

1.1 overview

Spring is a very popular and successful Java application development framework, and Spring Security is a member of the spring family. Based on the spring framework, Spring Security provides a complete solution for Web application security.

As you may know, the two main areas of security are "authentication" and "authorization" (or access control)

Generally speaking, the security of Web applications includes * * user Authentication and user authorization**

(Authorization, which are also important core functions of Spring Security.

  • (1) User authentication refers to verifying whether a user is a legal entity in the system, that is, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password. In common, it means that the system thinks whether the user can log in

  • (2) User authorization refers to verifying whether a user has permission to perform an operation. In a system, different users have different permissions. For example, for a file, some users can only read, while others can modify. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions Limit. Generally speaking, the system judges whether the user has permission to do something

1.2 comparison of the same product

1.2.1 Spring Security

Part of the Spring technology stack

Protect your applications by providing complete and extensible authentication and authorization support. https://spring.io/projects/spring-security

SpringSecurity features:

  • Seamless integration with Spring.

  • Comprehensive authority control.

  • Designed specifically for Web development.

    • The old version cannot be used away from the Web environment.

    • In the new version, the whole framework is extracted hierarchically, which is divided into core module and Web module.

  • Heavyweight.

1.2.2 Shiro

Apache's lightweight permission control framework.

characteristic:

  • Lightweight. Shiro advocates the idea of making complex things simple. It has better performance for Internet applications with higher performance requirements.

  • generality.

    • Benefits: it is not limited to the Web environment and can be used without the Web environment.
    • Defect: in the Web environment, some specific requirements require manual code customization.

1.2.3 comparison and summary

Spring Security is a security management framework in the spring family. In fact, Spring Security has been developed for many years before the emergence of Spring Boot, but it is not used much. The field of security management has always been Shiro's world.

Compared with Shiro, integrating Spring Security in SSM is a troublesome operation. Therefore, although Spring Security has more powerful functions than Shiro, it is not used as much as Shiro (although Shiro has less functions than Spring Security, Shiro is enough for most projects).

Since Spring Boot came into being, Spring Boot provides an automated configuration scheme for Spring Security, which allows you to use Spring Security with fewer configurations.

Therefore, generally speaking, the common combination of security management technology stacks is as follows:

  • SSM + Shiro

  • Spring Boot/Spring Cloud + Spring Security

The above is just a recommended combination. Technically speaking, no matter how it is combined, it can run

2. Introduction to spring security

2.1 introduction case demonstration

  • Create a project

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
  • Write Controller code
@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/hello")
    public String hello(){
        return "hello security!";
    }

}
  • Add tomcat port configuration in applicationContext.xml: server.port=8081

  • Access this item

    Default user name: user

    The password will be printed on the console when the project is started. Note that the password will change every time it is started!

Access succeeded!!!

2.5 spring Security Fundamentals

Spring security is essentially a chain of filters
The filter chain can be obtained from startup

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

Code bottom process: focus on three filters:

  • FilterSecurityInterceptor: it is a method level permission filter, which is basically located at the bottom of the filter chain.

super.beforeInvocation(fi) means to check whether the previous filter has passed. fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); means to actually call the background service.

  • ExceptionTranslationFilter: an exception filter used to handle exceptions thrown during authentication and authorization

  • UsernamePasswordAuthenticationFilter: intercept the POST request of / login and verify the user name and password in the form.

Diagram of filter loading process:

2.6 explanation of userdetailsservice interface

When nothing is configured, the account and password are generated by Spring Security definition.

In the actual project, the account and password are queried from the database. Therefore, we need to control the authentication logic through the custom logic. If the custom logic is required, we only need to implement the UserDetailsService interface. The interface definition is as follows:

  • Return value UserDetails

    This class is the system default user "principal"

// Means to obtain all permissions of the login user
Collection<? extends GrantedAuthority> getAuthorities();
// Indicates obtaining a password
String getPassword();
// Indicates to get the user name
String getUsername();
// Indicates whether the account has expired
boolean isAccountNonExpired();
// Indicates whether the account is locked
boolean isAccountNonLocked();
// Indicates whether the voucher {password} has expired
boolean isCredentialsNonExpired();
// Indicates whether the current user is available
boolean isEnabled();

The following is the inheritance and implementation relationship of UserDetails

In the future, we only need to use the User entity class

  • Method parameter username

    Indicates the user name. This value is the data passed from the client form. By default, it must be called username, otherwise it cannot be received.

2.7 PasswordEncoder interface explanation

Interface implementation class:

  • BCryptPasswordEncoder is the password parser officially recommended by Spring Security. It is usually used.

  • BCryptPasswordEncoder is a concrete implementation of bcrypt strong Hash method. It is a one-way encryption based on Hash algorithm. The encryption strength can be controlled through strength. The default is 10

Query method demonstration

@Test
public void test01(){
    // Create password parser
    BCryptPasswordEncoder bCryptPasswordEncoder = new 
    BCryptPasswordEncoder();
    // Encrypt password
    String atguigu = bCryptPasswordEncoder.encode("atguigu");
    // Print encrypted data
    System.out.println("Data after encryption:\t"+atguigu);
    //Judge whether the original characters match after encryption and before encryption
    boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
    // Print comparison results
    System.out.println("Comparison results:\t"+result);
}

2.8 automatic configuration of Security by springboot

https://docs.spring.io/spring-security/site/docs/5.3.4.RELEASE/reference/html5/#servlet-hello

3.SpringSecurity Web permission scheme

3.1 set the account and password to log in to the system

  • Method 1: in application.properties
spring.security.user.name=lxy
spring.security.user.password=123456
  • Method 2: use configuration class
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123456");
        auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
    }

    @Bean  //Add the BCryptPasswordEncoder component to the Spring container because
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }


}
  • Method 3: Customize implementation class settings

Customize a class to implement UserDetailsService, and set the login configuration in the loadUserByUsername method

@Service
public class MyUserDetailsService implements UserDetailsService {
    //User is an implementation class of the UserDetails Interface
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //to grant authorization
        List <GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");

        return new User("tom",new BCryptPasswordEncoder().encode("123456"),auths);
    }
}

In the configuration class, set and load the corresponding implementation class

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //Bind userdeialservice implementation class and decode the password
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Bean  //Add the BCryptPasswordEncoder component to the Spring container because
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }
}

Illustration of three ways:

3.2 realize database authentication to complete user login

Train of thought diagram:

Implementation steps:

  • Create data table

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!--lombok Used to simplify entity classes-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
  • Create entity class:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Users {
    private Integer id;
    private String username;
    private String password;
}
  • Create UsersMapper interface
@Mapper //@Mapper: added @ mapper on the interface class. When mapper runs, it generates the interface implementation class through dynamic proxy
@Repository //Spring annotation, load it into the spring container
public interface UsersMapper extends BaseMapper<Users> {
}
  • Write login implementation class
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersMapper usersMapper;
    //User is an implementation class of the UserDetails Interface
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //Write query criteria
        QueryWrapper <Users> wrapper = new QueryWrapper <>();
        //where username=?
        wrapper.eq("username",username);
        Users users = usersMapper.selectOne(wrapper);
        //judge
        if(users==null){//There is no user name in the database. Authentication failed
            throw new UsernameNotFoundException("user name does not exist!");
        }
        //to grant authorization
        List <GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
 		//When entering, the password of this user name will match the password entered by the user. If it cannot match exactly, it cannot be accessed
        return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}
  • Write database configuration information in yaml file
server:
  port: 8081
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///demo
    username: root
    password: 186259
  • Test:

When entering the wrong user name and password:

When entering the correct user name and password:

3.3 user defined user login interface

  • Override the config method with the parameter http in SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ......

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()//Customize the login interface written by yourself
                .loginPage("/login.html")//Login page setup
                .loginProcessingUrl("/user/login")//Login access path (controller)
                .defaultSuccessUrl("/test/index").permitAll()//After successful login, jump to the path
                .and().authorizeRequests()//Authorize the requested page
                .antMatchers("/","/test/hello","/user/login").permitAll()//Set which paths can be accessed directly without authentication
                .anyRequest().authenticated()//All requests except the above require authentication
                .and().csrf().disable();//Turn off csrf protection
    }
}

Create a new static folder under resources and write login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    user name:<input type="text" name="username"/>
    <br>
    password:<input type="text" name="password"/>
    <br>
    <input type="submit" value="login"/>
</form>

</body>
</html>

Note: the page submission method must be post request, so the above page cannot be used. The user name and password must be username and password

Reason: a filter UsernamePasswordAuthenticationFilter will be used during login

Underlying source code:

  • How to write index in controller
	@GetMapping("index")
    public String index()
    {
        return "hello index";
    }
  • Test:

If direct access http://localhost:8081/test/hello You do not need to log in

If access http://localhost:8081/test/index You need to jump to the login page and access it after successful authentication

3.4 access control based on roles or permissions

hasAuthority method

Returns true if the current principal has the specified permission; otherwise, returns false

**Modify configuration class: * * that is, the permissions required to access the page

Modify service: user's permission

hasAnyAuthority method

Returns true if the current principal has any provided roles (given as a comma separated string list)

Modify SecurityConfig code

hasRole method

If the user has a given role, access is allowed, otherwise 403 appears.

Returns true if the current principal has the specified role.

Underlying code:

To demonstrate:

Modify profile

Note that "ROLE" does not need to be added to the configuration file_ ", because the underlying code above will be automatically added to match it.

hasAnyRole method

It means that the user can access any condition.

Summary:

3.5 custom 403 page

3.6 use of notes

@Secured

Judge whether there is a ROLE. In addition, it should be noted that the matching string here needs to be prefixed with "ROLE". Before using annotation, you must turn on the annotation function!

@EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}

Write UserController Code:

@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update()
{
   return "hello update!";
}

Authorization in UserService:

//to grant authorization
List <GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_sale");

Can access successfully!

@PreAuthorize

Start annotation function first: @ EnableGlobalMethodSecurity(prePostEnabled = true) @PreAuthorize: annotation is suitable for * * permission verification before entering the method * *, @ PreAuthorize can transfer the roles/permissions parameter of the login user to the method.

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }

}

#####################Controller###################################
@GetMapping("update")
// @Secured({"ROLE_sale","ROLE_manager"})
@PreAuthorize("hasAnyAuthority('sale')")
public String update()
{
    return "hello update!";
} 

################Service##########################################
//to grant authorization
List <GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("sale");      

@PostAuthorize

Turn on the annotation function first: @ EnableGlobalMethodSecurity(prePostEnabled = true) @PostAuthorize annotation is not used much. Permission verification is performed after the method is executed. It is suitable for verifying permissions with return values

@RequestMapping("/update")
@ResponseBody
@PostAuthorize("hasAnyAuthority('sale')")
public String update(){
 System.out.println("update......");
	return "hello update";
}

@PostFilter

@PostFilter: filter the data after permission verification, leaving the data with the user name of admin1

The filterObject in the expression refers to an element in the method return value List

@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_administrators')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
	 ArrayList<UserInfo> list = new ArrayList<>();
     list.add(new UserInfo(1l,"admin1","6666"));
     list.add(new UserInfo(2l,"admin2","888"));
     return list;
}

@PreFilter

@PreFilter: filter the data before entering the controller

For the UserInfo of the List, filter it out if the matching id is even

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_administrators')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> 
list){
     list.forEach(t-> {
     	System.out.println(t.getId()+"\t"+t.getUsername());
     });
    return list;
}

3.7 user logout

Add an exit connection on the login page

Write success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    Login succeeded!
    <a href="/logout">sign out</a>
</body>
</html>

Configure:

Test:

3.7 automatic login

Principle analysis:

Source code view:

  • Write the Token to the bottom layer of the browser Cookie: UsernamePasswordAuthenticationFilter

It can be seen that the underlying essence is to create a Token string containing user information and store it in a Cookie

  • The underlying principle of writing tokens to the database: JDBC tokenrepositoryimpl

  • The underlying principle of automatic login: membermeauthenticationfilter

Code demonstration

  • Create a table that stores the Token database
CREATE TABLE persistent_logins (username VARCHAR(64) NOT NULL,
 series VARCHAR(64) PRIMARY KEY,  token VARCHAR(64) NOT NULL,
last_used TIMESTAMP NOT NULL)
  • Write configuration class:
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    //Injection data source
    @Autowired
    private DataSource dataSource;

    //The configuration object PersistentTokenRepository is the upper interface of JDBC tokenrepository. Polymorphism is used here
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
   

    @Bean  //Add the BCryptPasswordEncoder component to the Spring container because
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //After the configuration exit function exits, enter the / test/hello page
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
        //Configure that you do not have permission to access, and then jump to the custom page
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin()//Customize the login interface written by yourself
                .loginPage("/login.html")//Login page setup
                .loginProcessingUrl("/user/login")//Login access path (controller)
                .defaultSuccessUrl("/success.html").permitAll()//After successful login, jump to the path
                .and().authorizeRequests()//Authorize the requested page
                .antMatchers("/","/test/hello","/user/login").permitAll()//Set which paths can be accessed directly without authentication
                //hasAuthority method
                // .antMatchers("/test/index").hasAuthority("admins")
                //hasAnyAuthority method
                //.antMatchers("/test/index").hasAnyAuthority("admins,manager")
                // hasRole method
               // .antMatchers("/test/index").hasRole("sale")
                .anyRequest().authenticated()//All requests except the above require authentication
// ############The following is the configuration of automatic login#####################################
                .and().rememberMe().tokenRepository(persistentTokenRepository()) //Set the database operation mode used
                .tokenValiditySeconds(60)//Set the effective duration in seconds
                .userDetailsService(userDetailsService)//Set the custom userDetailsService to be used
                .and().csrf().disable();//Turn off csrf protection
    }
}

  • Modify the code of login.html: add the box of automatic login
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    user name:<input type="text" name="username"/>
    <br>
    password:<input type="text" name="password"/>
    <br>
    <input type="checkbox" name="remember-me"/>automatic logon
    <br>
    <input type="submit" value="login"/>
</form>

</body>
</html>

Test results:

When logging in, select auto log in, and you can see that there is a remember me Cookie in the browser's Cookie

After closing the browser, you can access it directly

Every time you log in again, a statement will be inserted into the database. When you log in next time, you will compare the Token of the browser with that in the database. If the comparison is successful, it indicates that you have logged in

4.SpringSecurity microservice permission scheme

4.1 what is micro service

1. Origin of microservices

Microservices were first proposed by Martin Fowler and James Lewis in 2014. Microservice architecture style is a way to use a set of small services to develop a single application. Each service runs in its own process and uses lightweight mechanisms to communicate, usually HTTP API s. These services are built based on business capabilities and can be independently deployed through automatic deployment mechanisms Deployment, these services are implemented in different programming languages, as well as different data storage technologies, and maintain a minimum of centralized management.

2. Microservice advantages

(1) Each module of microservice is equivalent to a separate project, the amount of code is significantly reduced, and the problems encountered are relatively easy to solve.

(2) Each module of microservice can use different storage methods (for example, some use redis, some use mysql, etc.), and the database is also a database corresponding to a single module.

(3) Each module of microservice can use different development technologies, and the development mode is more flexible.

3. Essence of microservice

(1) In fact, the key to microservices is not just the microservices themselves, but the system should provide a set of basic architecture, which enables microservices to be deployed, run and upgraded independently. Moreover, this system architecture also makes microservices and microservices "loosely coupled" in structure and unified in function. This so-called "unified whole" shows a unified style interface, unified authority management, unified security policy, unified online process, unified log and audit methods, unified scheduling mode, unified access entrance and so on.

(2) The purpose of microservices is to effectively split applications and realize agile development and deployment.

4.2 implementation ideas of micro service authentication and authorization

1. Authentication and authorization process analysis

(1) If it is based on session, spring security will parse the session ID in the cookie, find the session information stored by the server, and then judge whether the current user meets the requirements of the request.

(2) If it is a token, the token is parsed, and then the current request is added to the permission information managed by spring security

If there are many modules in the system, each module needs authorization and authentication, so we choose token based authorization and authentication

  • The user authenticates successfully according to the user name and password, then obtains a series of permission values of the current user role, stores them in the redis cache in the form of user name as key and permission list as value, and generates a token return according to the relevant information of the user name
  • The browser records the token in the cookie. Every time the api interface is called, the token is carried into the header request header by default
  • Spring security parses the header header to obtain token information, parses the token to obtain the current user name, and obtains the permission list from redis according to the user name, so that spring security can judge whether the current request has access permission

2. Rights management data model

3. Case technology introduction

4. Build project works

5. Introduce project dependency

For details, refer to the POM file in resources

6. Start Redis and Nacos

7. Write common tool class

In service_ Create relevant tool classes in base. The contents in resources

4.3 case code implementation

4.3.1 create tools related to certification and authorization

(1) DefaultPasswordEncoder: password processing method

//Password encoder
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

    public DefaultPasswordEncoder() {
        this(-1);
    }
    public DefaultPasswordEncoder(int length) {
    }
    //MD5 encryption
    @Override
    public String encode(CharSequence charSequence) {
        return MD5.encrypt(charSequence.toString());
    }

    //Compare passwords

    /**
     *
     * @param charSequence :Encrypted password
     * @param encodedPassword :Front end incoming password
     * @return
     */
    @Override
    public boolean matches(CharSequence charSequence, String encodedPassword) {
        //MD5 encryption twice is decryption
        return encodedPassword.equals(MD5.encrypt(charSequence.toString()));
    }
}

(2) TokenManager: tool class of token operation

//Token related methods
@Component
public class TokenManager {
    //Valid duration of token
    private long tokenExpiration = 24*60*60*1000;
    //Encoding key
    private String tokenSignKey = "123456";

    //Use jwt to generate a token based on the user name
    public String createToken(String username){
        String token = Jwts.builder().setSubject(username)//Set entity
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))//Set expiration time
                .signWith(SignatureAlgorithm.HS512,
                        tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();//Set secret key
        return token;
    }

    //2. Get the user information according to the token string
    public String getUserInfoFromToken(String token){
        String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return userinfo;
    }

    //3. Delete token can be written or not. If it is deleted, we can just leave it in the request header. It is necessary to delete it
    public void removeToken(String token)
    {

    }
}

(3) TokenLogoutHandler: exit the implementation

//Exiting processor
@Component
public class TokenLogoutHandler implements LogoutHandler {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    //The assignment here is to directly take the object from the Spring container for assignment
    public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        //1. Get the token from the header
        //2. If the token is not empty, remove the token and delete the token from redis
        String token = request.getHeader("token");
        if (token!=null){
            //remove
            tokenManager.removeToken(token);
            //Get user name from token
            String username = tokenManager.getUserInfoFromToken(token);
            redisTemplate.delete(username);
        }
        //Respond to the client success message
        ResponseUtil.out(response, R.ok());
    }
}

(4) Unauthorized entrypoint: unified processing is not authorized

//How to handle authentication failure. This method will be called when authentication fails
public class UnauthEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}

4.3.2 create authentication authorization entity class:

SecurityUser class

@Data
public class SecurityUser implements UserDetails {

    //Current login user
    private transient User currentUserInfo;

    //Current permissions
    private List <String> permissionValueList;

    public SecurityUser() {
    }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }

    @Override
    public Collection <? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList <>();
        for(String permissionValue : permissionValueList) {
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }

        return authorities;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

User class

@Data
@ApiModel(description = "User entity class")
public class User implements Serializable {
    public static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "WeChat openid")
    private String username;

    @ApiModelProperty(value = "password")
    private String password;

    @ApiModelProperty(value = "nickname")
    private String nickName;

    @ApiModelProperty(value = "User Avatar")
    private String salt;

    @ApiModelProperty(value = "User signature")
    private String token;
}

4.3.3 create a filter for authentication and authorization

(1) TokenLoginFilter: an authenticated filter

//Filter for Token login
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    private AuthenticationManager authenticationManager;

    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));
    }

    //1. Get the user name and password of form submission
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        User user = null;
        try {
            user = new ObjectMapper().readValue(request.getInputStream(), User.class);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList <>()));

    }
    //Authenticates the successfully invoked method

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
       //After successful authentication, the user information after successful authentication is obtained
        SecurityUser user = (SecurityUser)authResult.getPrincipal();
        //Generate token based on user name
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        //Put the user name and user permission list in redis
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        //Return token
        ResponseUtil.out(response, R.ok().data("token",token));

    }

    //3. Authentication failure call information
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response,R.error());

    }
}

(2) TokenAuthenticationFilter: authorization filter

//Token authorization filter
public class TokenAuthFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthFilter(AuthenticationManager authenticationManager,  TokenManager tokenManager, RedisTemplate redisTemplate) {
        super(authenticationManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //Obtain the permission information of the user who has successfully authenticated
        UsernamePasswordAuthenticationToken authenRequest = getAuthentication(request);
        //Judge whether there is permission information and put it in the permission context
        if (authenRequest!=null){
            SecurityContextHolder.getContext().setAuthentication(authenRequest);
        }
        chain.doFilter(request,response);


    }
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
        //Get token from header
        String token = request.getHeader("token");
        if(token!=null){
            //Get user name from token
            String username = tokenManager.getUserInfoFromToken(token);
            //Obtain the corresponding permission list from redis
            List <String> permissionValueList = (List <String>)redisTemplate.opsForValue().get(username);
            Collection <GrantedAuthority> authority = new ArrayList <>();
            for (String permissionValue : permissionValueList) {
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
                authority.add(auth);
            }
            return new UsernamePasswordAuthenticationToken(username,token,authority);
        }
        return  null;
    }

}

4.3.4 core configuration of spring security

The core configuration of Spring Security is to inherit the WebSecurityConfigurerAdapter and annotate the configuration of @ EnableWebSecurity. This configuration indicates the user name and password processing method, request path, login and logout control and other security related configurations

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private UserDetailsService userDetailsService;

    @Autowired
    public TokenWebSecurityConfig(TokenManager tokenManager, RedisTemplate redisTemplate, DefaultPasswordEncoder defaultPasswordEncoder, UserDetailsService userDetailsService) {

        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.userDetailsService = userDetailsService;
    }
    /**
     * configuration setting
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthEntryPoint())//Processing without access
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")//Exit path
                .addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and()//Set exit processor
                .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))//Custom login filter and authorization filter
                .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }


    //Call userDetailsService and password processing
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    //The path without authentication can be accessed directly
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**");
    }
}

4.3.5 user defined UserDetailsService

User name and password for database operation

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //Query data according to user name
        User user = userService.selectByUsername(username);
        //judge
        if(user == null) {
            throw new UsernameNotFoundException("user does not exist");
        }
        com.rg.entity.User curUser = new com.rg.entity.User();
        BeanUtils.copyProperties(user,curUser);

        
        //Query user permission list according to user
        List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
        SecurityUser securityUser = new SecurityUser();
        //Save user and permission list to SecurityUser
        securityUser.setCurrentUserInfo(curUser);
        securityUser.setPermissionValueList(permissionValueList);
        return securityUser;
    }
}

4.4 process diagram

5. Principle summary of spring security

5.1 introduction to spring security filters

Spring security adopts the design pattern of responsibility chain, which has a long filter chain. Now describe the 15 filters in this filter chain:

(1) WebAsyncManager integration filter: integrates the Security context with the WebAsyncManager used to handle asynchronous request mapping in Spring Web.

(2) SecurityContextPersistenceFilter: load the security context information related to the request into the SecurityContextHolder before each request is processed. After the request is processed, store the information about the request in the SecurityContextHolder into a "warehouse", and then clear the information in the SecurityContextHolder, For example, maintaining a user's security information in a Session is handled by this filter.

(3) HeaderWriterFilter: used to add header information to the response.

(4) CsrfFilter: used to handle cross site request forgery.

(5) LogoutFilter: used to process logout.

(6) UsernamePasswordAuthenticationFilter: used to process form based login requests and obtain user names and passwords from forms. Requests from = = / login = = are processed by default. When obtaining the user name and password from the form, the default form name values are username and password. These two values can be modified by setting the values of the usernameParameter and passwordParameter parameters of this filter.

(7) Defaultloginpagegenerating filter: if the login page is not configured, this filter will be configured during system initialization and used to generate a login form page when login is required.

(8) Basic authentication filter: detects and processes http basic authentication.

(9) Requestcachewarefilter: the cache used to process requests.

(10) Securitycontextholderawarererequestfilter: mainly used to wrap the request object request. (11) AnonymousAuthenticationFilter: check whether an Authentication object exists in the SecurityContextHolder. If it does not exist, provide an anonymous Authentication for it.

(12) SessionManagementFilter: the filter that manages the session

(13) ExceptionTranslationFilter: handles AccessDeniedException and AuthenticationException exceptions.

(14) FilterSecurityInterceptor: it can be regarded as the exit of the filter chain.

(15) RememberMeAuthenticationFilter: when a user directly accesses resources without logging in, find out the user's information from the cookie. If Spring Security can recognize the remember me cookie provided by the user, the user will not have to fill in the user name and password, but directly log in to the system. This filter is not enabled by default.

5.2 basic process of spring security

Spring Security adopts a filter chain to realize authentication and authorization. Only when the current filter passes can it enter the next filter:

The green part is the authentication filter, which needs to be configured by ourselves. Multiple authentication filters can be configured. The authentication filter can use the authentication filter provided by Spring Security or customize the filter (for example, SMS authentication). The authentication filter should be configured in the configure(HttpSecurity http) method. If it is not configured, it will not take effect. The following three filters will be highlighted:

  • UsernamePasswordAuthenticationFilter: this filter will intercept the POST login form request submitted by the front end and authenticate the identity.

  • ExceptionTranslationFilter: this filter does not need to be configured. The requests submitted by the front end will be released directly, and the subsequent exceptions thrown (mainly thrown by the back end) will be caught and processed (for example, permission access restrictions).

  • FilterSecurityInterceptor filter: this filter is the last filter in the filter chain. It determines whether the current request has permission to access the corresponding resources according to the resource permission configuration. If access is restricted, relevant exceptions will be thrown and caught and processed by the ExceptionTranslationFilter filter.

5.3 spring security authentication process

The authentication process is processed in the UsernamePasswordAuthenticationFilter. The specific process is as follows:

5.3.1 UsernamePasswordAuthenticationFilter source code

If the front end submits a POST login form request, it will be intercepted by the filter and authenticated.

The doFilter() method of the filter is implemented in its abstract parent class AbstractAuthenticationProcessingFilter. View the relevant source code:

The second procedure above calls the attemptAuthentication() method of UsernamePasswordAuthenticationFilter. The source code is as follows:

The UsernamePasswordAuthenticationToken created in the above (3) process is the implementation class of the Authentication interface. This class has two constructors, one is used to encapsulate the unauthenticated user information requested by the front end, and the other is used to encapsulate the user information after successful Authentication:

The implementation class of Authentication interface is used to store user Authentication information. View the specific definition of the interface:

5.3.2 UsernamePasswordAuthenticationFilter source code diagram

5.3.3 ProviderManager source code

In the above process, the (5) process of the attemptAuthentication() method of the UsernamePasswordAuthenticationFilter filter passes the unauthenticated Authentication object into the authenticate() method of the ProviderManager class for Authentication.

Introduction to ProviderManager

ProviderManager is the implementation class of the AuthenticationManager interface, which is the core interface related to authentication and the entry of authentication. In actual development, we may have many different authentication methods, such as user name + password, email + password, mobile phone number + verification code, etc., and there is always only one entry for these authentication methods, that is, authentication manager.

In the common implementation class ProviderManager of this interface, a List will be maintained to store various Authentication methods. In fact, this is the application of Delegate mode. Each Authentication method corresponds to an AuthenticationProvider. The AuthenticationManager delegates the corresponding AuthenticationProvider for user Authentication according to different Authentication methods (judged according to the incoming Authentication type).

Underlying source code:

In the (6) process after the above authentication is successful, call the eraseCredentials() method defined by the CredentialsContainer interface to remove sensitive information. View the eraseCredentials() method implemented by UsernamePasswordAuthenticationToken, which is implemented in its parent class

5.3.4 authentication success / failure handling

The above process is the core part of the authentication process. Next, go back to the doFilter() method of the UsernamePasswordAuthenticationFilter filter to view the processing of authentication success / failure

Dependent delegation:

5.4 spring security permission access process

Illustration:

5.4.1 ExceptionTranslationFilter

This filter is used to handle exceptions and does not need our configuration. Requests submitted by the front end will be released directly, and subsequent exceptions thrown will be caught and processed (for example, permission access restrictions). The specific source code is as follows:

5.4.2 FilterSecurityInterceptor filter

FilterSecurityInterceptor is the last filter in the filter chain. It is the last filter in the filter chain. It determines whether the current request has permission to access the corresponding resources according to the resource permission configuration. If the access is restricted, relevant exceptions will be thrown, and the thrown exceptions will be caught and processed by the previous filter, ExceptionTranslationFilter. The specific source code is as follows:

It should be noted that the filter chain of Spring Security is configured before the dispatcher servlet, the core component of spring MVC, runs. In other words, the request passing through all the filters of Spring Security does not mean that the resources can be accessed normally. The request also needs to pass through the interceptor chain of spring MVC.

5.5 sharing authentication information between spring security requests

Generally, the user information after successful Authentication is shared among multiple requests through Session. How to bind the authenticated user information object Authentication with Session in Spring Security is analyzed in detail

  • When explaining the successful authentication () processing method earlier, there are the following codes:

  • View the SecurityContext interface and its implementation class SecurityContextImpl, which is actually the encapsulation of Authentication:

  • View the SecurityContextHolder class, which actually encapsulates ThreadLocal and stores SecurityContext objects

5.5.1 SecurityContextPersistenceFilter

As mentioned earlier, after the UsernamePasswordAuthenticationFilter filter is authenticated successfully, the authenticated user information object Authentication will be encapsulated in the SecurityContext and stored in the SecurityContextHolder in the Authentication successful processing method.

After that, the response will pass the SecurityContextPersistenceFilter, which is located at the front of all filters. The request will pass it first, and the response will return the last one to pass it. Therefore, the authenticated user information object Authentication and Session are bound in the filter.

When the successful Authentication response passes the SecurityContextPersistenceFilter, the SecurityContext encapsulating the Authentication of the authenticated user information object will be taken from the SecurityContextHolder and put into the Session. When the request comes again, the request first passes through the filter, which will judge whether there is a SecurityContext object in the current requested Session. If so, take out the object and put it into the SecurityContextHolder again. Then the thread where the request is located obtains the Authentication user information, and subsequent resource access does not need identity Authentication; When the response returns again, the filter also takes out the SecurityContext object from the SecurityContextHolder and puts it into the Session. The specific source code is as follows:

Illustration:

Tags: JavaEE

Posted on Wed, 24 Nov 2021 14:33:53 -0500 by horsetags