Video link: SpringSecurity framework tutorial -Spring Security+JWT implementation of project level front-end separation, authentication and authorization -B site's most accessible Spring Security Course_ Beep beep_ bilibili
brief introduction
Spring Security is a security management framework in the spring family. Compared with Shiro, another security framework, it provides richer functions and community resources.
Generally speaking, large and medium-sized projects use SpringSecurity as a security framework. Shiro has many small projects, because it is easier to get started than SpringSecurity.
General Web applications require authentication and authorization.
Authentication: verify whether the user who currently accesses the system is the user of the system, and confirm the specific user
Authorization: determine whether the current user has permission to perform an operation after authentication
Authentication and authorization are also the core functions of SpringSecurity as a security framework.
1. Get started quickly
1.1 preparation
We need to build a simple SpringBoot project first
① Set parent project add dependency
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
② Create startup class
@SpringBootApplication public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class,args); } }
③ Create Controller
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; } }
1.2 introducing SpringSecurity
Using SpringSecurity in the SpringBoot project, we only need to introduce dependencies to implement the entry case.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
After dependency is introduced, the interface before we try to access will automatically jump to a default login page of SpringSecurity. The default user name is user, and the password will be output on the console.
The interface can only be accessed after logging in.
2. Certification
2.1 login verification process
2.2 principle discussion
If you want to know how to implement your login process, you must first know the SpringSecurity process in the entry case.
2.2.1 SpringSecurity complete process
The principle of SpringSecurity is actually a filter chain, which contains filters that provide various functions. Here we can take a look at the filter in the introductory case.
Only core filters are shown in the figure, and other non core filters are not shown in the figure.
UsernamePasswordAuthenticationFilter: is responsible for processing the login request after we fill in the user name and password on the login page. It is mainly responsible for the certification of entry cases.
ExceptionTranslationFilter: handles any AccessDeniedException and AuthenticationException thrown in the filter chain.
FilterSecurityInterceptor: a filter responsible for permission verification.
We can check which filters are in the SpringSecurity filter chain in the current system and their order through Debug.
2.2.2 detailed explanation of certification process
Concept quick check:
Authentication interface: its implementation class represents the current user accessing the system and encapsulates user related information.
AuthenticationManager interface: defines the method of authenticating Authentication
UserDetailsService interface: the core interface for loading user specific data. It defines a method to query user information according to user name.
UserDetails interface: provides core user information. The user information obtained and processed by UserDetailsService according to the user name should be encapsulated into a UserDetails object and returned. This information is then encapsulated in the Authentication object.
2.3 problem solving
2.3.1 thinking analysis
Sign in
① Custom login interface
Call the method of ProviderManager to authenticate. If the authentication passes, generate jwt
Store user information in redis
② Customize UserDetailsService
Query the database in this implementation class
Verification:
① Define Jwt authentication filters
Get token
Parse the token to get the userid
Get user information from redis
Deposit into SecurityContextHolder
2.3.2 preparations
① Add dependency
<!--redis rely on--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--fastjson rely on--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency> <!--jwt rely on--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
② Add Redis related configuration
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import com.alibaba.fastjson.parser.ParserConfig; import org.springframework.util.Assert; import java.nio.charset.Charset; /** * Redis Using FastJson serialization * * @author sg */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class<T> clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } protected JavaType getJavaType(Class<?> clazz) { return TypeFactory.defaultInstance().constructType(clazz); } }
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); // Use StringRedisSerializer to serialize and deserialize the key value of redis template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash's key also adopts the serialization method of StringRedisSerializer template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
③ Response class
import com.fasterxml.jackson.annotation.JsonInclude; /** * @Author */ @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResult<T> { /** * Status code */ private Integer code; /** * Prompt information. If there is an error, the front end can get this field to prompt */ private String msg; /** * The query result data, */ private T data; public ResponseResult(Integer code, String msg) { this.code = code; this.msg = msg; } public ResponseResult(Integer code, T data) { this.code = code; this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public ResponseResult(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
④ Tools
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; /** * JWT Tools */ public class JwtUtil { //Valid for public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 * 1000 one hour //Set secret key plaintext public static final String JWT_KEY = "sangeng"; public static String getUUID(){ String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; } /** * Generate jtw * @param subject token Data to be stored in (json format) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// Set expiration time return builder.compact(); } /** * Generate jtw * @param subject token Data to be stored in (json format) * @param ttlMillis token Timeout * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// Set expiration time return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //Unique ID .setSubject(subject) // Topics can be JSON data .setIssuer("sg") // Issued by .setIssuedAt(now) // Time filed .signWith(signatureAlgorithm, secretKey) //Use HS256 symmetric encryption algorithm for signature, and the second parameter is the secret key .setExpiration(expDate); } /** * Create a token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// Set expiration time return builder.compact(); } public static void main(String[] args) throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); System.out.println(claims); } /** * Generate encrypted secret key secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * analysis * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
import java.util.*; import java.util.concurrent.TimeUnit; @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * Cache basic objects, such as Integer, String, entity class, etc * * @param key Cached key value * @param value Cached value */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * Cache basic objects, such as Integer, String, entity class, etc * * @param key Cached key value * @param value Cached value * @param timeout time * @param timeUnit Time granularity */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * Set effective time * * @param key Redis key * @param timeout Timeout * @return true=Set successfully; false= setting failed */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * Set effective time * * @param key Redis key * @param timeout Timeout * @param unit Time unit * @return true=Set successfully; false= setting failed */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * Get the cached base object. * * @param key Cache key value * @return Cache the data corresponding to the key value */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * Delete a single object * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * Delete collection object * * @param collection Multiple objects * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * Cache List data * * @param key Cached key value * @param dataList List data to be cached * @return Cached objects */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * Get cached list object * * @param key Cached key value * @return Cache the data corresponding to the key value */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * Cache Set * * @param key Cache key value * @param dataSet Cached data * @return Objects that cache data */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * Get cached set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * Cache Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * Get cached Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * Store data into Hash * * @param key Redis key * @param hKey Hash key * @param value value */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * Get data in Hash * * @param key Redis key * @param hKey Hash key * @return Hash Objects in */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * Delete data in Hash * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * Get data in multiple hashes * * @param key Redis key * @param hKeys Hash keyset * @return Hash Object collection */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * Get a list of cached basic objects * * @param pattern String prefix * @return Object list */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class WebUtils { /** * Render string to client * * @param response Render objects * @param string String to render * @return null */ public static String renderString(HttpServletResponse response, String string) { try { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } return null; } }
⑤ Entity class
import java.io.Serializable; import java.util.Date; /** * User entity class * * @author third night watch */ @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -40356785423868312L; /** * Primary key */ private Long id; /** * user name */ private String userName; /** * nickname */ private String nickName; /** * password */ private String password; /** * Account status (0 normal 1 disabled) */ private String status; /** * mailbox */ private String email; /** * cell-phone number */ private String phonenumber; /** * User gender (0 male, 1 female, 2 unknown) */ private String sex; /** * head portrait */ private String avatar; /** * User type (0 administrator, 1 ordinary user) */ private String userType; /** * User id of the Creator */ private Long createBy; /** * Creation time */ private Date createTime; /** * Updated by */ private Long updateBy; /** * Update time */ private Date updateTime; /** * Delete flag (0 means not deleted, 1 means deleted) */ private Integer delFlag; }
2.3.3 realization
2.3.3.1 database verification user
From the previous analysis, we can know that we can customize a UserDetailsService and let SpringSecurity use our UserDetailsService. Our own UserDetailsService can query the user name and password from the database.
preparation
Let's create a user table first, and the statement is as follows:
CREATE TABLE `sys_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT 'user name', `nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT 'nickname', `password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT 'password', `status` CHAR(1) DEFAULT '0' COMMENT 'Account status (0 normal 1 disabled)', `email` VARCHAR(64) DEFAULT NULL COMMENT 'mailbox', `phonenumber` VARCHAR(32) DEFAULT NULL COMMENT 'cell-phone number', `sex` CHAR(1) DEFAULT NULL COMMENT 'User gender (0 male, 1 female, 2 unknown)', `avatar` VARCHAR(128) DEFAULT NULL COMMENT 'head portrait', `user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT 'User type (0 administrator, 1 ordinary user)', `create_by` BIGINT(20) DEFAULT NULL COMMENT 'User of the Creator id', `create_time` DATETIME DEFAULT NULL COMMENT 'Creation time', `update_by` BIGINT(20) DEFAULT NULL COMMENT 'Updated by', `update_time` DATETIME DEFAULT NULL COMMENT 'Update time', `del_flag` INT(11) DEFAULT '0' COMMENT 'Delete flag (0 means not deleted, 1 means deleted)', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='User table'
Introduce the dependency of MybatisPuls and mysql driver
<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> </dependency>
Configure database information
spring: datasource: url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver
Define Mapper interface
public interface UserMapper extends BaseMapper<User> { }
Modify User entity class
Class name plus@TableName(value = "sys_user") ,id Field plus @TableId
Configure Mapper scan
@SpringBootApplication @MapperScan("com.sangeng.mapper") public class SimpleSecurityApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SimpleSecurityApplication.class); System.out.println(run); } }
Add junit dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
Test whether MP can be used normally
/** * @Author third night watch @SpringBootTest public class MapperTest { @Autowired private UserMapper userMapper; @Test public void testUserMapper(){ List<User> users = userMapper.selectList(null); System.out.println(users); } }
Core code implementation
Create a class to implement the UserDetailsService interface and override its methods. Query user information from the database by user name
/** * @Author */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //Query user information according to user name LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUserName,username); User user = userMapper.selectOne(wrapper); //If the data cannot be queried, an exception is thrown to give a prompt if(Objects.isNull(user)){ throw new RuntimeException("Wrong user name or password"); } //TODO is added to LoginUser according to user query permission information //Encapsulated as UserDetails object and returned return new LoginUser(user); } }
Because the return value of the UserDetailsService method is of UserDetails type, it is necessary to define a class to implement the interface and encapsulate the user information in it.
/** * @Author third night watch */ @Data @NoArgsConstructor @AllArgsConstructor public class LoginUser implements UserDetails { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.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; } }
Note: if you want to test, you need to write user data into the user table, and if you want the user's password to be stored in clear text, you need to add {noop} before the password. for example
In this way, you can log in with sg as your user name and 1234 as your password.
2.3.3.2 password encrypted storage
In the actual project, we will not store the password plaintext in the database.
The default PasswordEncoder requires that the password format in the database be: {id}password. It will judge the encryption method of the password according to the ID. But we usually don't use this method. Therefore, PasswordEncoder needs to be replaced.
We usually use the BCryptPasswordEncoder provided by SpringSecurity.
We only need to inject the BCryptPasswordEncoder object into the Spring container, and SpringSecurity will use the PasswordEncoder for password verification.
We can define a SpringSecurity configuration class. SpringSecurity requires this configuration class to inherit WebSecurityConfigurerAdapter.
/** * @Author third night watch */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
2.3.3.3 login interface
Next, we need to customize the login interface, and then let SpringSecurity release this interface, so that users can access this interface without logging in.
In the interface, we use the authenticate method of AuthenticationManager to authenticate users, so we need to configure the AuthenticationManager into the container in SecurityConfig.
If the authentication is successful, a jwt should be generated and returned in the response. And in order to identify the specific user through jwt when the user requests next, we need to store the user information in redis, and the user id can be used as the key.
@RestController public class LoginController { @Autowired private LoginServcie loginServcie; @PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ return loginServcie.login(user); } }
/** * @Author third night watch */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http //Turn off csrf .csrf().disable() //Do not get SecurityContext through Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Allow anonymous access to login interface .antMatchers("/user/login").anonymous() // All requests except the above require authentication .anyRequest().authenticated(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
@Service public class LoginServiceImpl implements LoginServcie { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); if(Objects.isNull(authenticate)){ throw new RuntimeException("Wrong user name or password"); } //Generate token using userid LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userId = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userId); //authenticate into redis redisCache.setCacheObject("login:"+userId,loginUser); //Respond the token to the front end HashMap<String,String> map = new HashMap<>(); map.put("token",jwt); return new ResponseResult(200,"Login successful",map); } }
2.3.3.4 certified filters
We need to customize a filter, which will get the token in the request header, parse the token and get the userid in it.
Use userid to get the corresponding LoginUser object in redis.
Then the Authentication object is encapsulated and stored in the SecurityContextHolder
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //Get token String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { //Release filterChain.doFilter(request, response); return; } //Parse token String userid; try { Claims claims = JwtUtil.parseJWT(token); userid = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token illegal"); } //Get user information from redis String redisKey = "login:" + userid; LoginUser loginUser = redisCache.getCacheObject(redisKey); if(Objects.isNull(loginUser)){ throw new RuntimeException("User is not logged in"); } //Deposit into SecurityContextHolder //TODO obtains permission information and encapsulates it into Authentication UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); //Release filterChain.doFilter(request, response); } }
/** * @Author third night watch */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Override protected void configure(HttpSecurity http) throws Exception { http //Turn off csrf .csrf().disable() //Do not get SecurityContext through Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Allow anonymous access to login interface .antMatchers("/user/login").anonymous() // All requests except the above require authentication .anyRequest().authenticated(); //Add the token verification filter to the filter chain http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
2.3.3.5 exit login
We only need to define a login interface, obtain the authentication information in SecurityContextHolder, and delete the corresponding data in redis.
/** * @Author third night watch */ @Service public class LoginServiceImpl implements LoginServcie { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); if(Objects.isNull(authenticate)){ throw new RuntimeException("Wrong user name or password"); } //Generate token using userid LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userId = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(userId); //authenticate into redis redisCache.setCacheObject("login:"+userId,loginUser); //Respond the token to the front end HashMap<String,String> map = new HashMap<>(); map.put("token",jwt); return new ResponseResult(200,"Login successful",map); } @Override public ResponseResult logout() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Long userid = loginUser.getUser().getId(); redisCache.deleteObject("login:"+userid); return new ResponseResult(200,"Exit successful"); } }
3. Authorization
3.0 role of permission system
For example, in a school library management system, if an ordinary student logs in, he can see the functions related to borrowing and returning books. It is impossible for him to see and use the functions of adding book information and deleting book information. However, if a librarian logs in with his account, he should be able to see and use the functions of adding book information and deleting book information.
To sum up, different users can use different functions. This is the effect of the permission system.
We can't just rely on the front end to judge the user's permissions to choose which menus and buttons to display. Because if it's just like this, if someone knows the interface address of the corresponding function, they can directly send requests to realize relevant function operations without going through the front end.
Therefore, we also need to judge the user permission in the background to judge whether the current user has the corresponding permission. We must have the required permission to carry out the corresponding operation.
3.1 basic authorization process
In SpringSecurity, the default FilterSecurityInterceptor will be used for permission verification. In the FilterSecurityInterceptor, the Authentication will be obtained from the SecurityContextHolder, and then the permission information will be obtained. Whether the current user has the permissions required to access the current resource.
Therefore, in the project, we only need to save the permission information of the currently logged in user into Authentication.
Then set the permissions required by our resources.
3.2 authorization realization
3.2.1 restrict the permissions required to access resources
SpringSecurity provides us with an annotation based permission control scheme, which is also the main method used in our project. We can use annotations to specify the permissions required to access the corresponding resources.
But to use it, we need to turn on the relevant configuration first.
@EnableGlobalMethodSecurity(prePostEnabled = true)
Then you can use the corresponding annotation@ PreAuthorize
@RestController public class HelloController { @RequestMapping("/hello") @PreAuthorize("hasAuthority('test')") public String hello(){ return "hello"; } }
3.2.2 package permission information
When we wrote UserDetailsServiceImpl earlier, we said that after querying the user, we also need to obtain the corresponding permission information, which is encapsulated in UserDetails and returned.
First, we directly write the permission information into UserDetails for testing.
We previously defined the implementation class LoginUser of UserDetails, and we need to modify it to encapsulate permission information.
package com.sangeng.domain; import com.alibaba.fastjson.annotation.JSONField; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; /** * @Author */ @Data @NoArgsConstructor public class LoginUser implements UserDetails { private User user; //Store permission information private List<String> permissions; public LoginUser(User user,List<String> permissions) { this.user = user; this.permissions = permissions; } //A collection that stores the permission information required by SpringSecurity @JSONField(serialize = false) private List<GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if(authorities!=null){ return authorities; } //Convert the permission information of string type in permissions into GrantedAuthority object and store it in authorities authorities = permissions.stream(). map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.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; } }
After the LoginUser is modified, we can encapsulate the permission information into LoginUser in UserDetailsServiceImpl. We write the permission to test, and then we will query the permission information from the database.
package com.sangeng.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.sangeng.domain.LoginUser; import com.sangeng.domain.User; import com.sangeng.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * @Author */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUserName,username); User user = userMapper.selectOne(wrapper); if(Objects.isNull(user)){ throw new RuntimeException("Wrong user name or password"); } //TODO is added to LoginUser according to user query permission information List<String> list = new ArrayList<>(Arrays.asList("test")); return new LoginUser(user,list); } }
3.2.3 query permission information from database
3.2.3.1 RBAC permission model
RBAC permission model (Role-Based Access Control), that is, role-based permission control. This is currently the most commonly used and relatively easy-to-use general permission model by developers.
3.2.3.2 preparation
CREATE DATABASE /*!32312 IF NOT EXISTS*/`sg_security` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE `sg_security`; /*Table structure for table `sys_menu` */ DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT 'menu name', `path` varchar(200) DEFAULT NULL COMMENT 'Routing address', `component` varchar(255) DEFAULT NULL COMMENT 'Component path', `visible` char(1) DEFAULT '0' COMMENT 'Menu status (0 show 1 hide)', `status` char(1) DEFAULT '0' COMMENT 'Menu status (0 normal 1 disabled)', `perms` varchar(100) DEFAULT NULL COMMENT 'Permission ID', `icon` varchar(100) DEFAULT '#'COMMENT' menu icon ', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `del_flag` int(11) DEFAULT '0' COMMENT 'Delete (0 not deleted, 1 deleted)', `remark` varchar(500) DEFAULT NULL COMMENT 'remarks', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='Menu table'; /*Table structure for table `sys_role` */ DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL, `role_key` varchar(100) DEFAULT NULL COMMENT 'Role permission string', `status` char(1) DEFAULT '0' COMMENT 'Role status (0 normal 1 disabled)', `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag', `create_by` bigint(200) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(200) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `remark` varchar(500) DEFAULT NULL COMMENT 'remarks', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='Role table'; /*Table structure for table `sys_role_menu` */ DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT 'role ID', `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT 'menu id', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; /*Table structure for table `sys_user` */ DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT 'user name', `nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT 'nickname', `password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT 'password', `status` char(1) DEFAULT '0' COMMENT 'Account status (0 normal 1 disabled)', `email` varchar(64) DEFAULT NULL COMMENT 'mailbox', `phonenumber` varchar(32) DEFAULT NULL COMMENT 'cell-phone number', `sex` char(1) DEFAULT NULL COMMENT 'User gender (0 male, 1 female, 2 unknown)', `avatar` varchar(128) DEFAULT NULL COMMENT 'head portrait', `user_type` char(1) NOT NULL DEFAULT '1' COMMENT 'User type (0 administrator, 1 ordinary user)', `create_by` bigint(20) DEFAULT NULL COMMENT 'User of the Creator id', `create_time` datetime DEFAULT NULL COMMENT 'Creation time', `update_by` bigint(20) DEFAULT NULL COMMENT 'Updated by', `update_time` datetime DEFAULT NULL COMMENT 'Update time', `del_flag` int(11) DEFAULT '0' COMMENT 'Delete flag (0 means not deleted, 1 means deleted)', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='User table'; /*Table structure for table `sys_user_role` */ DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT 'user id', `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT 'role id', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SELECT DISTINCT m.`perms` FROM sys_user_role ur LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id` LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id` LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id` WHERE user_id = 2 AND r.`status` = 0 AND m.`status` = 0
package com.sangeng.domain; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * Menu entity class * * @author makejava * @since 2021-11-24 15:30:08 */ @TableName(value="sys_menu") @Data @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Menu implements Serializable { private static final long serialVersionUID = -54979041104113736L; @TableId private Long id; /** * menu name */ private String menuName; /** * Routing address */ private String path; /** * Component path */ private String component; /** * Menu status (0 show 1 hide) */ private String visible; /** * Menu status (0 normal 1 disabled) */ private String status; /** * Permission ID */ private String perms; /** * Menu icon */ private String icon; private Long createBy; private Date createTime; private Long updateBy; private Date updateTime; /** * Delete (0 not deleted, 1 deleted) */ private Integer delFlag; /** * remarks */ private String remark; }
3.2.3.3 code implementation
We only need to query the corresponding permission information according to the user id.
So we can first define a mapper, which provides a method to query permission information according to userid.
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.sangeng.domain.Menu; import java.util.List; /** * @Author Third watch station B: https://space.bilibili.com/663528522 */ public interface MenuMapper extends BaseMapper<Menu> { List<String> selectPermsByUserId(Long id); }
Especially for custom methods, you need to create corresponding mapper files and define corresponding sql statements
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.sangeng.mapper.MenuMapper"> <select id="selectPermsByUserId" resultType="java.lang.String"> SELECT DISTINCT m.`perms` FROM sys_user_role ur LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id` LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id` LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id` WHERE user_id = #{userid} AND r.`status` = 0 AND m.`status` = 0 </select> </mapper>
Configure the location of mapperXML file in application.yml
spring: datasource: url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml
Then we can call the mapper's method in UserDetailsServiceImpl to query the permission information and package it into the LoginUser object.
/** * @Author */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getUserName,username); User user = userMapper.selectOne(wrapper); if(Objects.isNull(user)){ throw new RuntimeException("Wrong user name or password"); } List<String> permissionKeyList = menuMapper.selectPermsByUserId(user.getId()); // //Test writing method // List<String> list = new ArrayList<>(Arrays.asList("test")); return new LoginUser(user,permissionKeyList); } }
4. Custom failure handling
We also hope to return json with the same structure as our interface in case of authentication failure or authorization failure, so that the front end can handle the response uniformly. To realize this function, we need to know the exception handling mechanism of SpringSecurity.
In SpringSecurity, if an exception occurs during the authentication or authorization process, it will be caught by the ExceptionTranslationFilter. In the ExceptionTranslationFilter, you will judge whether the exception occurs due to authentication failure or authorization failure.
If the exception occurs in the authentication process, it will be encapsulated as AuthenticationException, and then the method of AuthenticationEntryPoint object will be called to handle the exception.
If the exception occurs in the authorization process, it will be encapsulated as AccessDeniedException, and then call the method of AccessDeniedHandler object to handle the exception.
So if we need to customize exception handling, we just need to customize AuthenticationEntryPoint and AccessDeniedHandler and configure them to SpringSecurity.
① Custom implementation class
@Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "Insufficient permissions"); String json = JSON.toJSONString(result); WebUtils.renderString(response,json); } }
/** * @Author */ @Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "Authentication failed, please login again"); String json = JSON.toJSONString(result); WebUtils.renderString(response,json); } }
② Configure to SpringSecurity
Inject the corresponding processor first
@Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired private AccessDeniedHandler accessDeniedHandler;
Then we can use the method of HttpSecurity object to configure.
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint). accessDeniedHandler(accessDeniedHandler);
5. Cross domain
For security reasons, browsers must follow the same origin policy when using the XMLHttpRequest object to initiate HTTP requests, otherwise it is a cross domain HTTP request, which is prohibited by default. The same source strategy requires the same source to communicate normally, that is, the protocol, domain name and port number are completely consistent.
The front-end and back-end projects are separated, and the front-end projects and back-end projects are generally not homologous, so there must be cross domain requests.
So we need to deal with it so that the front end can make cross domain requests.
① First configure SpringBoot and run cross domain requests
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // Set paths that allow cross domain registry.addMapping("/**") // Set the domain name that allows cross domain requests .allowedOriginPatterns("*") // Allow cookie s .allowCredentials(true) // Set the allowed request mode .allowedMethods("GET", "POST", "DELETE", "PUT") // Set allowed header properties .allowedHeaders("*") // Cross domain allowed time .maxAge(3600); } }
② Enable cross domain access of SpringSecurity
Since our resources will be protected by SpringSecurity, we need to let SpringSecurity run cross domain access if we want cross domain access.
@Override protected void configure(HttpSecurity http) throws Exception { http //Turn off csrf .csrf().disable() //Do not get SecurityContext through Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Allow anonymous access to login interface .antMatchers("/user/login").anonymous() // All requests except the above require authentication .anyRequest().authenticated(); //Add filter http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //Configure exception handler http.exceptionHandling() //Configure authentication failure processor .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); //Allow cross domain http.cors(); }
6. Minor problems left
Other permission verification methods
We used the @PreAuthorize annotation before, and then used the hasAuthority method for verification. SpringSecurity also provides us with other methods, such as hasAnyAuthority, hasRole, hasAnyRole, etc.
Here we are not in a hurry to introduce these methods. We should first understand the principle of hasAuthority, and then learn other methods, which will be easier for you to understand, rather than memorizing the differences. And we can also choose to define the verification method to implement our own verification logic.
The hasAuthority method actually implements the hasAuthority of the SecurityExpressionRoot. As long as you debug the breakpoint, you can know its internal verification principle.
Internally, it calls the getAuthorities method of authentication to obtain the user's permission list. Then judge that the method parameter data we saved is in the permission list.
The hasAnyAuthority method can pass in multiple permissions, and only the user with any one of them can access the corresponding resources.
@PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')") public String hello(){ return "hello"; }
hasRole requires a corresponding ROLE to access, but it will splice the parameters we passed in * * ROLE_** Compare later. Therefore, in this case, the corresponding permission of the user should also have * * ROLE_** This prefix is OK.
@PreAuthorize("hasRole('system:dept:list')") public String hello(){ return "hello"; }
hasAnyRole can be accessed by any ROLE. It will also splice the parameters we passed in * * ROLE_** Compare later. Therefore, in this case, the corresponding permission of the user should also have * * ROLE_** This prefix is OK.
@PreAuthorize("hasAnyRole('admin','system:dept:list')") public String hello(){ return "hello"; }
Custom permission verification method
We can also define our own permission verification method and use our method in the @PreAuthorize annotation.
@Component("ex") public class SGExpressionRoot { public boolean hasAuthority(String authority){ //Get the permissions of the current user Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); List<String> permissions = loginUser.getPermissions(); //Determine whether authority exists in the user permission set return permissions.contains(authority); } }
Using @ex in a SPEL expression is equivalent to getting an object whose name is not ex in the bean in the container. Then call the hasAuthority method of this object
@RequestMapping("/hello") @PreAuthorize("@ex.hasAuthority('system:dept:list')") public String hello(){ return "hello"; }
Configuration based permission control
We can also use the configuration method to control the permission of resources in the configuration class.
@Override protected void configure(HttpSecurity http) throws Exception { http //Turn off csrf .csrf().disable() //Do not get SecurityContext through Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Allow anonymous access to login interface .antMatchers("/user/login").anonymous() .antMatchers("/testCors").hasAuthority("system:dept:list222") // All requests except the above require authentication .anyRequest().authenticated(); //Add filter http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //Configure exception handler http.exceptionHandling() //Configure authentication failure processor .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); //Allow cross domain http.cors(); }
CSRF
CSRF refers to cross site request forgery, which is one of the common attacks on the web.
CSRF attack and defense (well written)_ Catch the thief first catch the king's blog -CSDN blog
SpringSecurity can prevent CSRF attacks through csrf_token. The backend will generate a csrf_token. When the front end initiates a request, it needs to carry this csrf_token, the back-end will have a filter for verification. If it is not carried or forged, access is not allowed.
We can find that CSRF attacks rely on the authentication information carried in cookies. But in the project of front end and back end separation, our authentication information is actually a token, which is not stored in a cookie, and the front-end code needs to set the token into the request header, so there is no need to worry about CSRF attacks.
Authentication successful processor
In fact, when UsernamePasswordAuthenticationFilter performs login authentication, if the login is successful, it will call the AuthenticationSuccessHandler method to process after successful authentication. AuthenticationSuccessHandler is the login success handler.
We can also customize the successful processor to deal with it after success.
@Component public class SGSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("Certification successful"); } }
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler successHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin().successHandler(successHandler); http.authorizeRequests().anyRequest().authenticated(); } }
Authentication failure processor
In fact, when UsernamePasswordAuthenticationFilter performs login authentication, if the authentication fails, it will call the AuthenticationFailureHandler method to handle the authentication failure. AuthenticationFailureHandler is the login failure handler.
We can also customize the failure processor to handle the failure.
@Component public class SGFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("Authentication failed"); } }
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler successHandler; @Autowired private AuthenticationFailureHandler failureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // Configure authentication successful processor .successHandler(successHandler) // Configure authentication failure processor .failureHandler(failureHandler); http.authorizeRequests().anyRequest().authenticated(); } }
Logout successful processor
@Component public class SGLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("Logout successful"); } }
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler successHandler; @Autowired private AuthenticationFailureHandler failureHandler; @Autowired private LogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // Configure authentication successful processor .successHandler(successHandler) // Configure authentication failure processor .failureHandler(failureHandler); http.logout() //Configuration logoff successful processor .logoutSuccessHandler(logoutSuccessHandler); http.authorizeRequests().anyRequest().authenticated(); } }@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationSuccessHandler successHandler; @Autowired private AuthenticationFailureHandler failureHandler; @Autowired private LogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // Configure authentication successful processor .successHandler(successHandler) // Configure authentication failure processor .failureHandler(failureHandler); http.logout() //Configuration logoff successful processor .logoutSuccessHandler(logoutSuccessHandler); http.authorizeRequests().anyRequest().authenticated(); } }