一些重要的
JWT
简介
全称: JSON Web Token(https://jwt.io/)
定义了一种简洁的、自包含的格式,用于通信双方以 json 数据格式安全的传输信息。
组成:
第一部分: Header (头) ,记录令牌类型、签名算法等。例如: {"alg":"HS256","type":"JWT"}
第二部分: Payload (有效载荷) ,携带一些自定义信息、默认信息等。例如: {"id":"1","username":"Tom"}
第三部分: Signature (签名) ,防止 Token 被篡改、确保安全性。将 header、payload,并加入指定秘钥,通过指定签名算法计算而来。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.JTdCJTIybmFtZSUyMiUzQSUyMlRvcSUyMiUyQyUyMm1hdCUyMiUzQTE1MTYyMzkwMjIlNQ=.Sf1KxwRJ5MeKKF2QT4fwpMeJf...
Base64: 是一种基于 64 个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式。
如果篡改了头部和载荷部分的数据,那么验证失败
如果秘钥改了,验证失败
注意事项
JWT 校验时使用的签名秘钥,必须和生成 JWT 令牌时使用的秘钥是配套的。
如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或 失效了,令牌非法。
Spring Validation对注册接口的参数校验
- 引入Spring Validation 起步依赖

-
在参数前面添加@Pattern注解
-
在Controller类上添加@Validated注解

- 在全局异常处理器中处理参数校验失败的异常

ThreadLocal
提供线程局部变量
用来存取数据: set()/get()
使用ThreadLocal存储的数据,线程安全
用完记得调用remove方法释放
public class ThreadLocalTest {
@Test
public void testThreadLocalSetAndGet() {
//同一个ThreadLocal对象为两个线程提供供存储线程的空间,达到线程隔离
//提供一个ThreadLocal对象
ThreadLocal tl = new ThreadLocal();
//开启两个线程
new Thread(() -> {
tl.set("萧炎");
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
}, "蓝色").start();
new Thread(() -> {
tl.set("药尘");
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
System.out.println(Thread.currentThread().getName() + ": " + tl.get());
}, "绿色").start();
}
}
分组校验
更新文章分类和添加文章分类都@Validated了同一个规则
但添加时不需要传入id,更新时需要传入id,导致id校验规则矛盾
分组校验
把校验项进行归类分组,在完成不同的功能的时候,校验指定组中的校验项
-
定义分组
-
定义校验项时指定归属的分组
-
校验时指定要校验的分组
public class Category {
public interface Add {
}
public interface Update {
}
}
public class Category {
@NotNull(groups = Update.class)
private Integer id;//主键ID
@NotEmpty(groups = {Add.class, Update.class})
@Pattern(regexp = "^\\S{1,10}$", groups = {Add.class, Update.class})
private String categoryName;//分类名称
@NotEmpty(groups = {Add.class, Update.class})
@Pattern(regexp = "^\\S{1,10}$", groups = {Add.class, Update.class})
private String categoryAlias;//分类别名
}
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category) {
categoryService.update(category);
return Result.success();
}
注意事项
定义校验项时如果没有指定分组,则属于Default分组,分组可以继承
public class Category {
public interface Add extends Default{
}
public interface Update extends Default{
}
}
封装到实体类对象中的参数如何校验
注解 作用
NotNull 值不能为 null
NotEmpty 值不能为 null, 并且内容不为空
Email 满足邮箱格式
参数校验
1.在实体类中添加注解
@Data
public class User {
@NotNull
private Integer id;//主键ID
private String username;//用户名
private String password;//密码
@NotEmpty
@Pattern(regexp = "^\\S{1,10}$")
private String nickname;//昵称
@NotEmpty
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
2.在具体使用实体类的前面加上@Validated注解
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success();
}
自定义校验
已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验 (自定义校验注解)
1.自定义注解 State
2.创建自定义校验数据的类 StateValidation
实现 ConstraintValidator 接口
3.在需要校验的地方使用自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StateValidation.class)
public @interface State {
String message() default "文章状态只能是:已发布或者草稿";
Class[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateValidation implements ConstraintValidator<State, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
if (value.equals("已发布") || value.equals("草稿")) {
return true;
}
return false;
}
}
@Data
public class Article {
private Integer id;//主键ID
private String title;//文章标题
private String content;//文章内容
private String coverImg;//封面图像
@State
private String state;//发布状态 已发布|草稿
private Integer categoryId;//文章分类id
private Integer createUser;//创建人ID
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
令牌主动失效机制
登录的问题:
用户两次登录后会生成新旧两个令牌,此时旧的不应该生效
要使旧的失效:
令牌主动失效机制
登录成功后,给浏览器响应令牌的同时,把该令牌存储到redis中
LoginInterceptor拦截器中,需要验证浏览器携带的令牌,并同时需要获取到redis中存储的与之相同的令牌
当用户修改密码成功后,删除redis中存储的旧令牌
SpringBoot 集成 redis
1导入 spring-boot-starter-data-redis 起步依赖
2.在 yml 配置文件中,配置 redis 连接信息
3.调用 API (StringRedisTemplate) 完成字符串的存取操作
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml
spring:
data:
redis:
host: localhost
port: 6379
@Test
public void testStringSet(){
stringRedisTemplate.opsForValue().set("username","如花");
}
@Test
public void testStringGet(){
stringRedisTemplate.opsForValue().get("username");
}
SpringBoot多环境开发
开发、测试、生产三种环境下配置信息是不同的
多环境开发 - Profiles
SpringBoot 提供的 Profiles 可以用来隔离应用程序配置的各个部分,并在特定环境下指定部分配置生效
如何分隔不同环境的配置?
使用三个横杠 ---
如何指定哪些配置属于哪个环境?
spring:
config:
activate:
on-profile: 环境名称
如何指定哪个环境的配置生效?
spring:
profiles:
active: 环境名称