JWT正在发出相同的令牌 [英] JWT is issuing the same token

查看:310
本文介绍了JWT正在发出相同的令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Jersey 制作一个休息API。我正在使用 java-jwt https:// github.com/auth0/java-jwt )我的令牌生成工作。请检查以下代码。



UserJSONInfo - REST方法类

  @Path(/ user_info)
公共类UserInfoJSONService
{
@POST
@Path(/ authenticateUser)
@ Produces(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials)
{
UserInfoService service = new UserInfoService();

try {
UserInfo authenticateUser = service.authenticateUser(credentials.getUserName(),credentials.getPassword());
String generateToken = service.generateToken(AuthKey.authorizationSecret);

身份验证auth = new Authentication();
auth.setIdUser(authenticateUser.getIduser());
auth.setToken(generateToken);

返回Response.status(Response.Status.OK).entity(auth).build();
//返回authenticateUser;
}
catch(IndexOutOfBoundsException e)
{
抛出新的WebApplicationException(Response.Status.BAD_REQUEST);
} catch(JWTCreationException ex){
抛出新的WebApplicationException(Response.Status.UNAUTHORIZED);
} catch(UnsupportedEncodingException ex){
抛出新的WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
}

UserInfoService - 服务层

  public class UserInfoService {

private static UserInfoDAOInterface userDAOInterface;

public UserInfoService(){
userDAOInterface = new UserInfoDAOImpl();
}

public Session getSession(){
Session session = userDAOInterface.openCurrentSession();
返回会话;
}

public Transaction getTransaction(Session session){
Transaction transaction = userDAOInterface.openTransaction(session);
退货交易;
}



public UserInfo authenticateUser(String userName,String password)
{
return authenticate(userName,password);
}

private UserInfo authenticate(String userName,String password)throws IndexOutOfBoundsException
{
Session session = userDAOInterface.openCurrentSession();
交易事务= null;
UserInfo user = new UserInfo();

try {
transaction = userDAOInterface.openTransaction(session);
user = userDAOInterface.authenticate(userName,password,session);
transaction.commit();
//} catch(Exception ex){
// //ex.printStackTrace();
// System.out.println(OK);
//
} finally {
session.close();
}

返回用户;
}

public String generateToken(String secret)throws JWTCreationException,UnsupportedEncodingException
{
Token token = new Token();
返回token.issueTokenHMAC256(秘密);
}

}

AuthKey - 只需包含秘密

 公共接口AuthKey {
public static String authorizationSecret =xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
}

web.xml

 <?xml version =1.0encoding =UTF-8?> 
< web-app version =3.0xmlns =http://java.sun.com/xml/ns/javaeexmlns:xsi =http://www.w3.org/2001/ XMLSchema-instancexsi:schemaLocation =http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\">
< servlet>
< servlet-name> ExampleServlet< / servlet-name>
< servlet-class> test.ExampleServlet< / servlet-class>
< / servlet>
< servlet>
< servlet-name> Jersey RESTful Application< / servlet-name>
< servlet-class> org.glassfish.jersey.servlet.ServletContainer< / servlet-class>
< init-param>
< param-name> jersey.config.server.provider.packages< / param-name>
< param-value> rest< / param-value>
< / init-param>
< init-param>
< param-name> com.sun.jersey.api.json.POJOMappingFeature< / param-name>
< param-value> true< / param-value>
< / init-param>
< / servlet>
< servlet-mapping>
< servlet-name> ExampleServlet< / servlet-name>
< url-pattern> / ExampleServlet< / url-pattern>
< / servlet-mapping>
< servlet-mapping>
< servlet-name> Jersey RESTful Application< / servlet-name>
< url-pattern> / rest / *< / url-pattern>
< / servlet-mapping>
< session-config>
< session-timeout>
30
< / session-timeout>
< / session-config>
< welcome-file-list>
< welcome-file> index.jsp< / welcome-file>
< / welcome-file-list>
< / web-app>

我将我的令牌生成类维护为另一个java项目并将其作为库导入(我是使用Netbeans)。以下是代码

  package com.xyz.security; 

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;

/ **
*
* @author Yohan
* /
公共类令牌{

/ **
*生成HMAC256令牌
* @param secret
*秘密生成令牌
* @return
*令牌为字符串
* @throws UnsupportedEncodingException
*不支持UTF-8编码
* @throws JWTVerificationException
*签名配置无效/无法转换声明。
* /
public String issueTokenHMAC256(String secret)throws UnsupportedEncodingException,JWTCreationException
{

String token =;
try {
算法算法= Algorithm.HMAC256(秘密);
token = JWT.create()
.withIssuer(auth0)
.sign(algorithm);


} catch(UnsupportedEncodingException异常){
//不支持UTF-8编码
exception.printStackTrace();
} catch(JWTCreationException异常){
//无效的签名配置/无法转换声明。
exception.printStackTrace();
}

返回令牌;
}


/ **
*验证HMAC256令牌
* @param令牌
*您需要验证$ b $的令牌b * @param secret
*用于生成令牌的秘密
* @return
*如果令牌有效,则返回true。
* @throws UnsupportedEncodingException
*不支持UTF-8编码
* @throws JWTVerificationException
*无效的签名配置/无法转换声明。
* /
public boolean validateTokenHMAC256(String token,String secret)throws UnsupportedEncodingException,JWTVerificationException
{
算法算法= Algorithm.HMAC256(秘密);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(auth0)
.build(); //可重用的验证器实例
DecodedJWT jwt = verifier.verify(token);

返回true;
}
}

现在的问题是,每次我生成 token 当用户登录时,我得到相同的标记。我正在使用 POSTMAN 来检查REST方法,我打开了3个选项卡并为3个不同的用户进行了登录尝试。问题是我得到了相同的标记!这是对的还是错的?在那种情况下,我该如何解决?

解决方案

基于令牌的认证方案是什么



基于令牌的认证方案,令牌成为用户的凭证。用户名和密码等硬凭证将交换为必须在每个请求中发送的令牌,然后服务器才能执行身份验证/授权。令牌可以在很短的时间内有效,可以撤销,可以携带范围详细信息(可以使用令牌请求的内容)等。



使用令牌,您必须能够识别定位API的用户。因此,对于所有经过身份验证的用户,只有一个令牌是没有意义的。



解决您的问题



一旦你是使用JWT,您可以使用用户名进行声明。还要考虑为您的令牌添加到期日期( exp 声明)。您不希望您的令牌永远有效吗?是吗?



使用 java-jwt ,使用以下内容:

 尝试{

算法算法= Algorithm.HMAC256(secret);
Date expirationDate = Date.from(ZonedDateTime.now()。plusMinutes(60).toInstant());
String token = JWT.create()
.withExpiresAt(expirationDate)
.withClaim(username,username)
.sign(algorithm);

} catch(UnsupportedEncodingException e){
//不支持UTF-8编码
} catch(JWTCreationException e){
//签名配置无效/无法'转换索赔
}

验证令牌时,您将能够获得用户名声明并知道您为谁签发了令牌:

  try {

算法算法= Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);

索赔usernameClaim = jwt.getClaim(username);
String username = usernameClaim.asString();

} catch(UnsupportedEncodingException e){
//不支持UTF-8编码
} catch(JWTVerificationException e){
//无效签名/声明
}



使用JWT处理令牌更新



仅接受 有效(未过期)令牌以进行更新。客户有责任在 <$中指明的到期日期之前刷新令牌c $ c> exp 声明。



为避免令牌无限期刷新,您可以保留令牌的跟踪通过向您的令牌添加两个声明(声明名称由您决定)来刷新:




  • refreshLimit :表示可以刷新令牌的次数。

  • refreshCount :表示令牌已刷新的次数。



因此,只有在满足以下条件时才刷新令牌:




  • 令牌未过期( exp> =现在)。

  • 该次数已刷新的令牌少于令牌可刷新的次数( refreshCount< refreshLimit )。



An d刷新令牌时:




  • 更新到期日期( exp = now + some-amount-of-time )。

  • 增加令牌刷新的次数( refreshCount ++ )。



签署令牌并在服务器端验证签名后,客户端不会篡改令牌的内容。 / p>

除了跟踪茶点数量之外,您还可以声明绝对到期日期。在此日期之前,任何数量的茶点都是可以接受的。



另一种方法是发出一个单独的长期刷新令牌,用于发出短命的JWT令牌。



最佳方法取决于您的要求。



使用JWT处理令牌撤销



如果要撤销令牌,则必须跟踪它们。您不需要在服务器端存储整个令牌,只存储令牌标识符(必须是唯一的)和一些元数据(如果需要)。对于令牌标识符,您可以使用 UUID



jti 声明应该用于将令牌标识符存储在令牌本身上。验证令牌时,请通过检查的值来确保它未被撤销。 jti 针对您在服务器端的令牌标识符提出索赔。



出于安全考虑,请撤消所有用户更改密码时的令牌。






有关JAX中基于令牌的身份验证的更多详细信息-RS,请参阅此答案


I am making a rest API with Jersey. I am using java-jwt(https://github.com/auth0/java-jwt) for my token generation work. Please check the below code.

UserJSONInfo - REST methods class

@Path ("/user_info")
public class UserInfoJSONService 
{
    @POST
    @Path("/authenticateUser")
    @Produces(MediaType.APPLICATION_JSON)
    public Response authenticateUser(Credentials credentials)
    {
        UserInfoService service = new UserInfoService();

        try{
        UserInfo authenticateUser = service.authenticateUser(credentials.getUserName(), credentials.getPassword());
        String generateToken = service.generateToken(AuthKey.authorizationSecret);

        Authentication auth = new Authentication();
        auth.setIdUser(authenticateUser.getIduser());
        auth.setToken(generateToken);

        return Response.status(Response.Status.OK).entity(auth).build();
        //return authenticateUser;
        }
        catch(IndexOutOfBoundsException e)
        {
            throw new WebApplicationException(Response.Status.BAD_REQUEST);
        } catch (JWTCreationException ex) {
            throw new WebApplicationException(Response.Status.UNAUTHORIZED);
        } catch (UnsupportedEncodingException ex) {
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
    }
}

UserInfoService - Service Layer

public class UserInfoService {

    private static UserInfoDAOInterface userDAOInterface;

    public UserInfoService() {
        userDAOInterface = new UserInfoDAOImpl();
    }

    public Session getSession() {
        Session session = userDAOInterface.openCurrentSession();
        return session;
    }

    public Transaction getTransaction(Session session) {
        Transaction transaction = userDAOInterface.openTransaction(session);
        return transaction;
    }



    public UserInfo authenticateUser(String userName, String password)
    {
        return authenticate(userName, password);
    }

    private UserInfo authenticate(String userName, String password) throws IndexOutOfBoundsException
    {
        Session session = userDAOInterface.openCurrentSession();
        Transaction transaction = null;
        UserInfo user = new UserInfo();

        try {
            transaction = userDAOInterface.openTransaction(session);
            user = userDAOInterface.authenticate(userName, password, session);
            transaction.commit();
//        } catch (Exception ex) {
//            //ex.printStackTrace();
//            System.out.println("OK");
//        
        } finally {
            session.close();
        }

        return user;
    }

    public String generateToken(String secret) throws JWTCreationException, UnsupportedEncodingException
    {
        Token token = new Token();
        return token.issueTokenHMAC256(secret);
    }

}

AuthKey - Simply contains the secret

public interface AuthKey {
    public static String authorizationSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>ExampleServlet</servlet-name>
        <servlet-class>test.ExampleServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Jersey RESTful Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>rest</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>ExampleServlet</servlet-name>
        <url-pattern>/ExampleServlet</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Jersey RESTful Application</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>    
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

I have maintained my token generation classes as another java project and imported it here as a library (I am using Netbeans). Below is the code

package com.xyz.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;

/**
 *
 * @author Yohan
 */
public class Token {

    /**
     * Generate the HMAC256 Token
     * @param secret
     *          Secret to generate the token
     * @return 
     *      Token as a String
     * @throws UnsupportedEncodingException
     *      UTF-8 encoding not supported
     * @throws JWTVerificationException 
     *      Invalid Signing configuration / Couldn't convert Claims.
     */
    public String issueTokenHMAC256(String secret) throws UnsupportedEncodingException, JWTCreationException
    {

        String token="";
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            token = JWT.create()
                    .withIssuer("auth0")
                    .sign(algorithm);


        } catch (UnsupportedEncodingException exception) {
    //UTF-8 encoding not supported
            exception.printStackTrace();
        } catch (JWTCreationException exception) {
    //Invalid Signing configuration / Couldn't convert Claims.
            exception.printStackTrace();
        }

        return token;
    }


    /**
     * Validate a HMAC256 Token
     * @param token
     *          Token you need to validate
     * @param secret
     *          Secret used to generate the token
     * @return
     *          Returns `true` if token is valid.
     * @throws UnsupportedEncodingException
     *          UTF-8 encoding not supported
     * @throws JWTVerificationException 
     *          Invalid Signing configuration / Couldn't convert Claims.
     */
    public boolean validateTokenHMAC256(String token, String secret) throws UnsupportedEncodingException, JWTVerificationException
    {       
        Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("auth0")
                .build(); //Reusable verifier instance
            DecodedJWT jwt = verifier.verify(token);

        return true;
    }
}

Now the problem is, everytime I generate the token when a user login, I am getting the same token. I am using POSTMAN to check REST methods, I opened 3 tabs and made a login attempt for 3 different users. The issue is I am getting the same token! Is this is correct or something wrong? In that case how can I fix it?

解决方案

What authentication scheme based on tokens is about

In an authentication scheme based on tokens, the token becomes a credential of the user. Hard credentials such as username and password are exchanged for a token that must be sent in each request then the server can perform authentication/authorization. Tokens can be valid for a short amount of time, can be revoked, can carry scope details (what can be requested with the token), etc.

With a token, you must be able to identify the user who is targeting your API. Hence it makes no sense having a single token for all authenticated users.

Addressing your issue

Once you are using JWT, you could have a claim with the username. Also consider adding an expiration date for you token (exp claim). You don't want your​ token to be valid forever, do you?

With java-jwt, use the following:

try {

    Algorithm algorithm = Algorithm.HMAC256("secret");
    Date expirationDate = Date.from(ZonedDateTime.now().plusMinutes(60).toInstant());
    String token = JWT.create()
                      .withExpiresAt(expirationDate)
                      .withClaim("username", username)
                      .sign(algorithm);

} catch (UnsupportedEncodingException e){
    // UTF-8 encoding not supported
} catch (JWTCreationException e){
    // Invalid signing configuration / Couldn't convert claims
}

When verifying the token, you'll be able to get the username claim and know who you issued the token for:

try {

    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm).build();
    DecodedJWT jwt = verifier.verify(token);

    Claim usernameClaim = jwt.getClaim("username");
    String username = usernameClaim.asString();

} catch (UnsupportedEncodingException e){
    // UTF-8 encoding not supported
} catch (JWTVerificationException e){
    // Invalid signature/claims
}

Handling token refreshment with JWT

Accept only valid (an non-expired) tokens for refreshment. It's responsability of the client to refresh the tokens before the expiration date indicated in the exp claim.

To avoid a token from being refreshed indefinitely, you could keep the track of the token refreshment by adding two claims to your token (the claim names are up to you):

  • refreshLimit: Indicates how many times the token can be refreshed.
  • refreshCount: Indicates how many times the token has been refreshed.

So only refresh the token if the following conditions are true:

  • The token is not expired (exp >= now).
  • The number of times that the token has been refreshed is less than the number of times that the token can be refreshed (refreshCount < refreshLimit).

And when refreshing the token:

  • Update the expiration date (exp = now + some-amount-of-time).
  • Increment the number of times that the token has been refreshed (refreshCount++).

Once the token is signed and the signature is verified on server side, the content of the token cannot be tampered by the client.

Alternatively to keeping​ the track of the number of refreshments, you could have a claim that indicates the absolute expiration date. Before that date, any number of refreshments are acceptable.

Another approach involves issuing a separate long-lived refresh token that is used to issue short-lived JWT tokens.

The best approach depends on your requirements.

Handling token revocation with JWT

If you want to revoke tokens, you must keep the track of them. You don't need to store the whole token on server side, store only the token identifier (that must be unique) and some metadata if you need. For the token identifier you could use UUID.

The jti claim should be used to store the token identifier on the token itself. When validating the token, ensure that it has not been revoked by checking the value of the jti claim against the token identifiers you have on server side.

For security purposes, revoke all the tokens for a user when they change their password.


For more details on token-based authentication in JAX-RS, refer to this answer.

这篇关于JWT正在发出相同的令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆