JWT 发出相同的令牌
Posted
技术标签:
【中文标题】JWT 发出相同的令牌【英文标题】:JWT is issuing the same token 【发布时间】:2017-08-15 21:56:55 【问题描述】:我正在使用 Jersey
制作一个休息 API。我正在使用java-jwt
(https://github.com/auth0/java-jwt) 进行令牌生成工作。请检查以下代码。
UserJSONInfo - REST 方法类
@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 - 服务层
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 - 只包含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>
我已将我的令牌生成类作为另一个 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
*/
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;
现在的问题是,每次我在用户登录时生成token
,我都会得到相同的token
。我正在使用POSTMAN
检查 REST 方法,我打开了 3 个选项卡并尝试登录 3 个不同的用户。问题是我得到了相同的令牌!这是正确的还是错误的?在这种情况下,我该如何解决?
【问题讨论】:
你是如何在“AuthKey”中生成这个“authorizationSecret”的? 【参考方案1】:基于令牌的认证方案是什么
在基于令牌的身份验证方案中,令牌成为用户的凭证。用户名和密码等硬凭证被交换为必须在每个请求中发送的令牌,然后服务器可以执行身份验证/授权。令牌可以在短时间内有效,可以撤销,可以携带范围详细信息(可以使用令牌请求的内容)等。
使用令牌,您必须能够识别以您的 API 为目标的用户。因此,为所有经过身份验证的用户使用一个令牌是没有意义的。
解决您的问题
使用 JWT 后,您可以使用用户名进行声明。还可以考虑为您的令牌添加到期日期(exp
声明)。您不希望您的令牌永远有效,是吗?
对于java-jwt,使用以下内容:
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
验证令牌时,您将能够获得username
声明并知道您为谁颁发令牌:
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
使用 JWT 处理令牌刷新
接受仅有效(未过期)令牌以进行更新。在 exp
声明中指定的到期日期之前刷新令牌是客户的责任。
为避免令牌无限期刷新,您可以通过向令牌添加两个声明来跟踪令牌刷新(声明名称由您决定):
refreshLimit
:表示令牌可以刷新多少次。
refreshCount
:表示令牌刷新了多少次。
所以只有在满足以下条件时才刷新令牌:
令牌未过期 (exp >= now
)。
令牌已刷新的次数小于令牌可刷新的次数 (refreshCount < refreshLimit
)。
刷新令牌时:
更新到期日期 (exp = now + some-amount-of-time
)。
增加令牌刷新的次数 (refreshCount++
)。
一旦令牌被签名并在服务器端验证签名,令牌的内容就不能被客户端篡改。
除了跟踪茶点的数量之外,您还可以声明绝对过期日期。在此日期之前,可以接受任意数量的茶点。
另一种方法涉及发布一个单独的长寿命刷新令牌,用于发布短寿命 JWT 令牌。
最佳方法取决于您的要求。
使用 JWT 处理令牌撤销
如果您想撤销令牌,您必须跟踪它们。您不需要将整个令牌存储在服务器端,只存储令牌标识符(必须是唯一的)和一些元数据(如果需要)。对于令牌标识符,您可以使用 UUID。
jti
声明应用于将令牌标识符存储在令牌本身上。验证令牌时,请根据您在服务器端拥有的令牌标识符检查 jti
声明的值,确保它没有被撤销。
出于安全考虑,请在用户更改密码时撤销所有令牌。
有关 JAX-RS 中基于令牌的身份验证的更多详细信息,请参阅此answer。
【讨论】:
谢谢。验证时,我的validateTokenHMAC256()
也可以输入同样的内容,对吗?
谢谢。我还想问,如果我们得到一个JWT
对象并继续,而不是像JWT.create().withClaim........
等那样做,那将是非常可定制的,对吗?然后就像jwtobject.withClaim....
。我在打电话所以无法检查,但这有可能不是吗?
@PeakGen 对不起,我不明白你的意思。上述方法有什么问题吗?这就是您使用的 API 的工作原理。
谢谢。一切正常。另一个问题,当令牌过期时,我该如何处理?用户必须登录返回登录屏幕并每隔一小时登录一次?
@PeakGen 如果使用无效令牌执行请求,则服务器应以403
状态代码响应,并且客户端应处理该状态代码(例如,显示登录页面)。您还可以允许您的客户刷新他们的令牌。对此没有标准,但我用我通常做的事情更新了我的答案。以上是关于JWT 发出相同的令牌的主要内容,如果未能解决你的问题,请参考以下文章