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

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

  • [1 什么是无状态认证?](#1 什么是无状态认证?)
  • [2 什么是JWT?](#2 什么是JWT?)
    • [2.1 需要注意的事项](#2.1 需要注意的事项)
    • [2.2 JWT构成](#2.2 JWT构成)
  • [3 Spring Security + JWT实现无状态认证](#3 Spring Security + JWT实现无状态认证)
    • [3.1 创建一个Spring Boot项目](#3.1 创建一个Spring Boot项目)
      • [3.1.1 依赖](#3.1.1 依赖)
      • [3.1.2 Main](#3.1.2 Main)
      • [3.1.3 application.yml](#3.1.3 application.yml)
    • [3.2 Controller](#3.2 Controller)
      • [3.2.1 LoginController](#3.2.1 LoginController)
      • [3.3.2 IndexController](#3.3.2 IndexController)
    • [3.3 User Entity](#3.3 User Entity)
    • [3.4 Service](#3.4 Service)
    • [3.5 UserMapper.java](#3.5 UserMapper.java)
    • [3.6 UserMapper.xml](#3.6 UserMapper.xml)
    • [3.7 JwtTokenProvider.java](#3.7 JwtTokenProvider.java)
    • [3.8 Redis配置类](#3.8 Redis配置类)
    • [3.9 MyRedisSecurityContextRepository.java](#3.9 MyRedisSecurityContextRepository.java)
    • [3.10 DBUserDetailManager.java](#3.10 DBUserDetailManager.java)
    • [3.11 SpringSecurityConfig配置类](#3.11 SpringSecurityConfig配置类)
    • [3.12 MyAuthenticationEntryPoint](#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编码。
json 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}
  1. Payload(负载)
    负载部分包含了声明(claims),即需要传输的数据。这些数据可以是关于用户的信息或者其他的元数据。声明可以分为三类:
  • Registered claims(注册声明):预定义的一些声明,如 iss(签发者),exp(过期时间),sub(主题),aud(受众)。
  • Public claims(公共声明):可以自定义的声明,但为了避免冲突,应使用URI命名。
  • Private claims(私有声明):由双方约定的声明,用于信息交换。
json 复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
  1. Signature(签名)
    签名部分用于验证消息的发送者和确保消息在传递过程中未被篡改。签名的生成过程如下:
  • 将编码后的Header和Payload用句点 (.) 连接起来:
json 复制代码
base64UrlEncode(header) + "." + base64UrlEncode(payload)
  • 使用头部中指定的签名算法,并结合一个密钥对上述连接的字符串进行签名。
  • 对于HMAC SHA256算法,签名过程如下:
json 复制代码
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

最终,JWT的格式为:

json 复制代码
header.payload.signature

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

登录认证流程如上。

3.1 创建一个Spring Boot项目

3.1.1 依赖

xml 复制代码
<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

java 复制代码
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

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

用来处理

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

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类名等。

java 复制代码
<?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的。

java 复制代码
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配置类

java 复制代码
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

java 复制代码
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

用于实现数据库认证

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的配置类

java 复制代码
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

处理认证失败的请求。

java 复制代码
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);
    }
}
相关推荐
考虑考虑3 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261353 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊4 小时前
Java学习第22天 - 云原生与容器化
java
渣哥6 小时前
原来 Java 里线程安全集合有这么多种
java
间彧6 小时前
Spring Boot集成Spring Security完整指南
java
间彧6 小时前
Spring Secutiy基本原理及工作流程
java
Java水解7 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆10 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学10 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole10 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端