前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security JWT

Spring Security JWT

原创
作者头像
HLee
修改2021-08-20 10:04:32
9460
修改2021-08-20 10:04:32
举报
文章被收录于专栏:房东的猫房东的猫

简介

JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

应用场景

  • 身份认证 在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。
  • 信息交换 在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。

JWT结构

JWT包含了使用.分隔的三部分:

  • Header 头部
  • Payload 负载
  • Signature 签名
代码语言:javascript
复制
其结构看起来是这样的:xxxxx.yyyyy.zzzzz

Header

在header中通常包含了两部分:token类型和采用的加密算法。

代码语言:javascript
复制
{
  "alg": "HS256",
  "typ": "JWT"
}  

对这部分内容使用Base64Url编码组成了JWT结构的第一部分。

Payload

Token的第二部分是负载,它包含了Claim, Claim是一些实体(通常指的用户)的状态和额外的元数据,有三种类型的claim: reserved、public 和 private。

  • Reserved claims: 这些claim是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用,常用的有 iss(签发者), exp(过期时间戳), sub(面向的用户), aud(接收方), iat(签发时间)
  • Public claims:根据需要定义自己的字段,注意应该避免冲突
  • Private claims:这些是自定义的字段,可以用来在双方之间交换信息
代码语言:javascript
复制
负载使用的例子:
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

上述的负载需要经过Base64Url编码后作为JWT结构的第二部分。

Signature

创建签名需要使用编码后的header和payload以及一个秘钥,使用header中指定签名算法进行签名。例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建:

代码语言:javascript
复制
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)  

签名用于验证消息的发送者以及消息是没有经过篡改的。

完整示例

Pom

代码语言:javascript
复制
<dependency>
	<groupId>commons-codec</groupId>
	<artifactId>commons-codec</artifactId>
	<version>1.12</version>
</dependency>

JWTConfig.java

代码语言:javascript
复制
package com.vue.master.config;

import com.google.gson.Gson;
import com.vue.master.constant.Constant;
import com.vue.master.domain.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Huan Lee
 * @version 1.0
 * @date 6/27/21 5:27 PM
 * @describtion 业精于勤,荒于嬉;行成于思,毁于随。
 */
public class JWTConfig {


    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        String stringKey = Constant.JWT_SECRET;

        // 本地的密码解码
        byte[] encodedKey = Base64.decodeBase64(stringKey);

        // 根据给定的字节数组使用AES加密算法构造一个密钥
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * 创建jwt
     * @param id
     * @param issuer
     * @param subject
     * @param ttlMillis
     * @return
     * @throws Exception
     */
    public String createJWT(String id, String issuer, String subject, long ttlMillis) {

        // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", "123456");
        claims.put("user_name", "admin");
        claims.put("nick_name", "X-rapido");

        // 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。
        // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        SecretKey key = generalKey();

        // 下面就是在为payload添加各种标准声明和私有声明了
        // 这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 设置Header
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                // 设置payload
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setId(id)
                // iat: jwt的签发时间
                .setIssuedAt(now)
                // issuer:jwt签发人
                .setIssuer(issuer)
                // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                .setSubject(subject)
                // 设置签名
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, key);

        // 设置过期时间
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * 解密jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String jwt) throws Exception {

        // 签名秘钥,和生成的签名的秘钥一模一样
        SecretKey key = generalKey();
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(key)
                // 设置需要解析的jwt
                .parseClaimsJws(jwt).getBody();

        System.out.println(claims.get("user_name"));
        System.out.println(claims.getId());
        return claims;
    }

    public static void main(String[] args) {

        User user = new User(1056856191L, "admin", "admin", "");
        String subject = new Gson().toJson(user);

        try {
            JWTConfig jwtConfig = new JWTConfig();
            String jwt = jwtConfig.createJWT(Constant.JWT_ID, "lihuan", subject, Constant.JWT_TTL);
            System.out.println("JWT:" + jwt);

            System.out.println("\n解密\n");

            Claims c = jwtConfig.parseJWT(jwt);
            System.out.println(c.getId());
            System.out.println(c.getIssuedAt());
            System.out.println(c.getSubject());
            System.out.println(c.getIssuer());
            System.out.println(c.get("uid", String.class));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Constant.java

代码语言:javascript
复制
public static final String JWT_ID = UUID.randomUUID().toString();

/**
 * 加密密文
 */
public static final String JWT_SECRET = "123456789";
// millisecond
public static final int JWT_TTL = 60*60*1000;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 应用场景
    • JWT结构
      • Header
        • Payload
          • Signature
          • 完整示例
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档