SpringSecurity6+OAuth2.0 从入门到熟练使用

news2024/11/4 17:27:58

文章目录

  • 简介
  • 1、快速入门
    • 1.1 准备工作
        • 我们先要搭建一个SpringBoot工程
          • ① 创建工程 添加依赖
          • ② 创建启动类
          • ③ 创建Controller
    • 1.2 引入SpringSecurity
  • 2、 认证
    • 2.1 登录校验流程
    • 2.2 原理分析
      • 2.2.1 SpringSecurity完整流程
      • 2.2.2 认证流程详解
          • 概念速查:
    • 2.3 解决问题
      • 2.3.1 思路分析
      • 2.3.2 添加依赖及配置文件
        • yml配置:
      • 2.3.3 添加Redis相关配置
      • 2.2.4 添加响应类
        • 接口
      • 2.3.5 添加工具类- 基于token的鉴权机制
        • 1 什么是SSO
        • 2 为什么要用SSO
        • 3 什么是JWT?
        • 4 JWT的作用
        • 5 JWT工作流程
        • 6 JWT长什么样?
        • 7 JWT的构成
          • header
          • playload
          • signature
        • 8 JwtUtils工具类
          • 添加依赖
      • 2.3.6 认证的实现
        • 1 配置数据库校验登录用户
          • 1 实体类
          • 2 定义Mapper接口
          • 3 配置Mapper扫描
          • 4 测试MP是否能正常使用
        • 2 密码加密存储
        • 3 自定义登陆接口
        • 4 接口放行配置
        • 5 UserServiceImpl 实现类
        • 6 认证校验过滤器
        • 7 注册认证过滤器
  • 3、授权
    • 3.0 权限系统的作用
    • 3.1 授权基本流程
    • 3.2 授权实现
      • 3.2.1 限制访问资源所需权限
      • 3.2.2 封装权限信息
      • 3.3 从数据库查询权限信息
        • 3.3.1 RBAC权限模型
        • 3.3.2 准备工作
        • 3.3.3 代码实现
        • 3.3.4 测试接口权限
  • 4、自定义处理器
    • 4.1 自定义验证异常类
    • 4.2 编写认证用户无权限访问处理器
    • 4.3 编写匿名用户访问资源处理器
    • 4.4 改造认证校验过滤器
    • 4.5 自定义认证失败处理器
    • 4.6 修改UserDetailsServiceImpl
    • 4.7 配置SecurityConfig
    • 4.8 用户退出系统
      • 改造登录接口:
      • 退出后台代码实现:
      • 认证过滤器再次添加校验的代码信息:
  • 5、扩展OAuth2.0
    • 5.1 OAuth2.0介绍
    • 5.2 集成JustAuth
      • 5.2.1 引入依赖
      • 5.2.2 在gitee创建应用
      • 5.2.2 创建Request
      • 5.2.3 代码示例

简介

跟详细版本:建议系统认识看这个老版本,大致认识就看6版本
Spring Security :是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架 Shiro ,它提供了更丰富的功能,社区资源也比Shiro丰富。

一般来说中大型的项目都是使用 SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。

一般Web应用的需要进行 认证 和 授权 。

  • 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
  • 授权:经过认证后判断当前用户是否有权限进行某个操作

而认证和授权也是SpringSecurity作为安全框架的核心功能。

1、快速入门

1.1 准备工作

技术版本:

sprinboot
mybatisplus
redis6+
jwt
mysql8+
springsecurity 6.2+
我们先要搭建一个SpringBoot工程
① 创建工程 添加依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
	</dependency>  
</dependencies>
② 创建启动类
package cn.js;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
   
public static void main(String[] args) {
   
    SpringApplication.run(SecurityApplication.class,args);  
	}
}
③ 创建Controller
package cn.js.controller;
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
   
	
@RequestMapping("/hello") 
public String hello(){
   
    return "hello";
    }
}

1.2 引入SpringSecurity

在SpringBoot项目中使用SpringSecurity我们只需要引入依赖即可实现入门案例。

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

​ 引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。

​ 必须登陆之后才能对接口进行访问。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

退出:输入logout即可。

在这里插入图片描述

2、 认证

2.1 登录校验流程

在这里插入图片描述

2.2 原理分析

想要知道如何实现自己的登陆流程就必须要先知道入门案例中SpringSecurity的流程。

2.2.1 SpringSecurity完整流程

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

SpringSecurity6 之前一共15个过滤器,6及之后一共16个

在这里插入图片描述

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。

UsernamePasswordAuthenticationFilter : 负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

ExceptionTranslationFilter: 处理过滤器链中抛出的任何 AccessDeniedException 和 AuthenticationException 。

FilterSecurityInterceptor: 负责权限校验的过滤器。通俗一点就是授权由它负责。(鉴权,授权)

我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。

在这里插入图片描述

在这里插入图片描述

输入:run.getBean(DefaultSecurityFilterChain.class),敲回车

在这里插入图片描述

2.2.2 认证流程详解

在这里插入图片描述

在这里插入图片描述

概念速查:

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法(生产环境中重写该接口的实现类,不能基于内存了,改为连接数据库)。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

2.3 解决问题

2.3.1 思路分析

登录

在这里插入图片描述

①自定义登录接口

​ 调用ProviderManager的方法进行认证 如果认证通过生成jwt

​ 把用户信息存入redis中

②自定义UserDetailsService

​ 在这个实现类中去查询数据库

校验

​ 思考:从JWT认证过滤器中获取到userid后怎么获取到完整的用户信息?

在这里插入图片描述

​ 结论:如果认证通过,使用用户id生成一个jwt,然后用userid作为key,用户信息作为value存入redis,用户下一次访问的时候,到达JWT认证过滤器后,再去redis中就可以取到对应的用户信息(缓解一部分数据库的压力)

两种方案:

1.redis中存储jwt

2.不在redis中存储jwt,自解释

①定义Jwt认证过滤器:

​ 获取token

​ 解析token获取其中的userid

​ 从redis中获取用户信息(可选)

存入SecurityContextHolder


2.3.2 添加依赖及配置文件

<!--fastjson依赖-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.0</version>
</dependency>
<!--jwt依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
<!--web依赖-->
<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>

<!--mybatisplus依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.6</version>
</dependency>

<!--mysql驱动依赖-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.8</version>
</dependency>

<!--lombok依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!--validation依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!--redis坐标-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--springdoc-openapi-->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
    <version>2.1.0</version>
</dependency>
yml配置:
server:
  port: 8001
  #address: 127.0.0.1
#spring数据源配置
spring:
  application:
    name: token #项目名
# 数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security_manager?serverTimezone=GMT%2B8&useUnicode=true&useSSL=false&characterEncoding==utf-8
    username: root
    password: root
    druid:
      initial-size: 20
      min-idle: 20
      max-active: 100
      max-wait: 10000
      time-between-eviction-0runs-millis: 60000
      min-evictable-idle-time-millis: 30000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: true
      test-on-return: true

# redis 配置
  data:
    redis:
      database: 0
      host: 123.61.184.15
      port: 6379
      password: Qjsboss@123
      lettuce:
        pool:
          #最大连接数
          max-active: 8
          #最大阻塞等待时间(负数表示没限制)
          max-wait: -11
            #最大空闲
          max-idle: 8
          #最小空闲
          min-idle: 0
          #连接超时时间
          timeout: 10000
  # jackson 配置
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
# mybatis-plus配置
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  configuration:
    map-underscore-to-camel-case: true # 数据库下划线自动转驼峰标示关闭
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志配置
  mapper-locations: classpath*:/mapper/**/*.xml

2.3.3 添加Redis相关配置

package cn.js.config;

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;

/**
 * Redis 使用FastJson序列化
 **/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
   
    public static final Charset DEFAULT_CHARSET = Charset.forName("uTF-8");
    private Class<T> clazz;

    static {
   
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
   
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
   
        if (t == null) {
   
            return new byte[0];

        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
   
        if (bytes == null || bytes.length <= 0) {
   
            return null;

        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);

    }
}
package cn.js.config;

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;

/**
 * @Author Js
 * @Description
 * @Date 2024-10-27 16:46
 * @Version 1.0
 **/
@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);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        //Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}


2.2.4 添加响应类

package cn.js.common;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

@Data
public class R {
   
    private Boolean success; //返回的成功或者失败的标识符
    private Integer code; //返回的状态码
    private String message; //提示信息
    private Map<String, Object> data = new HashMap<String, Object>(); //数据

    //把构造方法私有
    private R() {
   
    }

    //成功的静态方法
    public static R ok() {
   
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    //失败的静态方法
    public static R error() {
   
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    //使用下面四个方法,方面以后使用链式编程
// R.ok().success(true)
// r.message("ok).data("item",list)
    public R success(Boolean success) {
   
        this.setSuccess(success);
        return this; //当前对象 R.success(true).message("操作成功").code().data()
    }

    public R message(String message) {
   
        this.setMessage(message);
        return this;
    }

    public R code(Integer code) {
   
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value) {
   
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map) {
   
        this.setData(map);
        return this;
    }
}
接口
package cn.js.common;
public interface ResultCode {
   
Integer SUCCESS=20000;
Integer ERROR=20001;
}

2.3.5 添加工具类- 基于token的鉴权机制

1 什么是SSO

SSO (Single Sign On),中文翻译为单点登录,它是目前流行的企业业务整合的解决方案之一,SSO的目标是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
使用SSO整合后,只需要登录一次就可以进入多个系统,而不需要重新登录,这不仅仅带来了更好的用户体验,更重要的是降低了安全的风险和管理的消耗,

2 为什么要用SSO

现在流行的Web系统不断变的复杂,从最早的单系统模块发展到现在的多系统多模块的应用群,用户在访问这些群的各个系统的时候,是不是都要分别登录,登出?

如果是这样,有N多个系统,用户可能会疯掉。用户希望的是在这些系统中统一的登录和登出,换句话说,不管登录哪个系统之后,其他子系统就无需再登录了。
举个实际应用的例子,比如京东的各个子系统,你会发现在浏览器不关闭的情况下,登录一个成功后,再访问另一个是无需再登录的,这种方式就是单点登录SSO的应用。

单点登录的实现方式,要实现单点登录,方式有很多,原理也各不相同,在这里主要讲主流的方案:使用WT机制实现单点登录。

后台返回token,前端用什么保存?Cookie,Sessionstorage,Localstorage

3 什么是JWT?

官网:https://jwt.io/introduction
JSON Web Token令牌 (JWT) 是一种开放标准(RFC 7519)它定义了一种紧凑且自包含的方式,用于在各方之间作为|SON对象安全地传输信息。

此信息可以验证和信任,因为它是经过数字签名的。JWT可以使用秘钥 (使用HMAC算法)或使用RSAECDSA的公钥/私钥对进行签名。

通俗的说:
JSON Web Token简称:JWT,也就是通过 JSON 形式作为Web应用的令牌,用于在各方之间安全地将信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密,签名等相关处理。

4 JWT的作用

JSON Web Token (JWT) 是为了在网络应用环境间传递声明而执,行的一种基于 JSON的开放标准,它定义了一种紧凑的、自包含的方式,用于作为ISON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

5 JWT工作流程

Authorization (授权):这是使用WT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器

  • 服务器进行验证用户的信息

  • 服务器通过验证发送给用户一个token

  • 客户端存储token,并在每次请求时携带上这个token值

  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里,另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了

Access-Control-Allow-Origin:*

6 JWT长什么样?

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

7 JWT的构成

第一部分我们称它为头部(header)
第二部分我们称其为载荷(payload,类似于飞机上承载的物品)
第三部分是签证(signature).

header

Jwt的头部承载两部分信息:

  • 声明类型,这里是Jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
   
'typ': 'JWT',
'alg': 'HS256'
}

然后将头部进行base64加密,构成了第一部分

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss : jwt签发者
  • sub : jwt所面向的用户
  • aud : 接收jwt的一方
  • exp : jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf : 定义在什么时间之前,该jwt都是不可用的.
  • iat : jwt的签发时间
  • jti : jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏 感信息,因为该部分在客户端可解密.

私有的声明 :

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的, 意味着该部分信息可以归类为明文信息。

定义一个payload: 用户信息

{
   
"sub": "1234567890",
"name": "mengshujun",
"admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret (盐-密钥)

这个部分需要base64加密后的header和base64加密后的payload使用 . 连接组成的字符串,然后通过 header中声明的加密方式进行加盐 secret 组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); //
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用 . 连接成一个完整的字符串,构成了最终的jwt:

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和 jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。


8 JwtUtils工具类
package cn.js.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

public class JwtUtils {
   
    //有效期为
    public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000 一个小时
    //设置秘钥明文(盐)
    public static final String JWT_KEY = "qW21YIU&^%$";

    //生成令牌
    public static String getUUID() {
   
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

    /**
     * 生成jtw
     *
     * @param subject   token中要存放的数据(json格式) 用户数据
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
   
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }

    //生成jwt的业务逻辑代码
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis,String uuid) {
   
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//签名算法
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();//获取到系统当前的时间戳
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
   
            ttlMillis = JwtUtils.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid) //唯一的ID
                .setSubject(subject) // 主题 可以是JSON数据
                .setIssuer("xx") // 签发者
                .setIssuedAt(now) // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }

    /**
     * 创建token
     *
     * @param id
     * @param subject
     * @param ttlMillis 添加依赖
     *                  2.3.5 认证的实现
     *                  1 配置数据库校验登录用户
     *                  从之前的分析我们可以知道,我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的
     *                  UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
     *                  我们先创建一个用户表, 建表语句如下:
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
   
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成加密后的秘钥 secretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
   
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtils.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length,
                "AES");
        return key;
    }

    /**
     * 解析jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
   
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}
添加依赖
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

2.3.6 认证的实现

1 配置数据库校验登录用户

从之前的分析我们可以知道,我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。

准备工作

我们先创建一个用户表, 建表语句如下:

CREATE TABLE `sys_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',
`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',
`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',
`sex` CHAR(

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt报错QOCI driver not loaded且QOCI available的解决方法

参考 Linux Qt 6安装Oracle QOCI SQL Driver插件&#xff08;适用WSL&#xff09; 安装 QOCI 插件完成后运行 Qt 项目报错&#xff1a; qt.sql.qsqldatabase: QSqlDatabase: QOCI driver not loaded qt.sql.qsqldatabase: QSqlDatabase: available drivers: QMIMER QPSQL QODBC…

【AI工作流】FastGPT - 深入解析FastGPT工作流编排:从基础到高级应用的全面指南

文章目录 一、工作流编排概述二、FastGPT的节点类型1. 基础功能插件(1) 文本输出(2) 功能调用(3) 工具(4) 外部调用(5) 其他 2. 系统插件3. 团队插件 三、工作流中的流向结语 在当今快速发展的人工智能领域&#xff0c;工作流编排的能力已成为提升用户体验和应用效率的关键因素…

SQL 常用语句

目录 我的测试环境 学习文档 进入数据库 基础通关测验 语句-- 查 展示数据库&#xff1b; 进入某个数据库&#xff1b; 展示表&#xff1a; 展示某个表 desc 查询整个表&#xff1a; 查询特定列&#xff1a; 范围查询 等于特定值 不等于 介于 特定字符查询 Li…

python在word的页脚插入页码

1、插入简易页码 import win32com.client as win32 from win32com.client import constants import osdoc_app win32.gencache.EnsureDispatch(Word.Application)#打开word应用程序 doc_app.Visible Truedoc doc_app.Documents.Add() footer doc.Sections(1).Footers(cons…

Centos环境下安装docker

本文演示离线版安装用于没有网络环境的系统 在线版安装可参考以下链接 https://www.runoob.com/docker/centos-docker-install.html 一、docker离线安装 1、下载docker离线安装包 docker下载地址&#xff1a; Docker版本下载 选择版本 下载后上传至服务器 百度网盘下载…

Java 中的 Map 集合及其子类详解:HashMap 和 TreeMap

Java 中的 Map 集合及其子类详解&#xff1a;HashMap 和 TreeMap 在 Java 编程中&#xff0c;Map 是一种用于存储键值对的集合结构。Java 提供了多种 Map 实现类&#xff0c;其中最常用的是 HashMap 和 TreeMap。 双列集合 在Java中&#xff0c;双列集合&#xff08;也称为映…

CoCode助力志晟信息成功通过CMMI五级复评!

热烈祝贺河北志晟信息技术股份有限公司在近期CMMI&#xff08;能力成熟度模型集成&#xff09;评估中&#xff0c;再次以卓越表现顺利通过CMMI成熟度五级的复评。 CoCode助力志晟信息通过CMMI五级 2024年9月1日至10日&#xff0c;CMMI评估组对志晟信息管理体系及其应用运行情况…

AD 授予委派full control权限后,部分用户无法unlock

文章目录 问题描叙根因解決方法&#xff1a; 问题描叙 通過委派方式授予被委派用戶full control 权限后&#xff0c;部分用户unlock是灰色显示&#xff1a; 根因 对比能正常unlock与无法unlock的用户&#xff0c;发现繼承無法unlock的用戶沒有"enable inheritance&quo…

css实现边框双色凹凸半圆

整体效果如下图&#xff1a; 结构代码&#xff1a; <div classline-outside-wrap><div classwrap><img src../img/avatar2x.png/><div classcontent-wrap></div></div></div> 内凹框实现&#xff1a; .content-wrap{width:100%;he…

算法深度剖析:前缀和

文章目录 前言一、一维前缀和模板二、二维前缀和模板三、寻找数组的中心下标四、除自身以外数组的乘积五、和为 K 的子数组六、和可被 K 整除的子数组七、连续数组八、矩阵区域和 前言 本章将深度剖析前缀和&#xff0c;以及总结前缀和模板。 前缀和是一种在算法和数据处理中…

关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

文章目录 1.冒泡排序2.二分查找3.转移表希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 根据当前所学C语言知识&#xff0c;对前面知识进行及时的总结巩固&#xff0c;出了这么一篇 vlog 介绍当前所学知识能遇到的常见算法&#xff0c;这些算法是…

特定领域的Embeddings模型微调全面指南

假设你正在为医学领域构建一个问答系统。你希望确保当用户提出问题时&#xff0c;系统能够准确地检索相关的医学文章。但是通用的嵌入模型可能在处理医学术语的高度专业化词汇和细微差别时会遇到困难。 这时候&#xff0c;微调就能派上用场了&#xff01;&#xff01;&#xf…

视频推荐的算法(字节青训)

题目&#xff1a; 西瓜视频 正在开发一个新功能&#xff0c;旨在将访问量达到80百分位数以上的视频展示在首页的推荐列表中。实现一个程序&#xff0c;计算给定数据中的80百分位数。 例如&#xff1a;假设有一个包含从1到100的整数数组&#xff0c;80百分位数的值为80&#…

安卓13默认连接wifi热点 android13默认连接wifi

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 有时候我们需要让固件里面内置好,相关的wifi的ssid和密码,让固件起来就可以连接wifi,不用在手动操作。 2.问题分析 这个功能,使用普通的安卓代码就可以实现了。 3.代…

详解RabbitMQ三种队列类型

RabbitMQ 是一个强大的消息队列系统&#xff0c;它提供了多种队列类型以满足不同的使用需求。本文将探讨三种主要队列类型&#xff1a;经典队列、仲裁队列和流式队列&#xff0c;并讨论它们的区别和选型建议。 经典队列&#xff08;Classic Queues&#xff09; 简介&#xff…

CytoSPACE·空转和单细胞数据的高分辨率比对

1. 准备输入文件&#xff0c;需要四个文件&#xff0c;所有文件都应以制表符分隔的表格输入格式 (.txt) 提供。 a. scRNA-seq 基因表达文件 矩阵必须是基因&#xff08;行&#xff09;乘以细胞&#xff08;列&#xff09;。 第一行必须包含单个细胞 ID&#xff0c;第一列必须…

react使用Fullcalendar

前言&#xff1a; 最近在做项目时&#xff0c;遇到了需要用日历的项目。一开始考虑使用antd的日历组件。后来 调研技术库&#xff0c;发现了fullcalendar 库。经过对比 fullcalendar 更强大&#xff0c;更灵活。 其实 antd的日历组件 也不错&#xff0c;简单的需求用他也行。…

LabVIEW过程控制实验平台

A3000实验平台通过LabVIEW开发&#xff0c;实现了过程控制的虚拟仿真与实时通信&#xff0c;显著提高了教学与实验的互动性和效率。该平台采用模块化设计&#xff0c;支持多种控制策略的实验教学&#xff0c;克服了传统实验设备的不足。项目背景 目前高校过程控制实验设备普遍…

腾讯会议pc端3.29.11开启悬浮窗口

之前是&#xff1a;pc端每次最小化&#xff0c;它就自动收回到任务栏里了 版本&#xff1a;3.29.11 解决办法&#xff1a; 打开腾讯会议&#xff0c;点击左上角的【头像】。 单击【设置】。 选择【显示当前说话者】来管理麦克风浮窗。 再进入会议&#xff0c;点击最小化一哈&…

聊一聊:ChatGPT搜索引擎会取代谷歌和百度吗?

当地时间 10 月 31 日&#xff0c;OpenAI 正式推出了 ChatGPT 搜索功能&#xff0c;能实时、快速获取附带相关网页来源链接的答案。这一重大升级标志着其正式向谷歌的搜索引擎霸主地位发起挑战。 本周五我们聊一聊&#xff1a; 欢迎在评论区畅所欲言&#xff0c;分享你的观点~ …