JWT与传统Session的比较(内有JWT整合Springboot的demo)

JWT与传统Session的比较(内有JWT整合Springboot的demo)

1.基于传统的Session认证

http协议本身是一种无状态的协议,意味着用户向应用提供了用户名和密码来进行用户名、密码来进行用户认证,但是当下一次请求时,用户还是需要再一次进行用户认证才行。 因为根据http协议,服务器并不能知道是那个用户发出的请求,所以为了让我们的应用能识别是那个用户发出的请求,我们只能再服务器存储一份用户登录的信息。这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能够识别来自于那个用于了,这就是基于session认证。

步骤:
  1. 浏览器发送请求携带用户名,密码等信息
  2. 服务器对用户名密码进行核对,如果核对成功,保存一个session对象(需要设置有效时长),并且将该session ID响应给浏览器。
  3. 浏览器拿到sessionID 后设置进cokkie,下回浏览器 访问 服务器后就可以直接通过获取cokkie里面的session ID信息,进行验证。
问题:
  1. 每个用户经过应用认证之后,我们的应用都要再服务端做一次记录,以方便用户下次请求的鉴别,通常而言,session都是保存再内存中,而随着认证用户的增多,服务端的开销会明显增大。
  2. 用户认证过后,服务端做认证记录上,如果认证的记录被保存在内存中的话,这意味着用户下次请求时同一台服务器上才能拿到授权的资源,这样在分布式的应用上,相应限制了负载均衡的能力,这也意味着限制了应用的扩展能力。
  3. 因为时基于cookie来进行的用户识别,cookie如果被截获,用户就容易受到跨站请求伪造的攻击。
  4. 在前后端分离系统中比较痛苦,在某些分离项目场景中,前端会通过nginx来进行发送请求到后端网关,网关再将信息发送给服务器,如果这个时候服务器是集群部署,还需要使用分布式共享session来进行session的传递,

2.基于JWT认证

1.流程图
2.步骤

1.前端通过Web表单将自己的用户名和密码发送到后端的接口,这一过程一般是一个HTTP POST请求。

2.后端核实了用户名密码后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码进行拼接后签名,形成一个JWT(token)。形成的JWT就是一个形同111.zzz.xxx的字符如:head.payload.singurater。

3.后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存再localStorage或者sessionStorage上,退出登录时前端删除保存的JWT即可。

4.前端再每次请求时将JWT放入 Header中的 Authorization位。(解决XSS和XSRF问题)

5.后端检查是否存在,如存在验证JWT的有效性。例如:检查签名是否正确;检查Token的接收方是否是自己,检查token是否过期。

6.验证通过后 后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

3.优势
  1. 简洁: 可以通过URL,POST参数或者再HTTP header发送, 因为数据小,传输速度也很快.
  2. 自包含: 负载中包含了所有用户所需要的信息,避免了多次查询数据库。
  3. 因为Token是以JSON加密的形式保存再客户端的,所以JWT是跨语言的,原则上任何Web形式都支持。
  4. 不需要再服务端保存会话信息,特别适用于分布式微服务。
4.JWT结构
1.令牌组成:

​ (1)标头(Header)

​ (2)有效载荷(Payload)

​ (3)签名(Signature)

​ 因此: JWT通常情况下如下所示:xxx.yyyy.zzzz Header.Payload.Signatur

2.Header

​ 标头通常由俩部分组成: 令牌的类型(即JWT)和所使用的签名算法,例如JMAC, SHA256或RSA,它会使用Base64编码自称JWT结构的第一部分。

​ 注意: Base64是一种编码,也就是说,它是可以被翻译回原来的样子的。它并不是一种加密过程。

3.Payload

​ 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的说明。同样的它会使用Base64编码组成JWT结构的第二部分。

4.Signature

​ 签名, 负载俩部分都是用Base64进行编码的,即前端可以通过解码知道里面的信息。Signature需要使用编码后的header以及payload以及我们提供的一个密钥,然后使用header中指定的签名算法(默认HS256)进行签名。签名作用是保证JWT没有被篡改过。

​ 例如: HMACSHA256(base64UrlEncode(header) + " ." + base64UrlEncode(payload),secret);

5.签名目的

​ 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改,如果有人对头部信息或者负载信息通过base64解码后,然后改掉头部或者负载里面的信息,再加上原来的签名组成新的JWT,服务器就会判断出不一样。

6.JWT简单使用demo

​ (1)生成一个token信息

java 复制代码
       	//定义一个过期的时间
		Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND,80);
	//可以不用设置header信息,因为头信息中就是放的算法的类型,默认的就是HMAC256,不设置就是默认
        String sign = JWT.create()
            	//往playload负载中假如信息(该信息不能包含密码等私密信息,因为可以被解密)
                .withClaim("username", "李四")
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256("!#HJK$#@"));
        System.out.println(sign);

​ (2) 验证一个token信息

java 复制代码
        //创建验证对象
        JWTVerifier require = JWT.require(Algorithm.HMAC256("!#HJK$#@")).build();
        DecodedJWT verify = require.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTM4OTQzMjAsInVzZXJuYW1lIjoi5p2O5ZubIn0.rlYRZOebDZHeohShR4mNA0XYVkZ1izFCJRyf00S6lMo");
        System.out.println(verify.getClaim("username"));
7.JWT工具类
java 复制代码
package com.chenze.bootmybatis.untis;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import javax.swing.*;
import java.util.Calendar;
import java.util.Map;

public class JWTUtils {

    public static final String SIGN = "@#fh$##@$fhjg#%$f&%";


    /**
     *  生成token
     */
    public static String getToken(Map<String,Object> map){
        Calendar instance = Calendar.getInstance();
        instance.set(Calendar.DATE, 7);
        JWTCreator.Builder builder = JWT.create();
        builder.withPayload(map);
//        map.forEach((k,v)->{
//            builder.withClaim(k, v);
//        });
        String token = builder.sign(Algorithm.HMAC256(SIGN));
        return token;
    }
    /**
     *  验证token  验证合法性
     */
    public static DecodedJWT verifyToken(String token){
        DecodedJWT verify= JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return verify;
    }
}
8.JWT整合springboot案例

​ 一般在我们平时使用的时候,为了防止代码重复,我们可以定义一个拦截器,用于拦截我们规定的请求去完成我们相关的操作

(1)拦截器

java 复制代码
package com.chenze.bootmybatis.Interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.chenze.bootmybatis.untis.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.SignatureException;
import java.util.HashMap;

import static java.sql.DriverManager.println;
//实现HandlerInterceptor
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader("token");
        HashMap<Object, Object> returnMap = new HashMap<>();
        try {
            JWTUtils.verifyToken(authorization);
            return true;//验证通过就放行
        } catch (SignatureVerificationException e) {
            returnMap.put("msg","无效签名");
            e.printStackTrace();
        }catch (TokenExpiredException e) {
            returnMap.put("msg","token过期");
            e.printStackTrace();
        }catch (AlgorithmMismatchException e) {
            returnMap.put("msg","token算法不一致");
            e.printStackTrace();
        }catch (Exception e) {
            returnMap.put("msg","token无效");
            e.printStackTrace();
        }
        returnMap.put("state",false);
        response.setContentType("application/json; charset=UTF-8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            //将map对象转成json字符串通过响应流返回
            writer.println(new ObjectMapper().writeValueAsString(returnMap));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            writer.close();
        }
        return false;
    }
}

但是需要注意的是,上面的拦截器只是定义了,但是并没有生效,所以我们需要将它加入到spring容器中

(2)将拦截器交给spring容器管理

java 复制代码
package com.chenze.bootmybatis.config;

import com.chenze.bootmybatis.Interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//加上该注解,让spring扫描到 
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**") //所有路径都拦截
                .excludePathPatterns("/user/**"); //放行调改路径下的接口

    }
}

(3)controller代码

java 复制代码
package com.chenze.bootmybatis.controller;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.chenze.bootmybatis.pojo.User;
import com.chenze.bootmybatis.service.UserService;
import com.chenze.bootmybatis.untis.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@Component
public class UserJwtController {

    @Autowired
    UserService userService;


    @RequestMapping("/user/login")
    public Map login(@RequestBody User user){
        User loginUser = userService.findUserById(user);
        HashMap<String, Object> map = new HashMap<>();
        map.put("name",loginUser.getName());
        map.put("id",loginUser.getId());
        String token = JWTUtils.getToken(map);
        HashMap<Object, Object> returnMap = new HashMap<>();
        returnMap.put("state","成功");
        returnMap.put("code",200);
        returnMap.put("token",token);
        return returnMap;
    }


    @RequestMapping("/jwt/test")
    public Map test(){
        System.out.println("进入了test方法");
        HashMap<Object, Object> map = new HashMap<>();
        map.put("state","成功");
        map.put("code",200);
        map.put("msg","完成了test方法");
        return map;
    }

}

(4)service

java 复制代码
package com.chenze.bootmybatis.service;

import com.chenze.bootmybatis.pojo.User;

public interface UserService {

    User findUserById(User user);
}

(5)UserServiceImpl实现

java 复制代码
package com.chenze.bootmybatis.service.impl;

import com.chenze.bootmybatis.mapper.UserMapper;
import com.chenze.bootmybatis.pojo.User;
import com.chenze.bootmybatis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;


    @Override
    public User findUserById(User user) {
        User loginUser = userMapper.findByUser(user.getName(),user.password);
        if(ObjectUtils.isEmpty(loginUser)){
            return null;
        }
        return loginUser;
    }
}

(6)mapper

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chenze.bootmybatis.mapper.UserMapper">


    <insert id="insertUser" parameterType="com.chenze.bootmybatis.pojo.User">
        insert into USER(id,name) values(#{id},#{name})
    </insert>
    <select id="findByUser" resultType="com.chenze.bootmybatis.pojo.User">
        select * from USER where name=#{name} and password=#{password}
    </select>

</mapper>
相关推荐
野犬寒鸦1 分钟前
SAP后端实习开发面试:操作系统与网络核心考点及Linux与Redis
java·服务器·网络·后端·面试
ServBay3 分钟前
代码减半,10分钟彻底告别 Java 开发旧习
java·后端
Soofjan4 分钟前
Go Map SwissTable Rehash 扩容与再哈希(源码笔记 6)
后端
future02106 分钟前
Spring 核心原理学习路线(完结汇总):7 篇文章串起 IOC、AOP、事务与 Boot
后端·学习·spring
阿杆10 分钟前
五分钟配好向日葵 MCP,让 AI 替你远程安装 OpenClaw!
后端·aigc·mcp
Soofjan32 分钟前
Go Map SwissTable GetMap 查找流程(源码笔记 3)
后端
Soofjan36 分钟前
Go Map SwissTable ModMap 插入与更新(源码笔记 4)
后端
qq_12498707531 小时前
基于springboot的微信小程序的博物馆文创系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·spring·微信小程序·毕业设计·计算机毕设
小杍随笔2 小时前
【Rust模块化进阶:深入解析mod.rs的用法与现代实践(1.94版本)】
开发语言·后端·rust
爱分享的鱼鱼2 小时前
在 Spring Boot + MyBatis 项目中为查询接口添加入参查询字段支持——以房费台账 paySource 为例
后端