• Spring Security实现用户认证四:使用JWT与Redis实现无状态认证


    1 什么是无状态认证?

    在基本的通信流程中,我们一般采用Session去存储用户的认证状态。在Spring Security实现用户认证三中讲过,在拿到前端传输过来的用户名和密码之后,会有专门的过滤器UsernamePasswordAuthenticationFilter处理这部分的需求,并且对认证成功的用户生成Token且存储在Session中。在下次发起请求时,直接从Session中取出同用户名的token进行密码哈希的比较要认证用户。

    对于无状态认证,则我们的认证不依赖与服务器端存储的Session的状态。所以无状态认证需要我们每次从前端传输一个包含完整认证信息的Token到服务器端进行自定义的认证过程,这使得服务器无需存储和管理会话数据。常见的无状态认证方法包括 JSON Web Token (JWT)、API Key和 OAuth 2.0。

    2 什么是JWT?

    JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519),用于在各方之间传递信息。JWT可以进行数字签名,并且可以选择加密其内容。它定义了一种紧凑和自包含的方式, 可以通过URL、POST参数或HTTP头在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的,但是签名不能保证数据的机密性。JWT 可以使用 HMAC 算法、RSA 或 ECDSA 的公钥/私钥对进行签名。

    JWT最常见的用途是用户身份验证。一旦用户登录成功,服务器会生成一个JWT并返回给客户端。客户端将JWT存储在本地(如localStorage或cookie),并在每次请求时将其发送到服务器,服务器通过验证JWT来验证用户身份。

    2.1 需要注意的事项

    • 保密:不要在JWT中存储敏感信息,因为JWT是可以被解码的。
    • 过期处理:设置合理的过期时间,并且在需要时支持刷新令牌机制。
    • 使用HTTPS:确保在传输JWT时使用HTTPS,防止中间人攻击。

    2.2 JWT构成

    JWT由三个主要部分构成:Header(头部)、Payload(负载)和 Signature(签名)。每个部分都有其特定的作用和结构。

    1. Header(头部)
      头部通常包含两个部分:令牌类型和使用的签名算法。头部数据结构为一个JSON对象,然后进行Base64Url编码。
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
    1. Payload(负载)
      负载部分包含了声明(claims),即需要传输的数据。这些数据可以是关于用户的信息或者其他的元数据。声明可以分为三类:
    • Registered claims(注册声明):预定义的一些声明,如 iss(签发者),exp(过期时间),sub(主题),aud(受众)。
    • Public claims(公共声明):可以自定义的声明,但为了避免冲突,应使用URI命名。
    • Private claims(私有声明):由双方约定的声明,用于信息交换。
    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
    
    1. Signature(签名)
      签名部分用于验证消息的发送者和确保消息在传递过程中未被篡改。签名的生成过程如下:
    • 将编码后的Header和Payload用句点 (.) 连接起来:
    base64UrlEncode(header) + "." + base64UrlEncode(payload)
    
    • 使用头部中指定的签名算法,并结合一个密钥对上述连接的字符串进行签名。
    • 对于HMAC SHA256算法,签名过程如下:
    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      secret
    )
    

    最终,JWT的格式为:

    header.payload.signature
    

    3 Spring Security + JWT实现无状态认证

    登录认证流程
    登录认证流程如上。

    3.1 创建一个Spring Boot项目

    3.1.1 依赖

    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-apiartifactId>
        <version>0.12.3version>
    dependency>
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-implartifactId>
        <version>0.12.3version>
        <scope>runtimescope>
    dependency>
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwt-jacksonartifactId> 
        <version>0.12.3version>
        <scope>runtimescope>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    dependency>
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
    
    
    <dependency>
        <groupId>tk.mybatisgroupId>
        <artifactId>mapperartifactId>
    dependency>
    <dependency>
        <groupId>javax.persistencegroupId>
        <artifactId>persistence-apiartifactId>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>8.0.28version>
    dependency>
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druid-spring-boot-3-starterartifactId>
        <version>1.2.21version>
    dependency>
    
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
    dependency>
    

    3.1.2 Main

    package com.song.cloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import tk.mybatis.spring.annotation.MapperScan;
    
    @SpringBootApplication
    @MapperScan("com.song.cloud.mapper")
    public class ServiceSecurityJwt6501 {
        public static void main(String[] args) {
            SpringApplication.run(ServiceSecurityJwt6501.class, args);
        }
    }
    

    3.1.3 application.yml

    spring:
      application:
        name: service-security-jwt
    
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        username: root
        password: root
        # 注意修改数据库名字
        url: jdbc:mysql://localhost:3306/test? characterEncoding=utf8&useSSL=false&serverTimeZone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
      data:  # 配置redis
        redis:
          port: 6379
          host: 192.168.62.128
          password: 1234
    
    app:
      jwt-sign-secret: gOk33w29WESOMEx8vUQLb69AsGhlUb7UmrFwu3g2TOo=
      jwt-expiration-milliseconds: 604800000  # 七天过期
    
    server:
      port: 6501
    
    
    logging:
      level:
        web: debug
        org.springframework.security: debug
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.song.cloud.entities  # 注意修改成自己的包名
      configuration:
        map-underscore-to-camel-case: true
    

    3.2 Controller

    3.2.1 LoginController

    用来处理

    package com.song.cloud.controller;
    
    import com.song.cloud.entities.User;
    import com.song.cloud.service.UserService;
    import com.song.cloud.utils.JwtTokenProvider;
    import jakarta.annotation.Resource;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class LoginController {
    
        @Resource
        private JwtTokenProvider jwtTokenProvider;
    
        @Resource
        private UserService userService;
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        @PostMapping("/api/auth")
        public String auth(@RequestBody User user){
            System.out.println(user);
    
            UserDetails userDetails =  userService.loadUserDetail(user);
            PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
            boolean matches = delegatingPasswordEncoder.matches(user.getPasswordHash(), userDetails.getPassword());
            if(matches){
                UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
                token.setDetails(userDetails);
                System.out.println(token);
                //保存token到redis
                redisTemplate.opsForValue().set(userDetails.getUsername(), token);
                return jwtTokenProvider.generateToken(token);
            }
            return "fail";
        }
    }
    
    

    3.3.2 IndexController

    package com.song.cloud.controller;
    
    import jakarta.servlet.http.HttpSession;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.context.SecurityContextHolderStrategy;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    public class IndexController {
    
        @GetMapping("/")
        public Map index() {
    
            SecurityContext context = SecurityContextHolder.getContext();
            Authentication authentication = context.getAuthentication();
            Object principal = authentication.getPrincipal();
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); //脱敏处理
            Object credentials = authentication.getCredentials();
    
            HashMap<Object, Object> map = new HashMap<>();
    
            map.put("username", authentication.getName());
            map.put("authorities", authorities);
            map.put("credentials", credentials);
            map.put("details", authentication.getDetails());
            map.put("principal", principal);
            
            return map;
    
        }
    }
    

    3.3 User Entity

    package com.song.cloud.entities;
    
    import javax.persistence.Column;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    /**
     * 表名:t_users_test
    */
    @Table(name = "t_users_test")
    public class User {
        /**
         * id
         */
        @Id
        @GeneratedValue(generator = "JDBC")
        private Long id;
    
        /**
         * 用户名
         */
        private String username;
    
        /**
         * 密码hash
         */
        @Column(name = "password_hash")
        private String passwordHash;
    
        /**
         * 是否启用
         */
        private Boolean enable;
    
        /**
         * 获取id
         *
         * @return id - id
         */
        public Long getId() {
            return id;
        }
    
        /**
         * 设置id
         *
         * @param id id
         */
        public void setId(Long id) {
            this.id = id;
        }
    
        /**
         * 获取用户名
         *
         * @return username - 用户名
         */
        public String getUsername() {
            return username;
        }
    
        /**
         * 设置用户名
         *
         * @param username 用户名
         */
        public void setUsername(String username) {
            this.username = username;
        }
    
        /**
         * 获取密码hash
         *
         * @return passwordHash - 密码hash
         */
        public String getPasswordHash() {
            return passwordHash;
        }
    
        /**
         * 设置密码hash
         *
         * @param passwordHash 密码hash
         */
        public void setPasswordHash(String passwordHash) {
            this.passwordHash = passwordHash;
        }
    
        /**
         * 获取是否启用
         *
         * @return enable - 是否启用
         */
        public Boolean getEnable() {
            return enable;
        }
    
        /**
         * 设置是否启用
         *
         * @param enable 是否启用
         */
        public void setEnable(Boolean enable) {
            this.enable = enable;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "enable=" + enable +
                    ", id=" + id +
                    ", username='" + username + '\'' +
                    ", passwordHash='" + passwordHash + '\'' +
                    '}';
        }
    }
    

    3.4 Service

    UserService

    package com.song.cloud.service;
    
    import com.song.cloud.entities.User;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.List;
    
    public interface UserService {
        UserDetails loadUserDetail(User user);
    }
    

    UserServiceImpl

    package com.song.cloud.service.impl;
    
    import com.song.cloud.config.DBUserDetailManager;
    import com.song.cloud.entities.User;
    import com.song.cloud.mapper.UserMapper;
    import com.song.cloud.service.UserService;
    import jakarta.annotation.Resource;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Resource
        private DBUserDetailManager dbUserDetailsManager;
    
        @Resource
        private UserMapper userMapper;
    
        @Override
        public List<User> list() {
            return userMapper.selectAll();
        }
    
        @Override
        public UserDetails loadUserDetail(User user) {
            return dbUserDetailsManager.loadUserByUsername(user.getUsername());
        }
    
    }
    

    3.5 UserMapper.java

    package com.song.cloud.mapper;
    
    import com.song.cloud.entities.User;
    import tk.mybatis.mapper.common.Mapper;
    
    public interface UserMapper extends Mapper<User> {
    }
    

    3.6 UserMapper.xml

    注意将相应信息改成自己的。包路径、实体名、mapper类名等。

    <?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.song.cloud.mapper.UserMapper">
      <resultMap id="BaseResultMap" type="com.song.cloud.entities.User">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="id" jdbcType="BIGINT" property="id" />
        <result column="username" jdbcType="VARCHAR" property="username" />
        <result column="password_hash" jdbcType="VARCHAR" property="passwordHash" />
        <result column="enable" jdbcType="BIT" property="enable" />
      </resultMap>
    </mapper>
    

    3.7 JwtTokenProvider.java

    主要用来创建JwtToken的。

    package com.song.cloud.utils;
    
    import io.jsonwebtoken.*;
    import io.jsonwebtoken.io.Decoders;
    import io.jsonwebtoken.security.Keys;
    import io.jsonwebtoken.security.SignatureException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.Authentication;
    import org.springframework.stereotype.Component;
    
    import javax.crypto.SecretKey;
    import java.util.Date;
    
    @Component
    public class JwtTokenProvider {
    
        private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
    
        @Value("${app.jwt-sign-secret}")
        private String jwtSignSecret;
    
        @Value("${app.jwt-expiration-milliseconds}")
        private long jwtExpirationDate;
    
        // 生成 JWT token
        public String generateToken(Authentication authentication) {
            // 构建一个JWT,它的注册声明(Subject)设为 username
            String username = authentication.getName();
    
            Date currentDate = new Date();
    
            Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);
    
            // 使用适合HMAC-SHA-256算法的密钥对JWT进行签名。
            // 将其紧凑压缩为最终的字符串形式。签名后的JWT称为'token'。
            String token = Jwts.builder()
                    .issuer("backend")
                    .subject(username)
                    .issuedAt(new Date())
                    .expiration(expireDate)
                    .signWith(key())
                    .compact();
    
            return token;
        }
    
        private SecretKey key() {
            return Keys.hmacShaKeyFor(
                    //从base64解码得到byte[]
                    Decoders.BASE64.decode(jwtSignSecret)
            );
        }
    
        // 从 Jwt token 获取用户名
        public String getUsername(String token) {
            Claims claims = Jwts.parser()
                    .verifyWith(key())
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();
    
            return claims.getSubject();
        }
    
        // 验证 Jwt token
        public boolean validateToken(String token) {
            try {
                Jwts.parser()
                        .verifyWith(key())
                        .build()
                        .parse(token);
                return true;
            } catch (MalformedJwtException e) {
                logger.error("Invalid JWT token: {}", e.getMessage());
            } catch (ExpiredJwtException e) {
                logger.error("JWT token is expired: {}", e.getMessage());
            } catch (UnsupportedJwtException e) {
                logger.error("JWT token is unsupported: {}", e.getMessage());
            } catch (IllegalArgumentException e) {
                logger.error("JWT claims string is empty: {}", e.getMessage());
            } catch (SignatureException e){
                logger.error("JWT signature validation fails: {}", e.getMessage());
            }
            return false;
        }
    }
    

    3.8 Redis配置类

    package com.song.cloud.config;
    
    import com.fasterxml.jackson.databind.Module;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.*;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.jackson2.SecurityJackson2Modules;
    
    import java.util.*;
    
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(new JdkSerializationRedisSerializer());
            return template;
        }
    
    }
    

    3.9 MyRedisSecurityContextRepository.java

    实现了SecurityContextRepository,使用redis存储和管理SecurityContext

    package com.song.cloud.config;
    
    import com.song.cloud.utils.JwtTokenProvider;
    import jakarta.annotation.Resource;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.context.SecurityContextHolderStrategy;
    import org.springframework.security.web.context.HttpRequestResponseHolder;
    import org.springframework.security.web.context.SecurityContextRepository;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    @Component
    public class MyRedisSecurityContextRepository implements SecurityContextRepository {
    
        @Resource
        private JwtTokenProvider jwtTokenProvider;
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        private final SecurityContextHolderStrategy contextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
    
        @Override
        public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
            SecurityContext emptyContext = this.contextHolderStrategy.createEmptyContext();
            HttpServletRequest request = requestResponseHolder.getRequest();
            String token = request.getHeader("Authorization");
            if(!StringUtils.hasText(token)) return emptyContext;
            String username = jwtTokenProvider.getUsername(token);
            if(username == null) return emptyContext;
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken)redisTemplate.opsForValue().get(username);
            emptyContext.setAuthentication(usernamePasswordAuthenticationToken);
    
            return emptyContext;
        }
    
        @Override
        public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
            System.out.println("saveContext-----------------------------------------");
        }
    
        @Override
        public boolean containsContext(HttpServletRequest request) {
            System.out.println("containsContext------------------------------------");
            CharSequence authorization = request.getHeader("Authorization");
            return StringUtils.hasText(authorization);
        }
    }
    

    3.10 DBUserDetailManager.java

    用于实现数据库认证

    package com.song.cloud.config;
    
    import com.song.cloud.entities.User;
    import com.song.cloud.mapper.UserMapper;
    import jakarta.annotation.Resource;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsPasswordService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.provisioning.UserDetailsManager;
    import org.springframework.stereotype.Component;
    import tk.mybatis.mapper.entity.Example;
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Collection;
    
    @Component
    public class DBUserDetailManager implements UserDetailsManager, UserDetailsPasswordService, Serializable {
    
        @Resource
        private UserMapper userMapper;
    
        private Collection<GrantedAuthority> authorities;
    
        private DBUserDetailManager(ArrayList<GrantedAuthority> authorities){
            this.authorities = authorities;
        }
    
        @Override
        public UserDetails updatePassword(UserDetails user, String newPassword) {
            return null;
        }
    
        @Override
        public void createUser(UserDetails userDetails) {
        }
    
        @Override
        public void updateUser(UserDetails user) {
        }
    
        @Override
        public void deleteUser(String username) {
        }
    
        @Override
        public void changePassword(String oldPassword, String newPassword) {
    
        }
    
        @Override
        public boolean userExists(String username) {
            return false;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            System.out.println("进入:DBUserDetailManager ");
            //查询数据库根据用户名
            Example example = new Example(User.class);
            Example.Criteria criteria = example.createCriteria();
    
            criteria.andEqualTo("username", username);
    
            User user = userMapper.selectOneByExample(example);
    
            authorities.add(() -> "rule");
    
            if (user == null) {
                throw new UsernameNotFoundException(username);
            }
    
            return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPasswordHash(),
                    true,
                    true,
                    true,
                    true,
                    authorities
            );
    
        }
    }
    

    3.11 SpringSecurityConfig配置类

    配置Spring Security的配置类

    package com.song.cloud.config;
    
    import com.song.cloud.handler.JwtAuthenticationEntryPoint;
    import com.song.cloud.handler.MyAuthenticationEntryPoint;
    import com.song.cloud.handler.MyAuthenticationSuccessHandler;
    import jakarta.annotation.Resource;
    import lombok.AllArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.Customizer;
    import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
    import org.springframework.security.config.http.SessionCreationPolicy;
    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.SecurityFilterChain;
    
    @Configuration
    @EnableWebSecurity
    public class SpringSecurityConfig {
    
        @Resource
        private MyRedisSecurityContextRepository myRedisSecurityContextRepository;
    
        @Bean
        public static PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    
            http.authorizeHttpRequests((authorize) -> {
                authorize.requestMatchers("/api/auth/**").permitAll();
                authorize.anyRequest().authenticated();
            }).formLogin(Customizer.withDefaults());
    
            http.sessionManagement(session->{
               session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            });
    
            http.securityContext(context->{
                context.securityContextRepository(myRedisSecurityContextRepository);
            });
    
            http.exceptionHandling(exception -> {
               exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
            });
    
            http.csrf(AbstractHttpConfigurer::disable);
            http.cors(Customizer.withDefaults());
            return http.build();
        }
    
        @Bean
        public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
            return configuration.getAuthenticationManager();
        }
    }
    

    3.12 MyAuthenticationEntryPoint

    处理认证失败的请求。

    package com.song.cloud.handler;
    
    import cn.hutool.json.JSONUtil;
    import com.song.cloud.resp.ResultData;
    import com.song.cloud.resp.ReturnCodeEnum;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    
    import java.io.IOException;
    
    public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
            System.out.println("进入:MyAuthenticationEntryPoint");
            String localizedMessage = authException.getLocalizedMessage();
    
            ResultData<Object> fail = ResultData.fail(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), localizedMessage);
    
            String jsonStr = JSONUtil.toJsonStr(fail);
    
    
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().print(jsonStr);
        }
    }
    
    
  • 相关阅读:
    Spring @Component、@Repository和@Service注释之间的区别
    送给即将工作的自己
    js递归理解及使用案例
    Centos OpenVp*
    HALCON: 对象(object)从声明(declaration)到结束(finalization)
    Map接口和常用方法
    第一章:为什么要并行计算
    电气工程的标准是什么
    c++还原简单的vector
    NLP模型笔记2022-20:py2neo接口处理知识图谱neo4j实体
  • 原文地址:https://blog.csdn.net/weixin_45248370/article/details/139220648