JWT令牌技术

在外面实现登入接口的时候,传统的方法是在用户登入界面,把用户名的账号密码提交给服务器,然后通过服务器端验证账号密码是否正确.

如果密码正确,则在服务器端创建session,通过cookie将sessionId返回给浏览器.

现在就出现了一个新问题,在集群环境下无法直接使用session.

在我们现在开发的项目中,很少会有企业将项目部署在一个服务器上,这样容易发生单点故障(一个服务器挂了,整个应用访问不了).

所以在通常情况下,一个web服务会部署在多个服务器上,通过Nginx等进行负载均衡.此时来自一个用户的请求就可能被分发到不同的服务器上.

而使用session只能存在一个服务器上,就好比用户登入的时候访问服务器A,此时成功登入,在服务器A上记录了用户的session,然后下一个请求被分配到了服务器B,但是服务器B没有记录用户的session,于是就会跳转到登入页面.这就出现了问题.

为了解决这种问题,我们来介绍一个新的技术:令牌技术.

令牌技术,听起来高大上,本质上就是一个字符串,用来作为用户的身份标识,类似于身份证号.

要用到令牌技术,服务器需要具备生成令牌 和验证令牌的能力.

用户登入之后,通过负载均衡,把请求转给了服务器A,此时服务器A进行账号密码验证,验证成功后,生成一个令牌并返回给客户端,

客户端收到令牌之后,把令牌存储起来,比如可以存储在cookie中.

用户登录成功之后,携带令牌进行其他操作,比如查询列表.此时请求转发到了服务器B,服务器B会先进行权限验证操作,验证令牌是否有效,如果有效,则说明用户已经正常登录,如果无效,则说明用户未进行登入.

令牌的优缺点:

解决了集群环境下的认证问题.

减轻了服务器的存储压力(无需在服务器中存储session)

但是需要自己实现令牌的生成传递和校验.

一.JWT组成

JWT全称:JSON Web Token

本质上是一个token,是一种紧凑的URL安全方法(URL安全:JWT 字符串可以直接放在 URL 地址栏传递,不会出现解析异常).

JWT由三部分组成,每部分中间使用(.)来分割,比如aaaa.bbbb.cccc

Header (头部) 头部包括令牌的类型(即 JWT)及使用的哈希算法(如 HMAC SHA256 或 RSA)

Payload (负载) 负载部分是存放有效信息的地方,里面是一些自定义内容。比如: {"userId":"123","userName":"zhangsan"},也可以存在 jwt 提供的现场字段,比如 exp (过期时间戳) 等.

此部分不建议存放敏感信息,因为此部分可以解码还原原始内容.

Signature (签名) 此部分用于防止 jwt 内容被篡改,确保安全性.

防止被篡改,而不是防止被解析. JWT 之所以安全,就是因为最后的签名.jwt 当中任何一个字符被篡改,整个令牌都会校验失败. 就好比我们的身份证,之所以能标识一个人的身份,是因为他不能被篡改,而不是因为内容加密.(任何人都可以看到身份证的信息,jwt 也是)

我们可以在jwt.io上看到相关介绍.

可以看到在jwt官网上看到举的例子.

二.JWT的令牌生成和校验.

1.引入JWT令牌的依赖.

二.生成令牌

我们可以先简单构建一个令牌.

构建一个map将用户ID,name传入,然后通过setClaims()方法放入载荷,然后通过compact将信息打包生成令牌.

运行代码,获得到jwt令牌

将这个字符串解析.可以看到旁边就有信息了,我们这边只设置了载荷信息,所以没有签名和令牌类型以及使用的算法类型.

此时我们加入别的信息,比如加入签名

怎么设置签名呢?

需要用到KEY,KEY需要一个长字符串通过base64编码获得.

我们可以手动创建KEY,也可以自动生成.

然后就可以生成一个令牌.

三.校验令牌

使用刚刚生成的令牌当做token,然后使用jwt的parserBuilder方法构建解析器.然后输入密钥,如果使用的是对称加密,就使用生成令牌的密钥,如果是非对称加密,则需要另一个密钥.

然后创建解析器.创建之后我们还可以拿到里面的各种信息.

执行getBody()方法,如果没有报错,则说明令牌合法,如果报错则说明令牌非法.可以用来校验令牌.

四.用户登入

在之前我们使用session的方法判断用户是否登入.现在我们使用JWT令牌的方式来判断用户是否登入.

java 复制代码
package org.example.blog.common.utils;


import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.Map;

public class JwtUtils {
    private static final Integer DELAY = 1000 * 60 * 60 * 24;//一天有效期
    private static final String SECRET_KEY = "5sjsdiijoqfueijq2349102jjdsodmcsfasfds777397A2443";//密钥
    private static final Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8));

    public static String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setExpiration(new Date(System.currentTimeMillis() + DELAY))
                .setClaims(claims)
                .signWith(key)
                .compact();
    }

    public static Boolean check(String token){
        if(!StringUtils.hasLength(token)){
            return false;
        }
        JwtParser jwt = Jwts.parserBuilder()//构建解析器
                .setSigningKey(key)//设置密钥,需要和生成令牌的密钥一致,使用HS256对称加密
                .build();//创建解析器
        try{
            String body = jwt.parseClaimsJws(token).getSignature();
            return true;
        }catch(Exception e){
            return false;
        }

    }

}

先创建一个JwtUtils工具类负责生成令牌和检验令牌.

然后在判断用户登入的时候,当用户成功登入,就生成令牌返回给前端,然后存在客户端中,后续请求通过看请求中是否携带正确令牌来判断用户是否登入.

java 复制代码
    public UserLoginResponse SelectUser(UserInfoRequest userInfoRequest) {
        UserInfo userInfo = userInfoMapper.selectOne(new QueryWrapper<UserInfo>().lambda()
                .eq(UserInfo::getUserName, userInfoRequest.getUsername())
                .eq(UserInfo::getDeleteFlag, Constant.NOT_DELETE)
        );
        if(userInfo == null){
            throw new BlogException("用户不存在");
        }
        if(!userInfo.getPassword().equals(userInfoRequest.getPassword())){
            throw new BlogException("密码错误");
        }
        //生成令牌
        Map<String, Object> map = new HashMap<>();
        map.put("userId", userInfo.getId());
        map.put("userName", userInfo.getUserName());
        String token = JwtUtils.generateToken(map);
        //后续需要将令牌存储在客户端.后续请求时需要从令牌中获取用户信息
        return new UserLoginResponse(userInfo.getId(), token);
    }

修改登入的前端代码,在登入成功之后新增在客户端存储用户ID以及token的代码:

这样就可以在后面的请求中携带令牌,并且通过令牌判断登入状态.

相关推荐
w_zero_one1 小时前
动态规划(三)
java·算法·动态规划
木棉软糖1 小时前
JDBC 读 ResultSet 读到一半,报 stream closed
java·mysql·spring
狂奔的sherry2 小时前
JRuby 多线程反引号偶发 127:GC 提前释放了 posix_spawn 的 argv
java·linux·ruby·gc·jruby
咖啡八杯2 小时前
异常体系设计:三类策略(i18n code、message、受检异常)
java·架构·代码规范
ESDWAN2 小时前
跨境网络延迟、抖动和丢包有什么区别?如何判断业务影响
开发语言·网络·计算机网络·php
Code_Solitude2 小时前
C语言分支结构练习题(华师版)
c语言·开发语言
Spatial3Ddddd2 小时前
2026空间三维重建服务商横向对比
java·服务器·人工智能
从小白到软工2 小时前
4-1 指针
c语言·开发语言·数据结构·经验分享·ai编程
Pniubi2 小时前
agent处理封号怎么解决误封问题
开发语言·python