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

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

  • 1 什么是无状态认证?
  • 2 什么是JWT?
    • 2.1 需要注意的事项
    • 2.2 JWT构成
  • 3 Spring Security + JWT实现无状态认证
    • 3.1 创建一个Spring Boot项目
      • 3.1.1 依赖
      • 3.1.2 Main
      • 3.1.3 application.yml
    • 3.2 Controller
      • 3.2.1 LoginController
      • 3.3.2 IndexController
    • 3.3 User Entity
    • 3.4 Service
      • UserService
      • UserServiceImpl
    • 3.5 UserMapper.java
    • 3.6 UserMapper.xml
    • 3.7 JwtTokenProvider.java
    • 3.8 Redis配置类
    • 3.9 MyRedisSecurityContextRepository.java
    • 3.10 DBUserDetailManager.java
    • 3.11 SpringSecurityConfig配置类
    • 3.12 MyAuthenticationEntryPoint

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.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.3</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- 或者jjwt-gson,如果你更喜欢Gson -->
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>

<!--        redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</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-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!--    数据库    -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
</dependency>
<dependency>
    <groupId>javax.persistence</groupId>
    <artifactId>persistence-api</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.21</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
</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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/702763.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CMS与AI的融合:构建万能表单小程序系统

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;MyCMS作为一款功能强大的内容管理系统&#xff0c;通过集成AI技术&#xff0c;进一步拓展了其应用范围和智能化水平。本文将探讨如何利用MyCMS结合AI技术&#xff0c;构建一个能够将用户提交的万能表单数据转化为智能提…

白话解读网络爬虫

网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网络蜘蛛、网络机器人或网络蠕虫&#xff0c;是一种自动化程序或脚本&#xff0c;被用来浏览互联网并收集信息。网络爬虫的主要功能是在互联网上自动地浏览网页、抓取内容并将其存储在本地或远程服务器上供后续处…

SpringBoot+MyBatis批量插入数据的三种方式

文章目录 1. 背景介绍2. 方案介绍2.1 第一种方案&#xff0c;用 for语句循环插入&#xff08;不推荐&#xff09;2.2 第二种方案&#xff0c;利用mybatis的foreach来实现循环插入&#xff08;不推荐&#xff09;2.3 第三种方案&#xff0c;使用sqlSessionFactory实现批量插入&a…

【文献阅读】一种多波束阵列重构导航抗干扰算法

引言 针对导航信号在近地表的信号十分微弱、抗干扰能力差的问题&#xff0c;文章提出了自适应波束形成技术。 自适应波束形成技术可以分为调零抗干扰算法和多波束抗干扰算法。 调零抗干扰算法主要应用功率倒置技术&#xff0c;充分利用导航信号功率低于环境噪声功率的特点&…

Generative AI原理本质、技术内核及工程实践之基于Vertex AI的大模型 (二)Generative AI on Vertex AI 概览

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

如何挑选靠谱的软件开发公司?

在数字化的大潮中&#xff0c;企业商家都明白一个道理&#xff1a;没有一艘强大的软件开发公司“战舰”&#xff0c;想在商海中乘风破浪可不容易。但问题是&#xff0c;市场上那么多软件开发公司&#xff0c;如何挑选出最靠谱的那一家呢&#xff1f;别急&#xff0c;这篇文章就…

进程和内存管理

内存的使用和剩余情况 当前cpu的负载情况 找进程的id 结束某个进程 检查内存&#xff1a; 方法一&#xff1a;/proc/meminfo注意&#xff1a;这是个伪文件&#xff0c;这个文件记录了内存的相关信息&#xff0c;不可以用vi打开&#xff0c;应该用cat查看方法二&#xff1a;fre…

数字员工将重塑工作与生产的未来格局?

数字员工&#xff0c;由AI、机器学习和自动化技术驱动&#xff0c;正逐渐取代或协助人类完成从基础到高端的任务&#xff0c;极大提升工作效率&#xff0c;并改变工作认知。它们不仅影响各行业&#xff0c;还重塑人与机器、社会、自然的关系。与二十世纪末的国企下岗变革相比&a…

SaaS企业营销:如何通过联盟计划实现销售增长?

联盟营销计划在国外saas行业非常盛行&#xff0c;国内如何借鉴国外的成功案例运用联盟计划实现销售增长呢&#xff1f;林叔今天以最近新发现的leadpages为例分享下经验。 Leadpages是一款用户友好的落地页制作工具&#xff0c;提供多种预设计模板、A/B测试和分析功能&#xff0…

实例详解C/C++中static与extern关键字的使用

目录 1、概述 2、编译C++代码时遇到的变量及函数重复定义的问题 3、用 extern 声明外部变量 4、extern与全局函数 5、为何在变量和函数前添加一个static关键字编译就没问题了呢? 6、静态局部变量 7、函数的声明与定义都放置到一个头文件中,不使用static,通过宏控制去…

安灯(andon)系统如何帮助工厂流水线实现精益生产

在当今竞争激烈的制造业领域&#xff0c;实现精益生产已成为众多工厂追求的目标。而安灯&#xff08;Andon&#xff09;系统在这一过程中发挥着至关重要的作用。 安灯&#xff08;Andon&#xff09;系统通过及时反馈和沟通机制&#xff0c;让生产过程中的问题能够迅速被察觉和解…

Si24R05—高度集成的低功耗 2.4G+125K SoC 芯片

Si24R05是一款高度集成的低功耗SoC芯片&#xff0c;具有低功耗、Low Pin Count、宽电压工作范围&#xff0c;集成了13/14/15/16位精度的ADC、LVD、UART、SPI、I2C、TIMER、WUP、IWDG、RTC、无线收发器、3D低频唤醒接收器等丰富的外设。内核采用RISC-V RV32IMAC&#xff08;2.6 …

VOP | Point Cloud

目录 Point Cloud Open —— 打开点云文件并搜索源位置周围的点 Point Cloud Find —— 返回最近点的点号列表 Point Cloud Find Radius —— 返回最近点的点号列表并考虑被搜索点的半径 Point Cloud Filter —— 过滤查询到的点以生成加权值 Point Cloud Farthest —— 查…

第二证券股市资讯:半导体,突发!

半导体又现突发&#xff01; 商场忽然传出&#xff0c;拜登政府正在考虑约束我国获取应用在人工智能&#xff08;AI) 芯片上的全栅级晶体管技能&#xff08;Gate-all-around, GAA) &#xff0c;但不过现在还不清楚美国官员何时会做出最终决议。从趋势来看&#xff0c;这意味着…

【Ardiuno】实验ESP32单片机自动配置Wifi功能(图文)

这里小飞鱼按照ESP32的示例代码&#xff0c;实验一下wifi的自动配置功能。所谓的自动配置&#xff0c;就是不用提前将wifi的名称和密码写到程序里&#xff0c;这样可以保证程序在烧录上传后&#xff0c;可以通过手机端的软件来进行配置&#xff0c;可以避免反复修改代码&#x…

【产品经理】ERP对接电商平台

电商ERP对接上游平台&#xff0c;会需要经历几个步骤环节&#xff0c;包括店铺设置等。 电商ERP对接上游电商平台&#xff0c;其主要设置为店铺设置、商品同步、库存同步&#xff0c;本次讲解下店铺设置应该如何进行设置&#xff0c;以及在设置过程中的可能出现的踩坑事项。 …

JAVA面试题:Redis分布式锁

Redis分布式锁 分布式锁使用的场景 集群情况下的定时任务,抢单,幂等性等场景 抢券场景 查询库存 -> 扣减库存 多个并发线程同时查询库存,出现超卖问题 添加互斥锁 所有线程执行操作之前必须尝试获取锁 保证一次只有一个线程能走查询库存->扣减库存的流程 Redis分…

物业管理的隐形杀手:纸质点检表,你还在用吗?

在日常的生活中&#xff0c;我们经常会看到小区物业保洁、客服人员在工作岗位忙忙碌碌&#xff0c;但忽略了默默为我们提供舒适环境的“隐形守护者”——物业设施设备。然而&#xff0c;一旦这些设备出现故障&#xff0c;我们的日常生活就会陷入混乱。那么&#xff0c;如何确保…

Codesys中根据时间生成随机数字

一、 说明 LTIME()函数返回LTIME 时间类型数据 这个函数产生自系统启动以来经过的时间&#xff0c;以纳秒为单位&#xff0c;以扫描周期1ms为例&#xff0c;这个函数每次获得的纳妙数是随机的&#xff0c;没有规律。 二、作用 例如用来生成0到100的随机数&#xff0c;可以用L…

Keepalived LVS群集

一、Keepalived案例分析 企业应用中&#xff0c;单台服务器承担应用存在单点故障的危险 单点故障一旦发生&#xff0c;企业服务将发生中断&#xff0c;造成极大的危害 二、Keepalived工具介绍 专为LVS和HA设计的一款健康检查工具 支持故障自动切换&#xff08;Failover&#…