Java的JWT(JSON Web令牌)库


76

我正在使用Java和AngularJS开发一个Web应用程序,并选择实现令牌认证和授权。出于练习目的,我到达了将凭证发送到服务器,生成随机令牌存储凭证并将其发送回客户端的地步。在对服务器的每个请求中,我都将令牌附加在标题中,并且可以完美地工作。对于身份验证的观点是完美的,不需要更多。

但是,我现在想跟踪用户类型(管理员,普通用户...),它的ID或任何其他唯一字段;据我了解,我必须使用登录过程中发送回客户端的令牌对令牌进行加密。那是对的吗?

您是否使用过任何JWT库,并且可以生成,加密和解密此类令牌?链接到库的API和Maven依赖关系将不胜感激。

谢谢


1
如果要存储在令牌中的信息不敏感,则不必加密令牌。用户标识和权限是秘密吗?可能没有。您需要确保的是只有您才能创建有效的令牌。jwt方法是使用Hmac对令牌进行数字签名,并使用秘密签名密钥来确保您能够验证其完整性和来源。我在下面的回答提供了一个库和示例。
Marquez 2014年

1
您好。我也尝试实现此JWT库,我在服务器端(Java)进行了操作,但是如何在前端(javascript)进行解码?您使用了哪个库在angularjs部分对其进行解码?
Thiago Miranda de Oliveira 2014年

蒂亚戈,我没有。流程如下:用户登录->发送到服务器的数据->创建令牌->发送回客户端。每当对服务器执行请求时,令牌都会被附加在标头中(我已经为此实现了一个拦截器)。验证是在服务器上完成的,并且正确的响应已发送回(无论是否经过授权)。
Marius Manastireanu 2014年

2
处理JWT时的有用页面:jwt.io
Vilmantas Baranauskas,2015年

@MariusManastireanu您从angular发送的令牌与从服务器收到的令牌相同?我正在同一部分上工作...请帮助
kittu '16

Answers:


53

JJWT旨在成为最易于使用和理解JVM和Android的JWT库:

https://github.com/jwtk/jjwt


4
简单,方便,干净,可以立即工作。我先去了Google JsonToken,然后在处理了无法解析的依赖关系和代码页面以组装一个简单的JWT之后就切换到了这里。
奥利弗·豪斯勒

1
如何防止令牌被盗?
JHS 2015年

我该如何处理令牌超时,例如在nodejs-jsonwebtoken中
vanduc1102

我可以在一个简单的测试程序中使用jjwt。但是,当我在dropwizard应用程序中尝试使用它时,在启动时出现以下错误:java.lang.NoSuchFieldError:WRITE_DURATIONS_AS_TIMESTAMPS。有什么想法吗?
雅。

1
“如何处理令牌超时”-根据jwt规范,您需要设置“ exp”声明。Jjwt为此提供了一个方便的“ setExpiration”方法。
雅。

26

如果有人需要答案,

我使用了这个库:http : //connect2id.com/products/nimbus-jose-jwt Maven在这里:http : //mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt/2.10.1


2
请确保您使用最新版本。该库将得到不断的审查和改进,并添加了新的算法/功能:mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt 从3.2版开始,所有标准JWS签名算法均已得到完全支持以及所有JWE加密algs,另存为PBES2和ECDH-ES。
弗拉基米尔·朱维诺夫(Fladimir Dzhuvinov)2014年

我收到以下错误:java.lang.NoSuchMethodError:org.apache.commons.codec.binary.Base64.decodeBase64

以上问题记录在此处bitbucket.org/connect2id/nimbus-jose-jwt/issue/52/…。我通过修改com.nimbusds.jose.util.Base64Url中的byte [] encode()方法来解决此问题,以使用android.util.Base64.decode方法。这是正确的方法吗?
2015年

1
在2.24版中,我们从Apache Commons Codec切换到内部实用程序来处理BASE64编码和解码。这样,编解码器的Android问题已成功解决。有关更多详细信息,请参见问题bitbucket.org/connect2id/nimbus-jose-jwt/issue/63。另外,您现在应该会获得更好的性能:)
Vladimir Dzhuvinov 2015年

@MariusManastireanu嗨,我从使用JWT的angularjs + Java身份验证开始。您能否指导我或显示示例代码以实现此目标?
kittu 2015年

19

通过参考https://jwt.io/,您可以找到jwt许多语言的实现,包括java。该站点还提供了这些实现(它们支持的算法和...)之间的一些比较。

对于java这些提到的库:


谢谢,它帮助我为我的游戏框架应用找到了JWT插件
chabeee

1
你们如何选择使用哪个?如果所有4个库都涵盖我的所有用例,该怎么办?
jkerak

6
@jkerak尝试执行您认为最简单的方法。如果所有实现似乎对您来说都很希腊,请使用名称最酷的实现。这对我来说很好。
TheFunk

13

该库似乎运行良好:https : //code.google.com/p/jsontoken/

这取决于Google Guava。这是Maven工件:

<dependency>
    <groupId>com.googlecode.jsontoken</groupId>
    <artifactId>jsontoken</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

该库实际上是由Google电子钱包使用的。

以下是创建jwt以及对其进行验证和反序列化的方法:

import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.util.Calendar;
import java.util.List;

import net.oauth.jsontoken.JsonToken;
import net.oauth.jsontoken.JsonTokenParser;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;
import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
import net.oauth.jsontoken.crypto.SignatureAlgorithm;
import net.oauth.jsontoken.crypto.Verifier;
import net.oauth.jsontoken.discovery.VerifierProvider;
import net.oauth.jsontoken.discovery.VerifierProviders;

import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;

import com.google.common.collect.Lists;
import com.google.gson.JsonObject;


/**
 * Provides static methods for creating and verifying access tokens and such. 
 * @author davidm
 *
 */
public class AuthHelper {

    private static final String AUDIENCE = "NotReallyImportant";

    private static final String ISSUER = "YourCompanyOrAppNameHere";

    private static final String SIGNING_KEY = "LongAndHardToGuessValueWithSpecialCharacters@^($%*$%";

    /**
     * Creates a json web token which is a digitally signed token that contains a payload (e.g. userId to identify 
     * the user). The signing key is secret. That ensures that the token is authentic and has not been modified.
     * Using a jwt eliminates the need to store authentication session information in a database.
     * @param userId
     * @param durationDays
     * @return
     */
    public static String createJsonWebToken(String userId, Long durationDays)    {
        //Current time and signing algorithm
        Calendar cal = Calendar.getInstance();
        HmacSHA256Signer signer;
        try {
            signer = new HmacSHA256Signer(ISSUER, null, SIGNING_KEY.getBytes());
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }

        //Configure JSON token
        JsonToken token = new net.oauth.jsontoken.JsonToken(signer);
        token.setAudience(AUDIENCE);
        token.setIssuedAt(new org.joda.time.Instant(cal.getTimeInMillis()));
        token.setExpiration(new org.joda.time.Instant(cal.getTimeInMillis() + 1000L * 60L * 60L * 24L * durationDays));

        //Configure request object, which provides information of the item
        JsonObject request = new JsonObject();
        request.addProperty("userId", userId);

        JsonObject payload = token.getPayloadAsJsonObject();
        payload.add("info", request);

        try {
            return token.serializeAndSign();
        } catch (SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Verifies a json web token's validity and extracts the user id and other information from it. 
     * @param token
     * @return
     * @throws SignatureException
     * @throws InvalidKeyException
     */
    public static TokenInfo verifyToken(String token)  
    {
        try {
            final Verifier hmacVerifier = new HmacSHA256Verifier(SIGNING_KEY.getBytes());

            VerifierProvider hmacLocator = new VerifierProvider() {

                @Override
                public List<Verifier> findVerifier(String id, String key){
                    return Lists.newArrayList(hmacVerifier);
                }
            };
            VerifierProviders locators = new VerifierProviders();
            locators.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocator);
            net.oauth.jsontoken.Checker checker = new net.oauth.jsontoken.Checker(){

                @Override
                public void check(JsonObject payload) throws SignatureException {
                    // don't throw - allow anything
                }

            };
            //Ignore Audience does not mean that the Signature is ignored
            JsonTokenParser parser = new JsonTokenParser(locators,
                    checker);
            JsonToken jt;
            try {
                jt = parser.verifyAndDeserialize(token);
            } catch (SignatureException e) {
                throw new RuntimeException(e);
            }
            JsonObject payload = jt.getPayloadAsJsonObject();
            TokenInfo t = new TokenInfo();
            String issuer = payload.getAsJsonPrimitive("iss").getAsString();
            String userIdString =  payload.getAsJsonObject("info").getAsJsonPrimitive("userId").getAsString();
            if (issuer.equals(ISSUER) && !StringUtils.isBlank(userIdString))
            {
                t.setUserId(new ObjectId(userIdString));
                t.setIssued(new DateTime(payload.getAsJsonPrimitive("iat").getAsLong()));
                t.setExpires(new DateTime(payload.getAsJsonPrimitive("exp").getAsLong()));
                return t;
            }
            else
            {
                return null;
            }
        } catch (InvalidKeyException e1) {
            throw new RuntimeException(e1);
        }
    }


}

public class TokenInfo {
    private ObjectId userId;
    private DateTime issued;
    private DateTime expires;
    public ObjectId getUserId() {
        return userId;
    }
    public void setUserId(ObjectId userId) {
        this.userId = userId;
    }
    public DateTime getIssued() {
        return issued;
    }
    public void setIssued(DateTime issued) {
        this.issued = issued;
    }
    public DateTime getExpires() {
        return expires;
    }
    public void setExpires(DateTime expires) {
        this.expires = expires;
    }
}

这是基于以下代码:https//developers.google.com/wallet/instant-buy/about-jwts此处:https//code.google.com/p/wallet-online-sample-java/source /browse/src/com/google/wallet/online/jwt/util/WalletOnlineService.java?r=08b3333bd7260b20846d7d96d3cf15be8a128dfa


5
该库使用了数千个依赖项,所有这些依赖项都需要解决,编译和使您的项目陷入困境。为什么不主要使用Java提供的功能?
奥利弗·豪斯勒

不是1000s,而是少于20s的依赖关系,为我工作。
vanduc1102 2015年

这个lib仍然活跃吗?
arvindwill

无法下载项目。得到401
Val Martinez

8

IETF已在其Wiki上建议了jose libs:http//trac.tools.ietf.org/wg/jose/trac/wiki

我强烈建议使用它们进行签名。我不是Java专家,但jose4j似乎是一个不错的选择。也有很好的例子:https : //bitbucket.org/b_c/jose4j/wiki/JWS%20Examples

更新:jwt.io提供了几个与jwt相关的库及其功能的完美对比。必须检查!

我很想听听其他Java开发人员的偏爱。


jose4j也有很多JWT支持,除了您指出的JWS示例之外,还有很多使用它的良好示例
布莱恩·坎贝尔



3

如果只需要解析未签名的未加密令牌,则可以使用以下代码:

boolean parseJWT_2() {
    String authToken = getToken();
    String[] segments = authToken.split("\\.");
    String base64String = segments[1];
    int requiredLength = (int)(4 * Math.ceil(base64String.length() / 4.0));
    int nbrPaddings = requiredLength - base64String.length();

    if (nbrPaddings > 0) {
        base64String = base64String + "====".substring(0, nbrPaddings);
    }

    base64String = base64String.replace("-", "+");
    base64String = base64String.replace("_", "/");

    try {
        byte[] data = Base64.decode(base64String, Base64.DEFAULT);

        String text;
        text = new String(data, "UTF-8");
        tokenInfo = new Gson().fromJson(text, TokenInfo.class);
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }

    return true;
}

2

https://github.com/networknt/jsontoken

这是原始google jsontoken的fork

自2012年9月11日以来未进行任何更新,并且取决于某些旧软件包。

我做了什么:

Convert from Joda time to Java 8 time. So it requires Java 8.
Covert Json parser from Gson to Jackson as I don't want to include two Json parsers to my projects.
Remove google collections from dependency list as it is stopped long time ago.
Fix thread safe issue with Java Mac.doFinal call.

所有现有的单元测试都通过了一些新添加的测试用例。

这是生成令牌并验证令牌的示例。有关更多信息,请检查https://github.com/networknt/light源代码以了解用法。

我是jsontoken和Omni-Channel Application Framework的作者。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.