Java安全框架——SpringSecurity

一、Spring Security 简介

什么是Spring Security

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,专为基于 Spring 的应用程序设计。它提供全面的安全解决方案,包括身份验证(Authentication)、授权(Authorization)、防护攻击(如 CSRF、会话固定等)以及与其他安全标准(如 OAuth2、OpenID Connect)的集成。

核心功能

身份验证(Authentication)
支持多种身份验证方式,例如表单登录、HTTP Basic、OAuth2、LDAP 等。默认提供基于数据库的用户存储,并允许自定义用户详情服务(UserDetailsService)。

授权(Authorization)
通过方法级安全(@PreAuthorize)和 URL 级安全(HttpSecurity配置)实现细粒度的权限控制。支持角色(Role)和权限(Authority)的灵活配置。

防护攻击
内置防护措施,如:

  • CSRF(跨站请求伪造)防护
  • 会话固定(Session Fixation)防护
  • 点击劫持(Clickjacking)防护
  • 安全头部(Security Headers)自动配置

集成扩展
与 Spring Boot、OAuth2、JWT(JSON Web Token)、SAML 等无缝集成,适用于单体应用和微服务架构。

二、快速上手

坐标引入

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
  </parent>
  <groupId>org.example</groupId>
  <artifactId>SpringSecurity</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>SpringSecurity</name>
  <description>SpringSecurity</description>
  <properties>
    <java.version>21</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</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-test</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>


</project>

基础使用

首先你需要随便编写一个接口,如下:

package com.qgy.springsecurity.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goods")
public class GoodsWeb {

    @GetMapping("/get")
    public String getGoods() {
        return "商品信息";
    }

}

我们在没有使用SpringSecurity时,是可以直接访问该接口的:

而在使用的该框架后,其为我们提供了两个接口login和logout,此时我们要访问我们的接口时就需要登录后才可以进行访问,如图:

注意:第一次使用时登录账号为user,密码在启动时有一个字符串,如图:

三、登录认证

登录校验基本流程

流程:

  1. 携带用户名密码等访问登录接口;
  2. 去和数据库对比校验;
  3. 当数据正确时,使用用户id生成JWT(token);
  4. 将JWT(token)响应给前端;
  5. 用户访问接口;
  6. 前端发起请求并在请求头中携带token;
  7. 解析token获取到用户id;
  8. 通过用户id获取用户信息并查询其权限;
  9. 如果有权限则访问目标资源;
  10. 响应信息;

SpringSecurity登录校验完整流程

1. 原理

SpringSecurity的原理其实就是一个过滤器链,内部包含了各种功能的过滤器,如图:

核心过滤器:

  1. UsernamePasswordAuthenticationFilter:负责用户的登录请求的处理;
  2. ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException;
  3. FilterSecurityInterceptor:负责权限校验。

2. 基础用法流程图

流程:

  1.  用户提交用户名和密码;
  2. 封装Authentication对象将用户名和账号加入到对象中(此时无权限信息);
  3. 调用authentication方法进行认证;
  4. 调用DaoAuthenticationProvider的authentication方法进行认证;
  5. 调用loadUserByUsername方法查询用户:(1)根据用户名去查询用户及其权限,InMemoryUserDetailsManager是在内存中查找;(2)把对应的用户信息包括权限信息封装成UserDetails对象;
  6. 返回UserDetails对象;
  7. 通过PasswordEncoder对比UserDetails中的密码和Authentication的密码是否相同;
  8. 如果正确就把UserDetails中的权限信息放入Authentication对象中;
  9. 返回Authentication对象;
  10. 如果上一步返回Authentication对象SecurityContextHolder.get(context).setAuthentication方法存储该对象;

注意:5.1操作在项目开发中,应该是从数据库中查询

三、集成JWT

坐标地址

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>

项目依赖配置文件

yml文件

spring:
  datasource:
    # 指定数据源类型为 DruidDataSource(使用阿里巴巴的 Druid 连接池)
    type: com.alibaba.druid.pool.DruidDataSource

    # MySQL JDBC 驱动类名(MySQL Connector/J 的驱动类)
    driver-class-name: com.mysql.cj.jdbc.Driver

    # 数据库连接 URL(需要填写实际的数据库地址、端口、数据库名称等信息)
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false

    # 数据库用户名(通常为 root,但生产环境应使用专用账号)
    username: root

    # 数据库密码
    password: root

    druid:
      # 初始化连接池时建立的连接数
      initial-size: 20

      # 最大空闲连接数(连接池中保持的最大空闲连接数量)
      max-idle: 20

      # 最大活跃连接数(同时最多有多少个连接被使用)
      max-active: 100

      # 获取连接时最大等待时间(单位:毫秒;超过该时间将抛出异常)
      max-wait: 10000

      # 连接回收线程运行的时间间隔(单位:毫秒;用于检测空闲连接是否超时)
      time-between-eviction-runs-millis: 60000

      # 连接在池中保持空闲的最短时间(单位:毫秒;超过该时间的空闲连接可能被回收)
      min-evictable-idle-time-millis: 30000

      # 用于验证连接是否有效的 SQL 查询语句
      validation-query: SELECT 1 FROM DUAL

      # 是否在连接空闲时进行有效性检查(有助于及时发现失效连接)
      test-while-idle: true

      # 是否在获取连接时进行有效性检查(确保返回给应用的连接是可用的)
      test-on-borrow: true

      # 是否在归还连接时进行有效性检查(一般设为 false 以提高性能)
      test-on-return: false

  data:
    redis:
      # Redis 数据库索引号(默认为 0,适用于多数据库场景)
      database: 0

      # Redis 服务器主机地址(需填写实际的 IP 或域名)
      host: 127.0.0.1

      # Redis 服务监听端口号(默认为 6379)
      port: 6379

      lettuce:
        pool:
          # 最大连接数(-1 表示无限制,但通常应设置一个合理的值)
          max-active: 8

          # 获取连接的最大等待时间(-1 表示无限等待)
          max-wait: -1

          # 最大空闲连接数(控制连接池资源的使用上限)
          max-idle: 8

        # Redis 客户端关闭时的最大等待时间(单位:毫秒)
        shutdown-timeout: 10000

  jackson:
    # 设置日期/时间的序列化格式(输出 JSON 时使用 yyyy-MM-dd HH:mm:ss 格式)
    date-format: yyyy-MM-dd HH:mm:ss

    # 设置 Jackson 使用的时区(GMT+8 表示中国标准时间)
    time-zone: GMT+8

mybatis-plus:
  global-config:
    db-config:
      # 配置逻辑删除字段名(表示软删除状态的字段,例如 deleted)
      logic-delete-field: deleted

      # 配置逻辑删除值(表示已删除的状态,例如 1)
      logic-delete-value: 1

      # 配置非逻辑删除值(表示未删除的状态,例如 0)
      logic-not-delete-value: 0

  configuration:
    # 自动将数据库下划线命名映射为 Java 对象的驼峰命名(如 user_name -> userName)
    map-underscore-to-camel-case: true

    # 设置 MyBatis 的日志实现类(使用标准输出打印 SQL 日志,适合调试)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  # 配置 MyBatis Mapper XML 文件的位置(扫描 classpath 下的 /mappers/**/*.xml)
  mapper-locations: classpath*:/mappers/**/*.xml

redis配置文件(为后面做准备)

package com.qgy.springsecurity.configs;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

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 value) throws SerializationException {
        if (value == null) {
            return new byte[0];
        }
        return JSON.toJSONString(value, 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);
    }
}
package com.qgy.springsecurity.configs;

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);

        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

}

这里注意要用自己写的FastJsonRedisSerializer序列号redis数据。

JWT工具类

package com.qgy.springsecurity.utils;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * JWT 工具类,用于生成、解析和验证 JWT(JSON Web Token)
 */
public class JwtUtil {

    /**
     * JWT 默认过期时间:60 分钟(单位毫秒)
     */
    public static final Long JWT_TTL = 60 * 60 * 1000L;

    /**
     * JWT 签名密钥(Base64 编码前的原始字符串)
     * 每次重启应用都会变化,实际生产环境应使用固定密钥或从配置文件中读取
     */
    public static final String JWT_KEY = Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded());

    /**
     * 生成一个不带连字符的 UUID 字符串,通常用作 JWT 的唯一标识 ID(jti)
     *
     * @return 去除 "-" 后的 UUID 字符串
     */
    public static String getUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 创建 JWT 令牌(默认使用随机生成的 ID)
     *
     * @param subject   主题(通常是用户信息,如用户名或用户ID)
     * @param ttlMillis 过期时间(单位:毫秒),若为 null 则使用默认值 JWT_TTL
     * @return JWT 令牌字符串
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
        return builder.compact(); // 构建并返回紧凑格式的 JWT 字符串
    }

    /**
     * 构建 JWT 对象的基础方法
     *
     * @param subject 主题信息
     * @param ttlMillis 过期时间(毫秒)
     * @param uuid      JWT 的唯一标识(jti)
     * @return 返回构建好的 JwtBuilder 对象
     */
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 使用 HS256 算法签名
        SecretKey secretKey = generalKey(); // 获取签名密钥

        long nowMillis = System.currentTimeMillis(); // 当前时间戳
        Date now = new Date(nowMillis); // 当前时间对象

        if (ttlMillis == null) {
            ttlMillis = JWT_TTL; // 若未指定过期时间,则使用默认值
        }

        long expMillis = nowMillis + ttlMillis; // 计算过期时间戳
        Date expDate = new Date(expMillis); // 设置过期时间

        // 构建 JWT
        return Jwts.builder()
                .setId(uuid)               // 设置 JWT ID(唯一标识)
                .setSubject(subject)       // 设置主题(如用户ID)
                .setIssuer("服务器")       // 设置签发者
                .setIssuedAt(now)          // 设置签发时间
                .signWith(signatureAlgorithm, secretKey) // 设置签名算法和密钥
                .setExpiration(expDate);   // 设置过期时间
    }

    /**
     * 创建 JWT 令牌(使用自定义 ID)
     *
     * @param id        自定义的 JWT ID(jti)
     * @param subject   主题信息(如用户名)
     * @param ttlMillis 过期时间(毫秒)
     * @return JWT 令牌字符串
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
        return builder.compact();
    }

    /**
     * 根据 JWT_KEY 生成 Base64 解码后的 SecretKey(用于签名和验证)
     *
     * @return SecretKey 密钥对象
     */
    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JWT_KEY); // 将密钥解码为字节数组
        // 使用 AES 算法构造 SecretKey
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    /**
     * 解析并验证 JWT 字符串,返回其包含的 Claims(负载内容)
     *
     * @param jwt 待解析的 JWT 字符串
     * @return 包含 JWT 负载信息的 Claims 对象
     * @throws Exception 如果 JWT 无效或签名错误会抛出异常
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey = generalKey(); // 获取签名密钥
        return Jwts.parser() // 创建 JWT 解析器
                .setSigningKey(secretKey) // 设置签名密钥
                .parseClaimsJws(jwt) // 解析并验证 JWT
                .getBody(); // 获取 JWT 中的 Claims(即 payload)
    }

    public static <T> T getSubject(String jwt, Class<T> clazz) {
        try {
            // 构建解析器
            JwtParser jwtParser = Jwts.parserBuilder()
                    .setSigningKey(generalKey())
                    .build();

            // 解析 JWT
            Jws<Claims> jws = jwtParser.parseClaimsJws(jwt);
            Claims claims = jws.getBody();

            // 获取 subject 字段内容(通常是 JSON 字符串)
            String subjectJson = claims.getSubject();

            if (subjectJson == null || subjectJson.isEmpty()) {
                throw new IllegalArgumentException("Subject is empty in the JWT token.");
            }

            // 使用 Jackson 反序列化成目标类型
            return JSON.parseObject(subjectJson,clazz);

        } catch (Exception e) {
            throw new RuntimeException("token解析错误", e);
        }
    }
}

自定义security的过滤器链

1.自定义UserDetails封装类
package com.qgy.springsecurity.models.views;

import com.qgy.springsecurity.models.bases.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Collection;

public class LoginUser implements UserDetails {

    private SysUser user;

    public SysUser getUser() {
        return user;
    }

    public void setUser(SysUser user) {
        this.user = user;
    }

    public LoginUser(SysUser user) {
        this.user = user;
    }

    public LoginUser() {

    }

    //权限列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.user.getPassword();
    }

    @Override
    public String getUsername() {
        return this.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;
    }
}
2.Jwt过滤器
package com.qgy.springsecurity.filters;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qgy.springsecurity.dao.UserDao;
import com.qgy.springsecurity.models.MyResult;
import com.qgy.springsecurity.models.bases.SysUser;
import com.qgy.springsecurity.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String jwt = null;
        try {
            jwt = extractJwtFromRequest(request);
            if (jwt != null && JwtUtil.parseJWT(jwt) != null) {
                Authentication authentication = getAuthenticationByToken(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            // 这里不需要特别处理异常,因为Spring Security会根据配置处理未授权的情况
        }

        filterChain.doFilter(request, response);
    }

    private Authentication getAuthenticationByToken(String jwt) {
        // 构造一个空的权限集合
        Collection<? extends GrantedAuthority> authorities = new ArrayList<>();

        // 使用带权限的构造方法
        return new UsernamePasswordAuthenticationToken(jwt, null, authorities);
    }

    private String extractJwtFromRequest(HttpServletRequest request){
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7); // 去掉 "Bearer " 前缀
        } else {
            return null;
        }
    }
}

Security配置类

package com.qgy.springsecurity.configs;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qgy.springsecurity.dao.UserDao;
import com.qgy.springsecurity.filters.JwtAuthenticationFilter;
import com.qgy.springsecurity.models.MyResult;
import com.qgy.springsecurity.models.bases.SysUser;
import com.qgy.springsecurity.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

@Configuration
public class SecurityConfig {

    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/qgy/users/login").permitAll() // 登录接口允许匿名访问
                        .requestMatchers("/qgy/**").authenticated() // 其他/qgy下的路径需要认证
                        .anyRequest().denyAll() // 拒绝所有其他请求
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling(exceptionHandling ->
                        exceptionHandling.authenticationEntryPoint(new JwtAuthenticationEntryPoint()) // 处理未认证的请求
                )
                .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护
                .sessionManagement(sessionManagement ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态会话管理,每一次都必须携带token
                )
                .authenticationProvider(myAuthenticationProvider);

        return http.build();
    }
}

@Component
class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(MyResult.error().message("登录失败").toString());
    }
}

@Component
class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDao userDao;

    @Lazy
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String principal = (String) authentication.getPrincipal();
        String credentials = (String) authentication.getCredentials();
        if (Objects.isNull(credentials)) {
            //只有一个传参,应该为token
            Long uid = JwtUtil.getSubject(principal, Long.class);
            SysUser user = userDao.selectOne(new QueryWrapper<>() {{
                eq("id", uid);
            }});
            if (Objects.isNull(user)) throw new RuntimeException("用户不存在");
            return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
        } else {
            //判断账号密码是否正确
            SysUser user = userDao.selectOne(new QueryWrapper<>() {{
                eq("user_name", principal);
            }});
            if (Objects.isNull(user) || !passwordEncoder.matches(credentials, user.getPassword())) throw new RuntimeException("用户名或密码错误");
            return new UsernamePasswordAuthenticationToken(user,null,new ArrayList<>());
//            Long num = userDao.selectCount(new QueryWrapper<>() {{
//                eq("user_name", principal);
//            }});
//            if (!num.equals(1L)) throw new RuntimeException("用户名或密码错误");
//            return new UsernamePasswordAuthenticationToken(principal, null, new ArrayList<>());
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

代码解释:我们主要重写了最后两步,自定义了一个MyAuthenticationProvider来处理我们需要的验证方式(账号密码验证和token验证),并使用我们自己的Dao层来从数据库可以进行查询来保持持久化,以便于我们后面来进行权限的设置。

结果:

登录测试

请求测试:

获取用户信息测试:

先到这吧,写不动了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星极天下第一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值