当前位置: 代码网 > it编程>编程语言>Java > Spring框架整合Java Web Token问题

Spring框架整合Java Web Token问题

2024年09月05日 Java 我要评论
java web tokenjson web token(jwt)是一个非常轻巧的规范。这个规范允许我们使用jwt在用户和服务器之间传递安全可靠的信息。jwt组成一个jwt实际上就是一个字符串,它由三

java web token

json web token(jwt)是一个非常轻巧的规范。

这个规范允许我们使用jwt在用户和服务器之间传递安全可靠的信息。

jwt组成

一个jwt实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

荷载

{
"iss": "john wu jwt",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"from_user": "b",
"target_user": "a"
}

这里面的前五个字段都是由jwt的标准所定义的。

  • iss: 该jwt的签发者
  • sub: 该jwt所面向的用户
  • aud: 接收该jwt的一方
  • exp(expires): 什么时候过期,这里是一个unix时间戳
  • iat(issued at): 在什么时候签发的

这些定义都可以在标准中找到。

将上面的json对象进行[base64编码]可以得到下面的字符串。

这个字符串我们将它称作jwt的payload(载荷)。

eyjpc3mioijkb2huifd1iepxvcisimlhdci6mtq0mtu5mzuwmiwizxhwijoxndqxntk0nziylcjhdwqioij3d3cuzxhhbxbszs5jb20ilcjzdwiioijqcm9ja2v0qgv4yw1wbguuy29tiiwiznjvbv91c2vyijoiqiisinrhcmdldf91c2vyijoiqsj9

头部

jwt还需要一个头部,头部用于描述关于该jwt的最基本的信息,例如其类型以及签名所用的算法等。

这也可以被表示成一个json对象。

{
“typ”: “jwt”,
“alg”: “hs256”
}

在这里,我们说明了这是一个jwt,并且我们所用的签名算法(后面会提到)是hs256算法。(算法根据实际情况而变)

eyj0exaioijkv1qilcjhbgcioijiuzi1nij9

签名

签名就是将荷载和头部编码用.号连接在一起就形成了

eyj0exaioijkv1qilcjhbgcioijiuzi1nij9.eyjmcm9tx3vzzxiioijciiwidgfyz2v0x3vzzxiioijbin0

我们将上面拼接完的字符串用hs256算法进行加密。

在加密的时候,我们还需要提供一个密钥(secret)。

如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容

rswamyaywuhco7ifagd1orpsp7nzl7bf5t7itqpkvim

最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的jwt

eyj0exaioijkv1qilcjhbgcioijiuzi1nij9.eyjmcm9tx3vzzxiioijciiwidgfyz2v0x3vzzxiioijbin0.rswamyaywuhco7ifagd1orpsp7nzl7bf5t7itqpkvim

签名的目的:

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。

所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到jwt后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在jwt的头部中已经用alg字段指明了我们的加密算法了。

如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个token的内容被别人动过的,我们应该拒绝这个token,返回一个http 401 unauthorized响应。

我们可以看到,jwt适合用于向web应用传递一些非敏感信息。例如在上面提到的完成加好友的操作,还有诸如下订单的操作等等。

其实jwt还经常用于设计用户认证和授权系统,甚至实现web应用的单点登录。

spring使用jwt

maven配置方式

<dependency>
    <groupid>com.auth0</groupid>
    <artifactid>java-jwt</artifactid>
    <version>3.2.0</version>
</dependency>

jwt算法(了解)

jws算法介绍
hs256hmac256hmac with sha-256
hs384hmac384hmac with sha-384
hs512hmac512hmac with sha-512
rs256rsa256rsassa-pkcs1-v1_5 with sha-256
rs384rsa384rsassa-pkcs1-v1_5 with sha-384
rs512rsa512rsassa-pkcs1-v1_5 with sha-512
es256ecdsa256ecdsa with curve p-256 and sha-256
es384ecdsa384ecdsa with curve p-384 and sha-384
es512ecdsa512ecdsa with curve p-521 and sha-512

使用方法

选择算法:

算法定义了一个令牌是如何被签名和验证的。它可以用hmac算法的原始值来实例化,也可以在rsa和ecdsa算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。

在使用rsa或ecdsa算法时,只需要签署jwts,就可以通过传递null值来避免指定公钥。当您需要验证jwts时,也可以使用私钥进行操作

使用静态的字符密文或者key来获取算法器:

//hmac
algorithm algorithmhs = algorithm.hmac256("secret");

//rsa
rsapublickey publickey = //get the key instance
rsaprivatekey privatekey = //get the key instance
algorithm algorithmrs = algorithm.rsa256(publickey, privatekey);

使用一个key提供者来获取算法:

通过使用keyprovider,您可以在运行时更改密钥,用于验证令牌签名或为rsa或ecdsa算法签署一个新的令牌。

这是通过实现rsakeyprovider或ecdsakeyprovider方法实现的:

  • getpublickeybyid(string kid): 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如jwk,它可以使用id来获取正确的轮换键(或者只是一直返回相同的键)。
  • getprivatekey(): 在令牌签名期间调用它,它应该返回用于签署jwt的密钥。
  • getprivatekeyid():在令牌签名期间调用它,它应该返回标识由getprivatekey()返回的键的id的id。这个值比jwtcreator.builder和keyid(string)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用keyprovider实例化算法。

创建jwt

try {
algorithm algorithm = algorithm.hmac256("secret");
string token = jwt.create()
    .withissuer("auth0")
    .sign(algorithm);
} catch (unsupportedencodingexception exception){
    //utf-8 encoding not supported
} catch (jwtcreationexception exception){
    //invalid signing configuration / couldn't convert claims.
}

如果claim不能转换为json,或者在签名过程中使用的密钥无效,那么将会抛出jwtcreationexception异常。

验证令牌

首先需要通过调用jwt.require()和传递算法实例来创建一个jwtverifier实例。

如果您要求令牌具有特定的claim值,请使用构建器来定义它们。

方法build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。

最后调用verifier.verify()来验证token

string token = "eyjhbgcioijiuzi1niisinr5cci6ikpxuyj9.eyjpc3mioijhdxromcj9.abijtdmfc7yua5mhvcp03njpycpzztqcgep-zwfokee";
try {
    algorithm algorithm = algorithm.hmac256("secret");
    jwtverifier verifier = jwt.require(algorithm)
        .withissuer("auth0")
        .build(); //reusable verifier instance
    decodedjwt jwt = verifier.verify(token);
} catch (unsupportedencodingexception exception){
    //utf-8 encoding not supported
} catch (jwtverificationexception exception){
    //invalid signature/claims
}

时间验证

当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个jwtverificationexception。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。

要指定令牌仍然被认为有效的余地窗口,在jwtverifier builder中使用accept回旋()方法,并传递一个正值的秒值。这适用于上面列出的每一项。

jwtverifier verifier = jwt.require(algorithm)
.acceptleeway(1) // 1 sec for nbf, iat and exp
.build();

您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。

jwtverifier verifier = jwt.require(algorithm)
.acceptleeway(1)   //1 sec for nbf and iat
.acceptexpiresat(5)   //5 secs for exp
.build();

信息解析

algorithm (“alg”)

返回jwt的算法值或,如果没有定义则返回null

string algorithm = jwt.getalgorithm();

如果您需要在您的lib/app中测试此行为,将验证实例转换为basever可视化,以获得verific.build()方法的可见性,该方法可以接受定制的时钟。

例如:

baseverification verification = (baseverification) jwt.require(algorithm)
.acceptleeway(1)
.acceptexpiresat(5);
clock clock = new customclock(); //must implement clock interface
jwtverifier verifier = verification.build(clock);

type (“typ”)

返回jwt的类型值,如果没有定义则返回null(多数情况类型值为jwt)

string type = jwt.gettype();

content type (“cty”)

返回内容的类型,如果没有定义则返回null

string contenttype = jwt.getcontenttype();

key id (“kid”)

返回key的id值,如果没有定义则返回null

string keyid = jwt.getkeyid();

自定义字段

在令牌的头部中定义的附加声明可以通过调用getheaderclaim() 获取,即使无法找到,也会返回。您可以通过调用claim.isnull()来检查声明的值是否为null。

claim claim = jwt.getheaderclaim("owner");

当使用jwt.create()创建一个令牌时,您可以通过调用withheader()来指定头声明,并同时传递声明的映射。

map<string, object> headerclaims = new hashmap();
headerclaims.put("owner", "auth0");
string token = jwt.create()
    .withheader(headerclaims)
    .sign(algorithm);

提示:在签名过程之后,alg和typ值将始终包含在header中。

jwt的负载(payload)声明

issuer ("iss")

返回签发者的名称值,如果没有在负载中定义则返回null

string issuer = jwt.getissuer();
subject ("sub")

返回jwt所面向的用户的值,如果没有在负载中定义则返回null

string subject = jwt.getsubject();
audience ("aud")

返回该jwt由谁接收,如果没有在负载中定义则返回null

list<string> audience = jwt.getaudience();
expiration time ("exp")

返回该jwt的过期时间,如果在负载中没有定义则返回null

date expiresat = jwt.getexpiresat();
not before ("nbf")

returns the not before value or null if it’s not defined in the payload.

date notbefore = jwt.getnotbefore();
issued at ("iat")

返回在什么时候签发的,如果在负载中没有定义则返回null

date issuedat = jwt.getissuedat();
jwt id ("jti")

返回该jwt的唯一标志,如果在负载中没有定义则返回null

string id = jwt.getid();

自定义声明

在令牌有效负载中定义的附加声明可以通过调用getclaims()或 getclaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用claim.isnull()来检查声明的值是否为null。

map<string, claim> claims = jwt.getclaims();    //key is the claim name
claim claim = claims.get("isadmin");

或者:

claim claim = jwt.getclaim("isadmin");

当使用jwt.create()创建一个令牌时,您可以通过调用withclaim()来指定自定义声明,并同时传递名称和值。

string token = jwt.create()
    .withclaim("name", 123)
    .witharrayclaim("array", new integer[]{1, 2, 3})
    .sign(algorithm);

您还可以通过调用withclaim()来验证jwt.require()的自定义声明,并传递该名称和所需的值。

jwtverifier verifier = jwt.require(algorithm)
    .withclaim("name", 123)
    .witharrayclaim("array", 1, 2, 3)
    .build();
decodedjwt jwt = verifier.verify("my.jwt.token");    

实例

package course.utils;

import com.auth0.jwt.jwt;
import com.auth0.jwt.jwtverifier;
import com.auth0.jwt.algorithms.algorithm;
import com.auth0.jwt.exceptions.jwtverificationexception;
import com.auth0.jwt.interfaces.decodedjwt;
import com.google.common.collect.maps;
import course.pojo.user;

import java.io.unsupportedencodingexception;
import java.util.date;
import java.util.map;

public class jwtutils {

    //创建token
    public static string creattoken(user user) throws illegalargumentexception, unsupportedencodingexception{
        algorithm algorithm = algorithm.hmac256("secret");
        string username = user.getusername();
        map<string, object> map = maps.newhashmap();
        map.put("alg", "hs256");
        map.put("typ", "jwt");
        string token = jwt.create().withheader(map)
                .withclaim("username", username)
                .withexpiresat(new date(system.currenttimemillis()+360000))
                .sign(algorithm);
        return token;
    }

    //验证jwt
    public static decodedjwt verifyjwt(string token){
        decodedjwt decodedjwt = null;
        try{
            algorithm algorithm = algorithm.hmac256("secret");
            jwtverifier jwtverifier = jwt.require(algorithm).build();
            decodedjwt = jwtverifier.verify(token);
        }catch(illegalargumentexception e){
            e.printstacktrace();
        }catch (unsupportedencodingexception e){
            e.printstacktrace();
        }catch(jwtverificationexception e) {
            e.printstacktrace();
        }
        return decodedjwt;
    }

    public static void main(string[] args) throws unsupportedencodingexception{
//        string username = "root";
//        integer id =1;
//        system.out.println(creattoken(username,id));
//        string token = "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjlehaioje1mdgxmzgxndasinvzzxjjzci6mswidxnlcm5hbwuioijyb290in0.oerdhjzkmxfbqin-a-usnqk8jykdzx-wcfr883omqfa";
//        system.out.println(verifyjwt("eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjlehaioje1mdgxmzgxndasinvzzxjjzci6mswidxnlcm5hbwuioijyb290in0.oerdhjzkmxfbqin-a-usnqk8jykdzx-wcfr883omqfa"));
//        system.out.println(verifyjwt(token).getclaims().get("username").asstring());
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com