2021 latest Spring Security knowledge combing

2021 latest Spring Security knowledge combing

1. Introduction to Spring Security Framework

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 about security, the two main areas are "authentication" and "authorization" (or access control). Generally speaking, the security of web applications includes user authentication (Authentication) and user authorization (Authorization). In part, these two points are also important core functions of Spring Security.

1. User authentication refers to verifying whether a user is a legal subject 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 layman's terms, it is whether the system thinks the user can log in.

2. User authorization refers to verifying whether a user has the authority to perform a certain operation. In a system, different users have different permissions. For example, for a file, some users can only read it, and some users can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions. In layman's terms, the system determines whether the user has the authority to do certain things.

The spring family bucket study notes have been sorted out. The learning materials of spring family members have been systematically sorted out. If necessary, you can click to receive it directly

2. Spring Security and shiro

1. Features of Spring Security

(1) Seamless integration with Spring.

(2) Comprehensive authority control.

(3) Designed specifically for Web development.

  • The old version cannot be used outside the Web environment.

  • The new version extracts the entire framework hierarchically and divides it into core modules and Web modules. alone

  • The introduction of core modules can be separated from the Web environment.

  • Heavyweight

2. Shiro features

A lightweight permission control framework under Apache.

(1) Lightweight

The philosophy that Shiro advocates is to make complicated things simple. Better performance for Internet applications that have higher requirements for performance.

(2) Versatility

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.

3. Summary of Spring Security and Shiro

Compared to Shiro, integrating Spring Security in SSM is a more troublesome operation. Therefore, although Spring Security is more powerful than Shiro, it is not used as much. (Shiro is not as much as Spring Security, but for most projects, Shiro It's enough). Since the introduction of Spring Boot, Spring Boot has provided an automated configuration solution for Spring Security, which allows you to use Spring Security with less configuration. Therefore, in general, the combination of common security management technology stacks is as follows:

(1) SSM + Shiro

(2) Spring Boot/Spring Cloud + Spring Security

The above is just a recommended combination, if purely technically speaking, no matter how the combination is, it will work.

3. Spring Security filter

1. Common filters in Spring Security

  1. FilterSecurityInterceptor: It is a method-level permission filter, which is basically located at the bottom of the filter chain.

  2. ExceptionTranslationFilter: is an exception filter, used to deal with exceptions thrown in the authentication and authorization process.

  3. UsernamePasswordAuthenticationFilter: intercept the POST request of/login and verify the user in the form

Name, password.

2. 15 kinds of filters

Spring Security uses a chain of responsibility design pattern, which has a long filter chain. Now explain the 15 filters of this filter chain:

(1) WebAsyncManagerIntegrationFilter: Integrate the Security context with the WebAsyncManager used to process asynchronous request mapping in Spring Web.

(2) SecurityContextPersistenceFilter: Load the security context information related to the request to the SecurityContextHolder before each request is processed, and then after the request is processed, store the information about this request in the SecurityContextHolder in a "repository", Then clear the information in the SecurityContextHolder. For example, this filter handles the maintenance of a user's security information in the Session.

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

(4) CsrfFilter: Used to deal with cross-site request forgery.

(5) LogoutFilter: used to process logout.

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

(7) DefaultLoginPageGeneratingFilter: If the login page is not configured, this filter will be configured when the system is initialized and used to generate a login form page when login is required.

(8) BasicAuthenticationFilter: detect and process http basic authentication.

(9) RequestCacheAwareFilter: The cache used to process requests.

(10) SecurityContextHolderAwareRequestFilter: mainly wraps the request object request.

(11) AnonymousAuthenticationFilter: Detect whether there is an Authentication object in the SecurityContextHolder, and if it does not exist, provide it with an anonymous Authentication.

(12) SessionManagementFilter: filter for managing session

(13) ExceptionTranslationFilter: handle AccessDeniedException and AuthenticationException exceptions.

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

(15) RememberMeAuthenticationFilter: When the user directly accesses the resource 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 need to fill in the user name and password, but log in directly. System, this filter is not enabled by default.

3. The basic process of Spring Security

Spring Security adopts a filter chain to implement authentication and authorization. Only the current filter passes before the next filter can be entered:

The green part is the authentication filter, which needs to be configured by ourselves, and multiple authentication filters can be configured. The authentication filter can use the authentication filter provided by Spring Security, or you can customize the filter (for example: SMS verification). The authentication filter should be configured in the configure(HttpSecurity http) method, and no configuration will not take effect.

The following will focus on the following three filters: UsernamePasswordAuthenticationFilter: This filter will intercept the POST login form request submitted by the front end and perform identity authentication. ExceptionTranslationFilter: This filter does not require us to configure. The request submitted by the front-end will be released directly, and subsequent exceptions thrown will be captured 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 resource according to the resource permission configuration. If the access is restricted, related exceptions will be thrown and captured and processed by the ExceptionTranslationFilter.

4. Spring Security certification process

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

4. PasswordEncoder interface

//Indicates that the parameters are parsed according to specific parsing rules String encode(CharSequence rawPassword); //means to verify whether the encoded password obtained from the storage matches the original password submitted after encoding. If the password matches If it matches, it returns true; if it does not match, it returns false. The first parameter indicates the password that needs to be parsed. the second The parameter represents the stored password. boolean matches(CharSequence rawPassword, String encodedPassword); //Indicates that if the parsed password can be parsed again and achieve a more secure result, return true, otherwise return false. It returns false by default. default boolean upgradeEncoding(String encodedPassword) { return false; } Copy code

BCryptPasswordEncoder is a password parser officially recommended by Spring Security, and we usually use this parser. BCryptPasswordEncoder is a concrete implementation of bcrypt's strong hashing method. It is a one-way encryption based on the Hash algorithm. The encryption strength can be controlled by strength, the default is 10.

5. Getting started with SpringBoot integration with Spring Security

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.guor</groupId> <artifactId>securityProject</artifactId> <version>0.0.1-SNAPSHOT</version> <name>securityProject</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--lombok is used to simplify the entity class --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Copy code

2. application.properties

server.port=8111 #spring.security.user.name=root #spring.security.user.password=root #mysql database connection spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=root Copy code

3. SecurityConfig

package com.guor.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("zs").password(password).roles("admin"); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } } Copy code
package com.guor.security.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import javax.sql.DataSource; @Configuration public class UserSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; //Inject the data source @Autowired private DataSource dataSource; //Configuration object @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); //jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userService(userService).passwordEncoder(password()); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //drop out http.logout().logoutUrl("/logout"). logoutSuccessUrl("/test/hello").permitAll(); //The configuration does not have permission to access the custom page http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin()//Customize your own login page .loginPage("/on.html")//Login page settings .loginProcessingUrl("/user/login")//Login access path .defaultSuccessUrl("/success.html").permitAll()//After successful login, jump path .failureUrl("/unauth.html") .and().authorizeRequests() .antMatchers("/","/test/hello","/user/login").permitAll()//Set which paths can be accessed directly without authentication //Currently logged in user, only with admins authority can access this path //1 hasAuthority method //.antMatchers("/test/index").hasAuthority("admins") //2 hasAnyAuthority method //.antMatchers("/test/index").hasAnyAuthority("admins,manager") //3 hasRole method ROLE_sale .antMatchers("/test/index").hasRole("sale") .anyRequest().authenticated() .and().rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60)//Set the valid duration in seconds .userDetailsService(userService); //.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); //.and().csrf().disable();//disable csrf protection } } Copy code

4. Startup 

package com.guor.security; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @SpringBootApplication @MapperScan("com.guor.security.mapper") @EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true) public class SecurityProjectApplication { public static void main(String[] args) { SpringApplication.run(SecurityProjectApplication.class, args); } } Copy code

5. User

package com.guor.security.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data public class User { private Integer id; private String username; private String password; } Copy code

6. UserService

package com.guor.security.service; import com.guor.security.entity.User; import com.guor.security.mapper.UsersMapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //Call the usersMapper method to query the database based on the username QueryWrapper<Users> wrapper = new QueryWrapper(); //where username=? wrapper.eq("username",username); User user = userMapper.selectOne(wrapper); //judgment if(user == null) {//The database does not have a user name, the authentication fails throw new UsernameNotFoundException("Username does not exist!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale"); //Return the users object from the query database, get the user name and password, and return return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()),auths); } } Copy code

7, UserMapper

package com.guor.security.mapper; import com.guor.security.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.springframework.stereotype.Repository; @Repository public interface UserMapper extends BaseMapper<User> { } Copy code

8. UserController

package com.guor.security.controller; import com.guor.security.entity.User; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/test") public class UserController { @GetMapping("hello") public String hello() { return "hello security"; } @GetMapping("index") public String index() { return "hello index"; } @GetMapping("update") //@Secured({"ROLE_sale","ROLE_manager"}) //@PreAuthorize("hasAnyAuthority('admins')") @PostAuthorize("hasAnyAuthority('admins')") public String update() { System.out.println("update......"); return "hello update"; } @GetMapping("getAll") @PostAuthorize("hasAnyAuthority('admins')") @PostFilter("filterObject.username =='admin1'") public List<Users> getAllUser(){ ArrayList<Users> list = new ArrayList<>(); list.add(new Users(11,"admin1","6666")); list.add(new Users(21,"admin2","888")); System.out.println(list); return list; } } Copy code

Sixth, the realization of microservice authentication and authorization

1. If it is based on Session, Spring-security will parse the sessionid in the cookie, find the session information stored in the server, and then determine 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 authority information managed by Spring-security.

If there are many modules in the system, each module needs to be authorized and authenticated, so we choose to perform authorization and authentication based on the token. The user successfully authenticates according to the user name and password, and then obtains a series of permission values of the current user role, and The name is key and the permission list is stored in the redis cache in the form of value. The token is returned according to the user name related information. The browser records the token in the cookie. Each 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 based on the user name, so that Spring-security can determine whether the current request has permission to access.

7. microservice code example

1. The parent project pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <modules> <module>common</module> <module>infrastructure</module> <module>service</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.atguigu</groupId> <artifactId>acl_parent</artifactId> <packaging>pom</packaging> <version>0.0.1-SNAPSHOT</version> <name>acl_parent</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <mybatis-plus.version>3.0.5</mybatis-plus.version> <velocity.version>2.0</velocity.version> <swagger.version>2.7.0</swagger.version> <jwt.version>0.7.0</jwt.version> <fastjson.version>1.2.28</fastjson.version> <gson.version>2.8.2</gson.version> <json.version>20170516</json.version> <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version> </properties> <dependencyManagement> <dependencies> <!--Spring Cloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--mybatis-plus persistence layer--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Velocity template engine, Mybatis Plus code generator requires --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>${velocity.version}</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson.version}</version> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <!--swagger ui--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>${json.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Copy code

2. Common module

common module pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>acl_parent</artifactId> <groupId>com.atguigu</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>common</artifactId> <packaging>pom</packaging> <modules> <module>service_base</module> <module>spring_security</module> </modules> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided </scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <scope>provided </scope> </dependency> <!--lombok is used to simplify entity classes: lombok plug-in needs to be installed --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided </scope> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <scope>provided </scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <scope>provided </scope> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- common-pool2 required by spring2.X to integrate redis--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency> </dependencies> </project> Copy code

3. Common module -> SpringSecurity submodule

(1) Core configuration class

The core configuration of Spring Security is to inherit the WebSecurityConfigurerAdapter and annotate the configuration of @EnableWebSecurity. This configuration specifies the processing method of username and password, request path, login and logout control, etc. and security-related configuration

package com.atguigu.security.config; import com.atguigu.security.filter.TokenAuthFilter; import com.atguigu.security.filter.TokenLoginFilter; import com.atguigu.security.security.DefaultPasswordEncoder; import com.atguigu.security.security.TokenLogoutHandler; import com.atguigu.security.security.TokenManager; import com.atguigu.security.security.UnauthEntryPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; @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(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder, TokenManager tokenManager, RedisTemplate redisTemplate) { this.userDetailsService = userDetailsService; this.defaultPasswordEncoder = defaultPasswordEncoder; this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; } /** * Configuration settings * @param http * @throws Exception */ //Set the exit address and token, redis operation address @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint(new UnauthEntryPoint())//No access .and().csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and().logout().logoutUrl("/admin/acl/index/logout")//Exit path .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and() .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)) .addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic(); } //Call userDetailsService and password processing @Override public 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/**"); } } Copy code

(2) Entity class

package com.atguigu.security.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(description = "User entity class") public class User implements Serializable { private 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; } Copy code

(3) Filter

package com.atguigu.security.filter; import com.atguigu.security.security.TokenManager; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; 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 { //Get the current authentication user authority information UsernamePasswordAuthenticationToken authRequest = getAuthentication(request); //Judging if there is permission information, put it in the permission context if(authRequest != null) { SecurityContextHolder.getContext().setAuthentication(authRequest); } chain.doFilter(request,response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { //Get token from header String token = request.getHeader("token"); if(token != null) { //Get username from token String username = tokenManager.getUserInfoFromToken(token); //Get 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; } } Copy code
package com.atguigu.security.filter; import com.atguigu.security.entity.SecurityUser; import com.atguigu.security.entity.User; import com.atguigu.security.security.TokenManager; import com.atguigu.utils.utils.R; import com.atguigu.utils.utils.ResponseUtil; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; 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 for submitting the form @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //Get form submission data try { User user = new ObjectMapper().readValue(request.getInputStream(), User.class); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(), new ArrayList<>())); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(); } } //2 Method of successful authentication call @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //Authentication is successful, get user information after successful authentication SecurityUser user = (SecurityUser)authResult.getPrincipal(); //Generate token based on username 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 The method called when authentication fails protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { ResponseUtil.out(response, R.error()); } } Copy code

(4) security

package com.atguigu.security.security; import com.atguigu.utils.utils.MD5; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component public class DefaultPasswordEncoder implements PasswordEncoder { public DefaultPasswordEncoder() { this(-1); } public DefaultPasswordEncoder(int strength) { } //Md5 encryption @Override public String encode(CharSequence charSequence) { return MD5.encrypt(charSequence.toString()); } //Perform password comparison @Override public boolean matches(CharSequence charSequence, String encodedPassword) { return encodedPassword.equals(MD5.encrypt(charSequence.toString())); } } Copy code
package com.atguigu.security.security; import com.atguigu.utils.utils.R; import com.atguigu.utils.utils.ResponseUtil; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //Exit the processor public class TokenLogoutHandler implements LogoutHandler { private TokenManager tokenManager; private RedisTemplate redisTemplate; 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 The token is not empty, remove the token, delete the token from redis String token = request.getHeader("token"); if(token != null) { //Remove tokenManager.removeToken(token); //Get username from token String username = tokenManager.getUserInfoFromToken(token); redisTemplate.delete(username); } ResponseUtil.out(response, R.ok()); } } Copy code
package com.atguigu.security.security; import io.jsonwebtoken.CompressionCodecs; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.util.Date; @Component public class TokenManager { //token valid time private long tokenEcpiration = 24*60*60*1000; //Encoding secret key private String tokenSignKey = "123456"; //1 Use jwt to generate token based on user name public String createToken(String username) { String token = Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)) .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact(); return token; } //2 Obtain 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 public void removeToken(String token) {} } Copy code
package com.atguigu.security.security; import com.atguigu.utils.utils.R; import com.atguigu.utils.utils.ResponseUtil; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class UnauthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { ResponseUtil.out(httpServletResponse, R.error()); } } Copy code

4. Common module -> service_base

(1) RedisConfig

package com.atguigu.utils; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @EnableCaching//Enable caching @Configuration//Configuration class public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key serialization method template.setKeySerializer(redisSerializer); //value serialization template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap serialization template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //Solve the problem of query cache conversion exception ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); //Configure serialization (solve the problem of garbled codes), with an expiration time of 600 seconds RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } } Copy code

(2) SwaggerConfig

package com.atguigu.utils; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration//Configuration class @EnableSwagger2//swagger annotation public class SwaggerConfig { @Bean public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() //.paths(Predicates.not(PathSelectors.regex("/admin/.*"))) .paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("Website-Course Center API Document") .description("This document describes the definition of the course center microservice interface") .version("1.0") .contact(new Contact("java", "http://atguigu.com", "1123@qq.com")) .build(); } } Copy code

(3) Tools

package com.atguigu.utils.utils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public final class MD5 { public static String encrypt(String strSrc) { try { char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','a','b','c','d','e','f' }; byte[] bytes = strSrc.getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(bytes); bytes = md.digest(); int j = bytes.length; char[] chars = new char[j * 2]; int k = 0; for (int i = 0; i <bytes.length; i++) { byte b = bytes[i]; chars[k++] = hexChars[b >>> 4 & 0xf]; chars[k++] = hexChars[b & 0xf]; } return new String(chars); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("MD5 encryption error!!!+" + e); } } public static void main(String[] args) { System.out.println(MD5.encrypt("111111")); } } Copy code
package com.atguigu.utils.utils; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class ResponseUtil { public static void out(HttpServletResponse response, R r) { ObjectMapper mapper = new ObjectMapper(); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); try { mapper.writeValue(response.getWriter(), r); } catch (IOException e) { e.printStackTrace(); } } } Copy code
package com.atguigu.utils.utils; import lombok.Data; import java.util.HashMap; import java.util.Map; //Classes that return results uniformly @Data public class R { private Boolean success; private Integer code; private String message; private Map<String, Object> data = new HashMap<String, Object>(); //Make the constructor private private R() {} //Successful static method public static R ok() { R r = new R(); r.setSuccess(true); r.setCode(20000); r.setMessage("Success"); return r; } //Failed static method public static R error() { R r = new R(); r.setSuccess(false); r.setCode(20001); r.setMessage("Failed"); return r; } public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key, Object value){ this.data.put(key, value); return this; } public R data(Map<String, Object> map){ this.setData(map); return this; } } Copy code

5. Gateway module

(1) pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>infrastructure</artifactId> <groupId>com.atguigu</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>api_gateway</artifactId> <dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>service_base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--gson--> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <!--Service call--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> </project> Copy code

(2) application.properties

# The port number server.port=8222 # Service Name spring.application.name=service-gateway # nacos service address spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 # Use service discovery route spring.cloud.gateway.discovery.locator.enabled=true # Configure routing rules spring.cloud.gateway.routes[0].id=service-acl # Set routing uri lb://registered service name spring.cloud.gateway.routes[0].uri=lb://service-acl # Specific path rules spring.cloud.gateway.routes[0].predicates= Path=/*/acl/** Copy code

(3) Resolve cross-domain

package com.atguigu.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.util.pattern.PathPatternParser; @Configuration public class CorsConfig { //Solve cross-domain @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**",config); return new CorsWebFilter(source); } } Copy code

(4) Startup

package com.atguigu.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } } Copy code

6, service module

Due to the word limit, the code here is not posted, if you need code reference, you can click on the spring security study notes to receive

(1) pom.xml

(2) application.properties

Take the menu authority management CRUD as an example:

(3) Permission

(4) PermissionController

(5) PermissionService 

(6) PermissionMapper

(7) PermissionMapper.xml

(8) PermissionHelper

(9) Startup