问题描述
我正在用 Jersey
制作一个 rest API.我正在使用 java-jwt
(https://github.com/auth0/java-jwt) 用于我的令牌生成工作.请检查以下代码.
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 方法类
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 - 服务层
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).下面是代码
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;
}
}
现在的问题是,每次我在用户登录时生成 token
时,都会得到相同的 token
.我正在使用 POSTMAN
来检查 REST 方法,我打开了 3 个选项卡并尝试登录 3 个不同的用户.问题是我得到了相同的令牌!这是正确的还是错误的?在这种情况下,我该如何解决?
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.
使用令牌,您必须能够识别以您的 API 为目标的用户.因此,为所有经过身份验证的用户使用一个令牌是没有意义的.
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.
使用 JWT 后,您可以使用用户名进行声明.还可以考虑为您的令牌添加过期日期 (exp代码> 声明).您不希望您的令牌永远有效,是吗?
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?
使用 java-jwt,使用以下内容:
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
}
验证令牌时,您将能够获得 username
声明并知道您为谁颁发令牌:
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
}
使用 JWT 处理令牌刷新
接受仅有效(未过期)令牌以进行更新.客户有责任在 中指示的到期日期之前刷新令牌exp
声明.
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
:表示token可以刷新多少次.refreshCount
:表示令牌刷新了多少次.
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:
- 令牌未过期(
exp >= now
). - 令牌被刷新的次数小于令牌可以被刷新的次数(
refreshCount
).
刷新令牌时:
- 更新到期日期(
exp = now + some-amount-of-time
). - 增加令牌刷新的次数 (
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.
另一种方法涉及发布一个单独的长寿命刷新令牌,用于发布短寿命 JWT 令牌.
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.
如果您想撤销令牌,您必须跟踪它们.您不需要将整个令牌存储在服务器端,只存储令牌标识符(必须是唯一的)和一些元数据(如果需要).对于令牌标识符,您可以使用 UUID.
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.
jti
应使用声明将令牌标识符存储在令牌本身上.验证令牌时,通过检查 jti
声明您在服务器端拥有的令牌标识符.
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.
有关 JAX-RS 中基于令牌的身份验证的更多详细信息,请参阅此答案.支持>
这篇关于JWT 发出相同的令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!