当前位置: 首页 > article >正文

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

文章目录

  • 简介
  • 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.kler.cn/a/383623.html

相关文章:

  • 【原创】java+ssm+mysql收纳培训网系统设计与实现
  • 法语nous sommes
  • 本地Docker部署ZFile网盘打造个人云存储,告别公共网盘让你数据安全感爆棚
  • 初识Mysql数据库
  • 【spark面试题】RDD和DataFrame以及DataSet有什么异同
  • Github 2024-11-07 Go开源项目日报 Top10
  • 视频自动播放被浏览器阻止及其解决方案
  • 「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
  • 【神经网络加速】神经加速棒
  • Spring中@Autowired@Resource和@Inject注解区别
  • 记录学习react的一些内容
  • 123456789
  • K8S node节点没有相应的pod镜像运行故障处理办法
  • Spring Boot驱动的导师双选系统:设计与实现
  • 现货白银的交易技巧:成功进行趋势跟踪的技巧
  • 双指针算法篇——一快一慢须臾之间解决问题的飘逸与灵动(3)
  • IT专业入门,高考假期预习指南
  • Linux服务管理-DHCP
  • 【STM32】项目实战——OV7725/OV2604摄像头颜色识别检测(开源)
  • 【bug日志-水】解决本地开发下代理和url同名导致刷新404的问题
  • webpack使用详解
  • Uniapp在Vue环境中引入iconfont图标库(详细教程)
  • 7.2、实验二:被动接口和单播更新
  • 拼多多客服安抚顾客话术大全
  • Bert框架详解(下)
  • conda 设置代理