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

发布于:2024-12-08 ⋅ 阅读:(94) ⋅ 点赞:(0)

1. 问题引入

在登录功能的实现中

传统思路:

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

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

解决方案:

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

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

2. JWT 的介绍

官网:JSON Web Tokens - jwt.io

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

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

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

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

3. JWT 的使用

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

<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 了

//生成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

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

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

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

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

//校验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 进行逻辑处理了:

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

@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;
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到