JWT应用

前言:传统项目中,用户的身份认证等信息都是通过session来处理(客户端和服务端保存一个对应的sessionid,每次请求都会携带该sessionid进行逻辑处理)。但是在集群环境或者用户请求量较大的情况下,使用session会大大增加代码处理复杂度以及压力。
JWT(JSON WEB TOKEN)能解决上面涉及的问题,其实际上就是一个三部分组成的字符串,该字符串包含了头部、载荷与签名。我们可以通过JWT在客户端和服务器间进行安全可靠的信息传递。

一、三部分组成说明

(1)头部(Header)

JWT的头部是一个描述该JWT基本信息的JSON对象,比如描述其类型以及签名所用的算法等。如下,alg字段表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ字段表示令牌的类型,JWT令牌统一写为JWT。

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

将其进行Base64编码转换为字符串,该字符串即为JWT的头部

(2)载荷(Payload)

载荷是JWT的主体内容部分,其是一个包含需要传递数据(存储)的JSON对象。

JWT默认指定了如下几个基础信息(标准定义):

iss:该JWT的签发者,也就是这个“令牌”归属于哪个用户。一般是userId
exp:过期时间,也就是这个令牌什么时候失效
sub:主题,即该JWT所面向的用户
aud:用户,即接受该JWT的一方
iat:发布时间,也就是这个令牌是什么时候创建的
jti:JWT ID,通过算法生成的一个唯一标识

另外,我们也可以将自己需要保存的信息放在这里,如:

{
"name": "test",
"role": "admin"
}

将上面涉及的JSON对象进行Base64编码转换为字符串,该字符串即为JWT的Payload(载荷)。

注意:因为Base64是一种编码而不是加密方式,其可以被翻译回原来的样子,所以不能存放隐私信息(如密码之类的)。

(3)签名

将上面生成的两个编码字符串通过.号连接起来(头部.载荷),组成一个新字符串。然后将该新字符串用HS256(头部声明的加密算法)算法进行加密,加密的过程中还要使用我们提供的密钥。加密后生成了一个新字符串,该字符串即为签名。
(4)jwt字符串

最后,我们将上面的三个字符串通过.拼接起来,即形成了我们的JWT,即头部.载荷.签名。

二、代码演示

1、引入依赖:

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

2、代码(一般我们会在用户请求获取token的时候,在生成的jwt token前面加上 "Bearer ",即 "Bearer token"。然后通过响应头返回给用户,之后用户每次调用服务的restful接口的时候,都要在请求头加上该token,即请求头一般为:"Authorization":"Bearer token",下面的代码即是以这种形式操作):

import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Component
public class JwtUtil {
    //过期时间设置,这里设置过期时间为7天
    public static final long EXPIRE_TIME = 604800L;
    //服务端的秘钥,一般存放在配置文件方便修改,在任何场景都不能泄露出去
    static final String SECRET = "abcd";
    static final String TOKEN_PREFIX = "Bearer ";
    //static final String HEADER_KEY = "Authorization";
    /**
     * 通过该方法生成jwt对应的token
     * 使用Hs256算法,密钥为常量 SECRET
     * @param userId  要设置到载荷的用户数据
     * @param username  签发人信息
     * @return
     */
    public static String createJwtToken(Integer userId, String username) {
        Instant now=Instant.now();
        //为payload添加各种标准声明和私有声明
        JwtBuilder jwtBuilder = Jwts.builder()
                //另外,如果设置的数据多,可以把一些安全的数据放到map,然后使用setClaims(map)设置到载荷中来代替多个.claim()操作
                //私有声明是给builder的claim赋值,一定要先设置私有的声明,一旦写在标准的声明赋值之后,可能会覆盖标准声明
                .claim("userId",userId)
                //设置签发时间
                .setIssuedAt(Date.from(now))
                //设置过期时间
                .setExpiration(Date.from(now.plusSeconds(EXPIRE_TIME)))
                //设置签名使用的签名算法和秘钥
                .signWith(SignatureAlgorithm.HS256, SECRET)
                //代表这个JWT的拥有人
                .setSubject("username");
        return TOKEN_PREFIX + jwtBuilder.compact();
    }

    /**
     * 通过该方法将jwt字符串解析
     * @param token
     * @return
     */
    public static Claims parseJwtToken(String token){
        try
        {
            if(Objects.isNull(token)||!token.startsWith(TOKEN_PREFIX))
            {
                throw new IllegalArgumentException("illggal jwt");
            }
            token = token.replace(TOKEN_PREFIX, "");
            Claims claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(SECRET.getBytes())
                    .parseClaimsJws(token)
                    .getBody();

            //可以通过如下方法从claims获取jwt token中设置的载荷信息
            if(!Objects.isNull(claims))
            {
                claims.getSubject();
                claims.get("userId",Integer.class);
            }
            return claims;
        }
        catch (ExpiredJwtException e){
            // 如果jwt过期则会抛出这个异常。
            throw  e;
        }
    }

    /**
     * 验证该jwt token是否有效
     * @param token
     * @return
     */
    public static boolean validateJwtToken(String token) {
        if (!Objects.isNull(token)) {
            // 解析token
            try {
                Claims claims = parseJwtToken(token);
                Integer userId = claims.get("userId",Integer.class);

                boolean isExpiredToken = claims.getExpiration().before(Date.from(Instant.now().plusSeconds(EXPIRE_TIME)));
                if(Objects.isNull(userId) || isExpiredToken){
                    return false;
                }
                return true;
            } catch (ExpiredJwtException e) {
                // 如果jwt过期则会抛出这个异常。
                return false;
            }
        }else {
           return false;
        }
    }
}

三、问题

1、我们的信息是否会暴露

因为base64编码可逆,所以我们放置在载荷的信息是能够被获取的,因此不应该在载荷存放隐私数据。但可以存放一些用户身份验证以及权限方面的信息。

2、jwt怎么验证信息

jwt包含了签名字符串,该字符串是通过 头部.载荷 的字符串进行加密生成。所以服务器在接受到jwt字符串后,会对 头部.载荷 进行同样的加密算法计算,如果计算出来的字符串和jwt的签名字符串不一致则表示该jwt被修改过,将会被拒绝访问(401)。

3、有什么缺点

jwt在使用的时候,会涉及到过期时间的设置,该时间一旦设置则无法修改,所以jwt的时间续签问题是其致命缺点。

另外,在单点登录如果使用jwt,则需要防止该jwt token被别人获取并进行登录。

发布了68 篇原创文章 · 获赞 41 · 访问量 15万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览