SpringBoot整合JWT Token

在文章之前,我们先介绍几个概念

OAuth2、JWT,Spring Security、Spring Security OAuth2

OAuth2:Open Authorization,是一种授权协议,是规范,不是技术实现。

JWT:JSON Web Token,是一种具体的Token实现框架。

Spring Security:前身是 Acegi Security ,能够为 Spring企业应用系统提供声明式的安全访问控制。该框架老古董了。

Spring Security OAuth2:Spring 对 OAuth2 开源实现(与Spring Cloud技术栈无缝集成)。

目前用的最多是JWT,因此本文也是围绕JWT来实现。

1.什么是JWT

JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户。当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。

2.JWT的组成

JWT TOKEN分为三部分:header.payload.signature,因此JWT通常如下表示xxx.yyyyy.zz

2.1header

头部包含了两部分,token 类型和采用的加密算法。alg字段指定了生成signature的算法,默认值为HS256,typ默认值为JWT。

如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程

扫描二维码关注公众号,回复: 9124477 查看本文章

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

2.2payload

这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分。

{
    "iss": "admin",          //该JWT的签发者
    "iat": 1535967430,        //签发时间
    "exp": 1535974630,        //过期时间
    "nbf": 1535967430,         //该时间之前不接收处理该Token
    "sub": "www.admin.com",   //面向的用户
    "jti": "9f10e796726e332cec401c569969e13e"   //该Token唯一标识
}

2.3Signature

String secret = "123456";//秘钥

HMACSHA256( base64UrlEncode(header) + "." +base64UrlEncode(payload),secret

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

2.4JWT的样式

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。

2.5签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

2.6信息暴露

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

流程参考如下:资料引用于https://www.cnblogs.com/wenqiangit/p/9592132.html

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。
  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
  5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  6. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

3JWT TOKEN的实现

3.1引入依赖

    <!--JWT Token-->
        <dependency>
                <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

3.2Token工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: Token工具类
 * @author hutao
 * @mail [email protected]
 * @date 2020年1月11日
 */
public class TokenUtil {
    
    

    /**
     * 签名秘钥
     */
    public static final String SECRET = "123456";
    /**
     * 签发地
     */
    public static final String ISSUER  = "hutao.com";
    /**
     * 过期时间
     */
    public static final long TTLMILLIS = 3600*1000*60;

    /**
     * @Description: 生成Token令牌
     * @author hutao
     * @mail [email protected]
     * @date 2020年1月11日
     * @param claims    私有声明
     * @param id        编号
     * @param issuer    该JWT的签发者,是否使用是可选的
     * @param subject   该JWT所面向的用户,是否使用是可选的;
     * @param ttlMillis 有效时间
     * @return token String
     */
    public static String generateJwtToken(Map<String,Object> claims,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 ConcurrentHashMap<String,Object>();
        claims.put("aaaa", "aaaa");
        claims.put("bbbb", "bbbb");
        claims.put("cccc", "cccc");*/
        
        // 通过秘钥签名JWT
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

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

        // 如果已指定,则添加到期时间
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(exp);
        }

        // 构建JWT并将其序列化为一个紧凑的url安全字符串
        return builder.compact();
    }


    /**
     * @Description: 解析Token
     * @author hutao
     * @mail [email protected]
     * @date 2020年1月11日
     */
    public static Claims parseJWT(String jwt) {
        // 如果这行代码不是签名的JWS(如预期),那么它将抛出异常
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
                .parseClaimsJws(jwt).getBody();
        return claims;
    }
    public static void main(String[] args) {

        String token = TokenUtil.generateJwtToken(null,"100",ISSUER,"hutao",TTLMILLIS);
        
        System.out.println(token);

        Claims claims = TokenUtil.parseJWT(token);

        System.out.println(claims);

    }
}
 

发布了7 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/m0_37892044/article/details/103945042