首页 > 其他分享 >详解JWT

详解JWT

时间:2023-04-13 14:48:52浏览次数:27  
标签:return String jwt JWT 用户 token 详解 import

登录验证方案

session认证

众所周知,http 协议本身是无状态的协议,那就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过 http 协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至 session ),然后在认证成功后返回 cookie 值传递给浏览器,那么用户在下一次请求时就可以带上 cookie 值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。这就是传统的 session 认证方式。

session 认证的缺点其实很明显,由于 session 是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。于是乎为了解决 session 共享的问题,又引入了 redis,接着往下看。

token认证

这种方式跟 session 的方式流程差不多,不同的地方在于保存的是一个 token 值到 redis,token 一般是一串随机的字符(比如UUID),value 一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上 token 在请求头,后端接收到token 则根据 token 查一下 redis 是否存在,如果存在则表示用户已认证,如果 token 不存在则跳到登录界面让用户重新登录,登录成功后返回一个 token 值给客户端。

优点是多台服务器都是使用 redis 来存取 token,不存在不共享的问题,所以容易扩展。缺点是每次请求都需要查一下redis,会造成 redis 的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个 token 在 redis,也会消耗 redis 的存储空间。

 

那能不能不存储token了?其实我们只是验证token的合法性,只要能验证这个token是我们签发的,就可以不存储。看看下面jwt的方案。

 

1、jwt是什么

   jwt是json web token的缩写,常用于登录身份校验,它将用户信息加密保存到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。

 

2、jwt验证过程

  1. 用户首次登录,发送用户名和密码请求服务端
  2. 服务端接收到用户名和密码,去数据库校验用户合法性,合法就会生成一个token字符串给前端
  3. 前端收到token后,把token保存到自己本地(可以是cookie或者localstorage)
  4. 前端每次发送请求的时候,都带上token
  5. 后端拦截器,每次都先校验下token的合法性,因为token是后端加密签发的,中间变化了,校验不通过
  6. 返回给前端结果,如果通过就返回接口数据,不通过跳转到登录页登录。

3、jwt组成

  一个jwt生成后的token是下面这样的,分别有三部分组成,由"."分隔连接而成。

  • Header 
  • Payload
  • Signature

每个组成部分的数据。

Header

header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等),然后对这个部分进行base64生成第一部分字符串。

{
    'alg': "HS256",
    'typ': "JWT"
}

 

PayLoad

是载荷,承载用户基本信息,可以自定义数据,正常是保存用户的用户id,用户名等信息

例如:

{
    "userId":"121",
    "userName":"fwf"
}

然后对这部分base64,生成第二个字符串。

Signature 

  为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可

      HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

  算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

 

下面这张图就可以看出header和payload,所以载荷里最好不要存敏感信息,很容易解析出原数据

4、jwt的实现

  引入jar包,不需要重复造轮子了

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

   jwt工具类

  

package com.als.api.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

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

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/4/4
 */
public class JwtUtil {

    private static String header = "Authorization";
    private static String secret = "bda8bb94fb3746a0bd5083bb0b0e616d";
    public static final String TOKEN_PREFIX = "Bearer ";
    /**
     * 创建令牌
     */
    public static String createToken(String userId, String userName) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("userName", userName);
        claims.put("expireTime", System.currentTimeMillis());
        return createJwt(claims);
    }

    /**
     * 生成令牌
     */
    public static String createJwt(Map<String, Object> claims) {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    public static Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {

        }
        return null;
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    public static String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (!StringUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)) {
            token = token.replace(TOKEN_PREFIX, "");
        }
        return token;
    }

    //刷新token把CLAIM_KEY_CREATED刷新了
    public static String refreshToken(String token){
        String refreshedToken;
        try {
            final Claims claims = parseToken(token);
            claims.put("expireTime", System.currentTimeMillis());
            refreshedToken = createJwt(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

}

配置拦截器

package com.als.api.filter;

import com.als.api.exception.ApiException;
import com.als.api.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/4/4
 */
public class JwtInterceptor implements HandlerInterceptor {

    @Value("${crabc.token.expireTime:36000}")
    private long expireTime;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getMethod().toUpperCase().equals("OPTIONS")){
            return true; // 通过OPTION请求
        }
        String token = JwtUtil.getToken(request);
        if (token == null) {
            throw new ApiException(401, "用户未登录");
        }
        Claims claims = JwtUtil.parseToken(token);
        if (claims == null) {
            throw new ApiException(401, "用户未登录");
        }
        long nowTime = System.currentTimeMillis();
        Long expire = Long.parseLong(claims.get("expireTime").toString());
        long time = nowTime - expire;
        if (time/1000 > expireTime) {
            throw new ApiException(401, "登录失效,请重新登录");
        }
        return true;
    }
}

用户登录

package com.als.api.controller;

import com.als.api.dto.ApiUserDTO;
import com.als.api.response.Result;
import com.als.api.util.JwtUtil;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author xiangwei.li
 * @version 1.0.0
 * @date 2023/4/4
 */
@RestController
@RequestMapping("/api/user")
public class ApiUserController {

    /**
     * 登录
     *
     * @param userId
     * @param pwd
     * @return
     */
    @PostMapping("/login")
    public Result login(String userId, String pwd) throws Exception {
        //模拟用户登录,这里访问数据库
        ApiUserDTO userInfo = new ApiUserDTO();
        userInfo.setUserId(userId);
        userInfo.setPwd(pwd);
        userInfo.setUserName("jack");
        if (userInfo == null) {
            return Result.error("账号或密码错误!");
        }

        Map<String, Object> user = new HashMap<>();
        //签发token
        String token = JwtUtil.createToken(userInfo.getUserId(), userInfo.getUserName());
        user.put("expires", 3600);
        user.put("access_token", token);
        user.put("refresh_token", UUID.randomUUID().toString().replace("-", ""));
        return Result.success(user);
    }
}

 

 最后讲讲 JWT 的缺点,因为任何技术都不是完美的,所以我们得用辩证思维去看待任何一项技术。

  • 安全性没法保证,所以 jwt 里不能存储敏感数据。因为 jwt 的 payload 并没有加密,只是用 Base64 编码而已。
  • 无法中途废弃。因为一旦签发了一个 jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的 jwt 过期后重新签发新的 jwt。
  • 续签问题。当签发的 jwt 保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当 jwt有效期到了就会导致用户需要重新登录。那么怎么为 jwt 续签呢?最简单粗暴就是每次签发新的 jwt,但是由于过于暴力,会影响性能。如果要优雅一点,又要引入 Redis 解决,但是这又把无状态的 jw t硬生生变成了有状态的,违背了初衷

标签:return,String,jwt,JWT,用户,token,详解,import
From: https://www.cnblogs.com/jack1990/p/17314731.html

相关文章

  • HTTP缓存机制详解
    目录什么是HTTP缓存缓存策略强制缓存ExpiresCache-Control对比缓存Last-Modified/If-Modified-SinceEtag/If-None-Match(优先级高于Last-Modified/If-Modified-Since)优先级Nginx缓存配置默认设置显示设置浏览器缓存策略总结什么是HTTP缓存HTTP缓存可以说是HTTP性......
  • oracle中if/else的三种实现方式详解
    oracle中if/else的三种实现方式详解1、标准sql规范1、单个IFIFv=...THENENDIF;2、IF...ELSEIFv=...THENELSEt....;ENDIF;3、多个IFIFv=...THENELSIFv=...THENt...;ENDIFL注意:多个IF的是'ELSIF'不是'ELSEIF'2、decode函数DECODE(VALUE,IF......
  • 2.1万字,30张图详解操作系统常见面试题(收藏版)
    耗时两周,新版的操作系统常见知识点/问题总结总算搞完了,手绘了30多张图。大家可以用来复习操作系统或者准备操作系统面试。对于大部分公司的面试来说基本够用了,不过,像腾讯、字节这种大厂的面试还是要适当深入一些。这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如用......
  • AlertDialog(对话框)详解
    本节继续给大家带来是显示提示信息的第三个控件AlertDialog(对话框),同时它也是其他Dialog的的父类!比如ProgressDialog,TimePickerDialog等,而AlertDialog的父类是:Dialog!另外,不像前面学习的Toast和Notification,AlertDialog并不能直接new出来,如果你打开AlertDialog的源码,会发现构造方法......
  • JDBC详解(韩顺平教程)
    JDBC一、原理示意图二、前提步骤IDEA导入MySQL的jdbc驱动,并操作数据库-打点-博客园(cnblogs.com)三、JDBC编写步骤:用法1:packageHsp.JDBC;​importcom.mysql.jdbc.Driver;importjava.sql.Connection;importjava.sql.SQLException;importjava.sql.Statement;......
  • C# Byte数组转化String详解(c# byte转化为string)
    C#Byte数组转化String详解(c#byte转化为string)原文链接:https://www.zhiu.cn/148955.htmlC#编程过程中将Byte数组转化String是咱们常常碰到的问题,那么怎么处理C#Byte数组转化String呢?那么咱们来看看详细的涉及到的办法以及关于怎么处理C#Byte数组转化String的评论。FCL得许多......
  • JavaWeb之Servlet详解(以及浏览器调用 Servlet 流程分析图)
    Servlet1.什么是ServletServlet(java服务器小程序)他是由服务器端调用和执行的(一句话:是Tomcat解析和执行)他是用java语言编写的,本质就是Java类他是按照Servlet规范开发的(除了tomcat->Servletweblogic->Servlet)功能强大,可以完成几乎所有的网站功能2.开发......
  • VUE中的插槽使用详解
     <!--什么是插槽?  *插槽就是子组件中的提供给父组件使用的一个占位符,插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制。  一般用slot标签来表示插槽要渲染的位置,slot的用法可以分为三类,分别是默认插槽、具名插槽、作用域插槽 ......
  • python关于*args所能接收的参数、关于**kwargs所接收的参数详解
    1#!/usr/bin/envpython2#-*-coding:utf8-*-3#python-day32-20170110:456#关于*args所能接收的参数78#这种接收的是位置参数,可变长9deffunc1(*args):10print(args,type(args))1112#传入位置参数可以被args所接收,以元组的形式来保存......
  • Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
    Mat矩阵点乘——A*BOpencv重载了运算符“*”,姑且称之为Mat矩阵“点乘”,其中一个重载声明为:CV_EXPORTSMatExproperator*(constMat&a,constMat&b);点乘说明:1. A*B是以数学运算中矩阵相乘的方式实现的,即Mat矩阵A和B被当做纯粹的矩阵做乘法运算,这就要求A的列数等 ......