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.Driverurl: jdbc:mysql://localhost:3306/security_manager?serverTimezone=GMT%2B8&useUnicode=true&useSSL=false&characterEncoding==utf-8username: rootpassword: rootdruid:initial-size: 20min-idle: 20max-active: 100max-wait: 10000time-between-eviction-0runs-millis: 60000min-evictable-idle-time-millis: 30000validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: truetest-on-return: true# redis 配置data:redis:database: 0host: 123.61.184.15port: 6379password: Qjsboss@123lettuce:pool:#最大连接数max-active: 8#最大阻塞等待时间(负数表示没限制)max-wait: -11#最大空闲max-idle: 8#最小空闲min-idle: 0#连接超时时间timeout: 10000# jackson 配置jackson:date-format: yyyy-MM-dd HH:mm:sstime-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;}@Overridepublic byte[] serialize(T t) throws SerializationException {if (t == null) {return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic 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.rhkb.cn/news/466377.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络——SDN

分布式控制路由 集中式控制路由

自动驾驶革命:从特斯拉到百度,谁将主宰未来交通?

内容概要 自动驾驶技术正在经历一个前所未有的革命性变化&#xff0c;各大企业纷纷抢占这一充满潜力的新市场。以特斯拉和百度为代表的行业巨头&#xff0c;正利用各自的优势在这一技术的赛道上展开激烈竞争。特斯拉凭借其在电动汽车和自动驾驶领域的前瞻性设计与不断革新的技…

Group By、Having用法总结(常见踩雷点总结—SQL)

Group By、Having用法总结 目录 Group By、Having用法总结一、 GROUP BY 用法二、 HAVING 用法三、 GROUP BY 和 HAVING 的常见踩雷点3.1 GROUP BY 选择的列必须出现在 SELECT 中&#xff08;&#x1f923;最重要的一点&#xff09;3.2 HAVING 与 WHERE 的区别3.3 GROUP BY 可以…

MySQL存储目录与配置文件(ubunto下)

mysql的配置文件&#xff1a; 在这个目录下&#xff0c;直接cd /etc/mysql/mysql.conf.d mysql的储存目录&#xff1a; /var/lib/mysql Ubuntu版本号&#xff1a;

深度学习经典模型之Network in Network

1 Network in Network 1.1 模型介绍 ​ Network In Network (NIN)是由 M i n L i n Min Lin MinLin等人提出&#xff0c;在CIFAR-10和CIFAR-100分类任务中达到当时的最好水平&#xff0c;因其网络结构是由三个多层感知机堆叠而被成为NIN [ 5 ] ^{[5]} [5]。NIN以一种全新的角…

Java版ERP管理系统源码解析:利用Spring Cloud Alibaba和Spring Boot实现微服务架构

ERP系统&#xff0c;亦称为企业资源计划系统&#xff0c;是一种融合了企业多元部门和复杂业务的综合管理信息系统。在全球经济蓬勃发展及企业竞争日趋激烈的背景下&#xff0c;ERP系统已逐步跃升为现代企业管理的核心工具。该系统通过优化资源配置及提升业务流程效率&#xff0…

Python 基础笔记之生成器generator

生成斐波拉契数列 def fib(length):a,b0,1n0while n<length:yield aa,bb,abn1return abc g2fib(10) try:print(next(g2)) 生成器方法&#xff1a; __next__():获取下一个元素 send(value):向每次生成器调用中传值 注意&#xff1a;第一次调用send(None) def gen():i0while…

vscode翻译插件

vscode翻译插件 需求 &#xff1a; 在编写代码的时候&#xff0c; 打印或者定义变量的时候总是想不起来英文名称&#xff0c; 所有就开发了一款中文转换为英文的插件。 功能 1、目前支持选中中文&#xff0c;右键选择打印或者变量进行转换。 2、目前支持选中中文&#xff0…

美格智能5G车规级通信模组:高精度定位守护极致安全

物联网时代&#xff0c;众多应用和设备都需要位置相关服务&#xff0c;尤其是对移动场景而言&#xff0c;定位的需求更加重要。随着自动驾驶、高阶辅助驾驶等智能车载技术的高速发展&#xff0c;在智能车载领域的定位需求除基础的位置信息之外&#xff0c;还对信息获取的速度、…

SpringMVC学习记录(三)之响应数据

SpringMVC学习记录&#xff08;三&#xff09;之响应数据 一、页面跳转控制1、快速返回模板视图2、转发和重定向 二、返回JSON数据1、前置准备2、ResponseBody 三、返回静态资源1、静态资源概念2、访问静态资源 /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为h…

药品进销存表格制作 佳易王药店药品入库出库台账库存管理系统操作教程

一、概述 【软件试用版资源文件下载可以点文章最后官网卡片】 药品进销存表格制作 药店药品入库出库台账库存管理系统操作教程 ‌核心功能全面‌&#xff1a;涵盖药品进货、销售、库存管理&#xff0c;以及数据分析与报表生成。 ‌药品进货管理‌&#xff1a;记录供应商信息和…

网页版五子棋——用户模块(服务器开发)

前一篇文章&#xff1a;网页版五子棋—— WebSocket 协议-CSDN博客 目录 前言 一、编写数据库代码 1.数据库设计 2.配置 MyBatis 3.创建实体类 4.创建 UserMapper 二、前后端交互接口 1.登录接口 2.注册接口 3.获取用户信息 三、服务器开发 1.代码编写 2.测试后端…

A day a tweet(seventeen)——Visualize Convolution Neural Network!

a.形象化地CNNs visually explained! . .CNN(Convolution Neural Network) 卷积神经网络 a.不可思议的,难以置信的 v.使形象化CNN explainer is an incredible interactive tool to visualize the internal workings of a CNN. n.解释器;讲解员 …

将vscode的终端改为cygwin terminal

现在终端是默认的power shell&#xff0c;没有显示cygwin 接下来选择默认配置文件 找到cygwin的选项即可 然后提示可能不安全什么的&#xff0c;点是&#xff0c;就有了

大语言模型训练的全过程:预训练、微调、RLHF

一、 大语言模型的训练过程 预训练阶段&#xff1a;PT&#xff08;Pre training&#xff09;。使用公开数据经过预训练得到预训练模型&#xff0c;预训练模型具备语言的初步理解&#xff1b;训练周期比较长&#xff1b;微调阶段1&#xff1a;SFT&#xff08;指令微调/有监督微调…

《AI在企业战略中的关键地位:以微软和阿里为例》

内容概要 在当今商业环境中&#xff0c;人工智能&#xff08;AI&#xff09;的影响力如滔滔洪水&#xff0c;愈演愈烈。文章将揭示AI在企业战略中的崛起&#xff0c;尤其以微软和阿里巴巴为代表的企业&#xff0c;这两家科技巨头通过不同方式&#xff0c;将智能技术融入其核心…

aspose如何获取PPT放映页“切换”的“持续时间”值

文章目录 项目场景问题描述问题1&#xff1a;从官方文档和资料查阅发现并没有对切换的持续时间进行处理的方法问题2&#xff1a;aspose的依赖包中&#xff0c;所有的关键对象都进行了混淆处理 解决方案1、找到ppt切换的持续时间对应的混淆对象中的字段2、获取ppt切换的持续时间…

Linux挖矿病毒(kswapd0进程使cpu爆满)

一、摘要 事情起因:有台测试服务器很久没用了&#xff0c;突然监控到CPU飙到了95以上&#xff0c;并且阿里云服务器厂商还发送了通知消息&#xff0c;【阿里云】尊敬的xxh: 经检测您的阿里云服务&#xff08;ECS实例&#xff09;i-xxx存在挖矿活动。因此很明确服务器中挖矿病毒…

线性代数:Matrix2x2和Matrix3x3

今天整理自己的框架代码&#xff0c;将Matrix2x2和Matrix3x3给扩展了一下&#xff0c;发现网上unity数学计算相关挺少的&#xff0c;所以记录一下。 首先扩展Matrix2x2&#xff1a; using System.Collections; using System.Collections.Generic; using Unity.Mathemati…

CLIP论文CLIP 改进工作串讲

文章目录 CLIPViLTCLIP 改进工作串讲Lseg&#xff08;Language -driven semantic segmentation)Group ViT&#xff08;Semantic Segmentation Emerges from Text Supervision&#xff09;ViLDGLIP_V1/V2&#xff08;Ground Language-Image Pre-train&#xff09;CLIP PassoCLIP…