文章目录
- 简介
- 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算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
通俗的说:
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(