如何通过 JWT 来解决登录认证问题

1. 问题引入

在登录功能的实现中

传统思路:

  1. 登录页面时把用户名和密码提交给服务器
  2. 服务器验证用户名和密码,并把检验结果返回给后端
  3. 如果密码正确,则在服务器端创建 session,通过 cookie 把 session id 返回给浏览器

但是正常情况下一个 web 应用是部署到多个服务器上的,通过 Nginx 等进行负载均衡,此时就可能出现这样的情况:用户登录请求之后把 session 存储在了第一台服务器上,但是后续的请求操作,例如查询等,就可能会转发到第二台服务器上,但是第二台服务器没有存储该用户的 session,就会让用户重新登录,这肯定是不合理的

解决方案:

  1. 对于服务端来说,上述出现的问题是由于 session 是默认存储在内存中的,服务器重启之后,session 就丢失了,如果把 session 存储在 Redis 中,那么就能共同访问,并且不丢失数据。
  2. 第二种方案就是引入 token,也就是令牌,用户登录之后,服务器对账号和密码进行验证,验证通过就生成一个令牌,并返回给客户端,客户端收到令牌之后,把令牌存储起来,之后再发起其他请求就带着令牌,处理请求的服务器校验令牌是否有效即可

引入令牌之后就解决了集群环境下的认证问题,并且减轻了服务器的存储压力,令牌由客户端存储,服务器只负责生成和校验

2. JWT 的介绍

官网:jwt.io/

JWT 令牌本身是一个字符串,包括头部,载荷,签名三部分,将信息作为 JSON 对象进行传输

头部:包括令牌的类型和使用的哈希算法

载荷:存储的有效信息,为自定义内容

签名:用于防止 JWT 内容被篡改(并不是防止被解析),只要被篡改,令牌就会失效

3. JWT 的使用

首先需要导入对应的依赖:

xml 复制代码
<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>

接下来就可以测试生成 token 了

arduino 复制代码
//生成token
@Test
public void getToken() {
    String secret = "abcdefghijklmnopqrstuvwxyz";
    //设置key,用于签名
    Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
    //载荷
    Map<String, Object> map = new HashMap<>();
    map.put("name", "zhangsan");
    map.put("id", 1);
    //生成token
    String compact = Jwts.builder().setClaims(map).signWith(key).compact();
    System.out.println(compact);
}

此时报出了一个错误,要求使用提供的方法来生成 key

接下来看怎么生成 key

java 复制代码
@Test
public void genKey(){
    //生成key
    SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    //转化为String类型
    String encode = Encoders.BASE64.encode(secretKey.getEncoded());
    System.out.println(encode);
}

生成之后就可以替换掉原来自定义的字符串了,再去生成 token

在官网中也是可以校验成功的

接下来看怎么通过方法来进行 token 的校验:

ini 复制代码
//校验token
@Test
public void parseToken(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6MX0.xllreml0yt9aQDXSQe0ngQb45VpV5843rOEKdDQ4QCk";
    //JWT解析器
    JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
    //对创建好的token进行解析
    Object body = build.parse(token).getBody();
    System.out.println(body);
}

如果说签名错了就无法正确解析了:

这就可以通过 try- catch 进行逻辑处理了:

根据这些就可以写一个工具类,服务端就可以直接调用了

typescript 复制代码
@Slf4j
public class JwtUtil {
    //设置key,用于签名
    private final static String secret = "WHMgtn1tTrIxc00ys17ukp65bf2KZ0wrihyqynY18F8=sssss";
    private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
    private final static long expiration = 24 * 60 * 60 * 1000;

    //生成token
    public static String getToken(Map<String, Object> map) {
        return Jwts.builder()
        .setClaims(map)
        .setExpiration(new Date(System.currentTimeMillis() + expiration))//设置过期时间
        .setIssuedAt(new Date())  //设置签发日期
        .signWith(key)
        .compact();
    }

    //校验token
    public static Claims parseToken(String token) {
        if (!StringUtils.hasLength(token)) {
            return null;
        }
        //JWT解析器
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        //对创建好的token进行解析
        Claims body = null;
        try {
            body = build.parseClaimsJws(token).getBody();
            return body;
        } catch (SignatureException e) {
            log.error("token非法...e{}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("token过期... e{}", e.getMessage());
        } catch (Exception e) {
            log.error("token解析失败,e{}", e.getMessage());
        }
        return body;
    }
}
相关推荐
Marktowin6 小时前
Mybatis-Plus更新操作时的一个坑
java·后端
赵文宇6 小时前
CNCF Dragonfly 毕业啦!基于P2P的镜像和文件分发系统快速入门,在线体验
后端
程序员爱钓鱼6 小时前
Node.js 编程实战:即时聊天应用 —— WebSocket 实现实时通信
前端·后端·node.js
Libby博仙7 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸7 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长7 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊8 小时前
TCP的自我介绍
后端
小周在成长8 小时前
MyBatis 动态SQL学习
后端
子非鱼9218 小时前
SpringBoot快速上手
java·spring boot·后端