目录
单例:说明获取到的对象是同一个。从Context中多次获取对象,得到的都是同一个这种行为模式,就称为Bean的作用域
多例:context中获取的对象,是只要执行这个函数,就会重新从context中获取,那么也就会重新创建一个对象,但是属性注入的对象是不变的编辑
request 当我们使用request的时候,说明一件事情,我们最后底层都是引用的是那一个对象,我们可以看到,每次发送一个请求,都会生成一个对象
Session一次会话中,无论你发多少次申请,都是一个情况。编辑
在这里有个小疑问:JWT和Token和Cookie和Session的区别?
Bean的作用域
我们在使用@Data的时候,data为我们提供了获得元素地址的方法
看当前图片我们能够知道,两个对象的地址一样,说明他们两个是同一个对象,那就说明什么问题呢?
从Context中多次获取对象,得到的都是同一个对象,这种行为模式,就称为bean作用域
这种叫做:单例作用域
Bean的六大作用域
User:是一个代理对象,JDK,CGLIB(实现的两种方法)(不是真正的目标类,而是一个代理类)
单例:说明获取到的对象是同一个。从Context中多次获取对象,得到的都是同一个这种行为模式,就称为Bean的作用域
多例:context中获取的对象,是只要执行这个函数,就会重新从context中获取,那么也就会重新创建一个对象,但是属性注入的对象是不变的
request 当我们使用request的时候,说明一件事情,我们最后底层都是引用的是那一个对象,我们可以看到,每次发送一个请求,都会生成一个对象
Session一次会话中,无论你发多少次申请,都是一个情况。
application
堆和栈
栈是每个线程都有自己的
堆是公共的(User user=new User() )
Bean的生命周期
加密
1.对称加密y=f(x),x=f(y),
2.非对称加密:y=f(x),加密和解密是不同的
解密:x=m(y) (公钥(是公开的)和私钥(并不公开,在客户端存在))
3.摘要算法:把一个不固定长度的字符串,通过一定算法,变成固定长度的字符串
加密算法,不是字符串的编码方式,通过md5加密
不可逆的,无法解密(只能加密,所以通常用来做校验,如果两个)
加盐
一个密码拼接一个随机字符来进行加密,这个随机字符称为"盐"
数据库存储应该是存储密文
为什么要用令牌JWT实现登录
1.服务器要实现多机部署,session不够用了
解决方式:
1.把session放在一个公共的地方,比如redis.假如服务器重启,那么这个session就会丢失
(社区拿着我们身份证,我们去社区取,后住酒店)
2.使用token(更加常见)->拥有特定含义,且不能伪造的字符串,token是存储在客户端的(相当于我们手里拿着身份证住酒店)
JWT的使用方法
引入JWT令牌的依赖
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is
preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
这个注解@deprecated表示,不建议使用,不知道什么时候就会迭代删除了。
他的报错的意思是不足24个比特位的密钥,说我们设置的不安全
Encoded-我们把生成的token放到JWT中,发现可以解析出我们写的内容(我们会发现能看到,因为token不是用来加密的,别人无法知道我们的密钥,看到信息,但是无法伪造)
下面是测试的源码以及注解
package com.example.blog.Controller;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.Test;
import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtilTest {
//1.生成token
//2.验证token
private static final long expiration=30*60*1000;
private static final String secertCode="l9VuEY5ToYQPvYN3G0Lm0Xw2tRxVLbYP3YF7JiyB/eo=";
@Test
public void genToken(){
//根据一个字符串生产一个key,相当于一个密钥
Key key= Keys.hmacShaKeyFor(Decoders.BASE64.decode(secertCode));
Map<String,Object> claim=new HashMap<>();
claim.put("id",5);
claim.put("name","lcl");
String token= Jwts.builder().setClaims(claim)
//设置有效期 -里面是毫秒数
.setExpiration(new Date(System.currentTimeMillis()+expiration))
//设置签名信息
.signWith(key)
//通过compact生成这个token
.compact();
System.out.println(token);
}
@Test
public void genKey(){
//随机生成一个key,对于一个程序来说,我们应该使用一个固定的key,而不是随机生成
SecretKey secretKey=Keys.secretKeyFor(SignatureAlgorithm.HS256);
//获取这个key信息
String key= Encoders.BASE64. encode(secretKey.getEncoded());
System.out.println(key);
}
}
secretKeyFor这个算法是一个枚举类(他的内部源码)
登录流程
(之前流程)
1.根据用户名和密码,验证密码是否正确
2.前段发回用户名密码,密码正确,存储session
3.后续访问,携带Cookie(主要是sessionId)
token方式
1.根据用户名和密码,验证密码是否正确
2.如果密码正确,后端生成token,并且返回给前端(可以放在cookie中,也可以放在本地存储)
3.后续访问时,携带token,后端校验token的合法性(token一般放在http的请求header中)
统一数据格式的返回,原因SpringMVC会注册很多转换器,JSON是一种格式
package com.example.blog.config; import com.example.blog.Common.Result; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; public class ResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter returnType, Class converterType) { //设置这个,拦截器启动 return true; } // 这段代码是一个beforeBodyWrite方法,看起来像是Spring框架中的ResponseBodyAdvice接口的一个实现,用于在HTTP响应体写入之前对响应体进行处理。 // 在这个方法中,对响应体body进行了检查和处理: // 如果body是Result类型的实例,则直接返回body,不做任何修改。 // 如果body是String类型的实例,则使用objectMapper将其转换为JSON格式的字符串,并将这个字符串包装在Result.success中返回。 // 如果body既不是Result类型也不是String类型,则将其包装在Result.success中返回。 // // 如果你去掉对String类型的判断,那么所有非Result类型的响应体都会被包装在Result.success中返回。这包括那些已经是JSON格式字符串的响应体,或者是其他类型的响应体。 // // 对于已经是JSON格式字符串的响应体,这可能会导致双重序列化的问题。因为Result.success(body)会将body(此时是一个JSON格式的字符串)再次序列化成一个JSON对象,导致返回的响应体是嵌套的JSON字符串。这通常不是你想要的结果,因为它会使前端解析响应体变得复杂,并且可能引发错误。 // // 对于其他类型的响应体,虽然它们会被正确地包装在Result.success中返回,但你可能失去了对原始响应体类型的控制。这意味着,即使你的原始响应体是一个复杂的对象或集合,它也会被序列化为一个JSON字符串,并被包装在Result对象中。这可能不是你想要的行为,特别是当你需要保持原始响应体的类型或结构时。 // // 因此,保留对String类型的判断是很重要的,它可以确保已经是JSON格式字符串的响应体不会被错误地再次序列化。 @SneakyThrows @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //这个地方发生的情况是会报一个嵌套的Result if(body instanceof Result){ return body; } 如果body是String类型的实例,则使用objectMapper将其转换为JSON格式的字符串,并将这个字符串包装在Result.success中返回。 if(body instanceof String){ return objectMapper.writeValueAsString(Result.success(body)); } return Result.success(body); } }
抄代码时候Controller层漏掉的一个东西,当时忘记了这个知识点
@RestController:@ResponseBody+@Controller
接口设计:类似于这种
功能列表
请求
/blog/getlist
响应:
code:200
msg:" ",
data: [(...)]
-
客户端使用用户名跟密码请求登录
-
服务端收到请求,去验证用户名与密码
-
验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
-
客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
-
客户端每次向服务端请求资源的时候需要带着服务端签发的 token
-
服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
有token的好处,即使服务器重启,你也无需重新登录(王者更新的时候,不需要重新登录,而是它自动加载),除非你清除浏览器缓存。
在这里有个小疑问:JWT和Token和Cookie和Session的区别?
一直在思考JWT和Token的关系,知道一个哥告诉我,JWT相当于是token的实现方式,这样其实就明白了,Cookie存储在浏览器端,Session存储在服务器中。
这种方法的扩展性不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表