Spring最后一课,Spring原理,JWT(MD5加密),SpringBoot集成Kaptcha,Bean的作用域Bean的六大作用域单例:说明获取到的对象是同一个。从Context中多次

目录

Bean的作用域

Bean的六大作用域

单例:说明获取到的对象是同一个。从Context中多次获取对象,得到的都是同一个这种行为模式,就称为Bean的作用域

多例:context中获取的对象,是只要执行这个函数,就会重新从context中获取,那么也就会重新创建一个对象,但是属性注入的对象是不变的​编辑

request 当我们使用request的时候,说明一件事情,我们最后底层都是引用的是那一个对象,我们可以看到,每次发送一个请求,都会生成一个对象

Session一次会话中,无论你发多少次申请,都是一个情况。​编辑

application

加密

加盐

为什么要用令牌JWT实现登录

JWT的使用方法

在这里有个小疑问: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 就是这种方案的一个代表

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狗哥不是甜妹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值