在基于令牌的身份验证中,客户端交换硬凭据(如用户名和密码)以获取称为令牌的数据。对于每个请求,客户端将向服务器发送令牌以执行身份验证和授权,而不是发送硬凭据。
简而言之,基于令牌的身份验证方案遵循以下步骤:
@Path("/authentication")
public class AuthenticationEndpoint {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response authenticateUser(@FormParam("username") String username,
@FormParam("password") String password) {
try {
// Authenticate the user using the credentials provided
authenticate(username, password);
// Issue a token for the user
String token = issueToken(username);
// Return the token on the response
return Response.ok(token).build();
} catch (Exception e) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
private void authenticate(String username, String password) throws Exception {
// Authenticate against a database, LDAP, file or whatever
// Throw an Exception if the credentials are invalid
}
private String issueToken(String username) {
// Issue a token (can be a random String persisted to a database or a JWT token)
// The issued token must be associated to a user
// Return the issued token
}
}
如果成功验证了凭据,将返回状态为200
(OK)的响应,并将发出的令牌发送到响应负载中的客户端。客户端必须在每个请求中向服务器发送令牌。
当使用application/x-www-form-urlencoded
时,客户端必须在请求有效载荷中以以下格式发送凭据:
username=admin&password=123456
可以将用户名和密码封装到一个类中,而不是表单params:
public class Credentials implements Serializable {
private String username;
private String password;
// Getters and setters omitted
}
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
// Authenticate the user, issue a token and return a response
}
json prettyprint-override">{
"username": "admin",
"password": "123456"
}
客户端应该在请求的标准HTTPAuthorization
标头中发送令牌。例如:
Authorization: Bearer <token-goes-here>
标准HTTP标头的名称是不幸的,因为它携带身份验证信息,而不是授权。但是,它是向服务器发送凭据的标准HTTP标头。
JAX-RS提供@namebinding
,这是一个元注释,用于创建其他注释,将过滤器和拦截器绑定到资源类和方法。定义@securite
注释,如下所示:
@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String REALM = "example";
private static final String AUTHENTICATION_SCHEME = "Bearer";
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
.build());
}
private void validateToken(String token) throws Exception {
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
如果某些方法或类不需要身份验证,只需不对其进行注释即可:
@Path("/example")
public class ExampleResource {
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myUnsecuredMethod(@PathParam("id") Long id) {
// This method is not annotated with @Secured
// The authentication filter won't be executed before invoking this method
...
}
@DELETE
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response mySecuredMethod(@PathParam("id") Long id) {
// This method is annotated with @Secured
// The authentication filter will be executed before invoking this method
// The HTTP request must be performed with a valid token
...
}
}
在上面显示的示例中,过滤器将只针对MySecuredMethod(Long)
方法执行,因为它是用@securite
注释的。
您很可能需要知道是哪个用户在执行REST API的请求。可以使用以下方法来实现:
final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return () -> username;
}
@Override
public boolean isUserInRole(String role) {
return true;
}
@Override
public boolean isSecure() {
return currentSecurityContext.isSecure();
}
@Override
public String getAuthenticationScheme() {
return AUTHENTICATION_SCHEME;
}
});
@Context
SecurityContext securityContext;
在JAX-RS资源方法中也可以这样做:
@GET
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id,
@Context SecurityContext securityContext) {
...
}
然后获取主体
:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
如果出于某种原因,您不想重写SecurityContext
,可以使用CDI(上下文和依赖项注入),它提供了有用的特性,如事件和生产者。
@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser { }
@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;
userAuthenticatedEvent.fire(username);
创建一个CDI bean来处理身份验证事件,找到一个具有对应用户名的User
实例,并将其分配给AuthenticatedUser
生产者字段:
@RequestScoped
public class AuthenticatedUserProducer {
@Produces
@RequestScoped
@AuthenticatedUser
private User authenticatedUser;
public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
this.authenticatedUser = findUser(username);
}
private User findUser(String username) {
// Hit the the database or a service to find a user by its username and return it
// Return the User instance
}
}
AuthenticatedUser
字段生成一个User
实例,该实例可以注入容器托管bean,如JAX-RS服务、CDI bean、servlet和EJB。使用下面的代码插入用户
实例(实际上,它是一个CDI代理):
@Inject
@AuthenticatedUser
User authenticatedUser;
请注意,CDI@produces
注释不同于JAX-RS@produces
注释:
确保在AuthenticatedUserProducer
bean中使用CDI@produces
注释。
这里的关键是用@requestscoped
注释的bean,允许您在过滤器和bean之间共享数据。如果您不想不使用事件,可以修改过滤器,将经过身份验证的用户存储在请求范围bean中,然后从JAX-RS资源类中读取它。
与重写SecurityContext
的方法相比,CDI方法允许您从JAX-RS资源和提供者以外的bean获得经过身份验证的用户。
详见下文:
可以通过生成随机字符串并将其与用户标识符和过期日期一起持久化到数据库来发布令牌。在这里可以看到如何在Java中生成随机字符串的一个很好的示例。您还可以使用:
Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);
客户端可以读取有效负载,通过在服务器上验证令牌的签名,可以轻松地检查令牌的完整性。签名可以防止令牌被篡改。
如果不需要跟踪JWT令牌,就不需要持久化它们。但是,通过持久化令牌,您将有可能使它们的访问无效和撤消。为了跟踪JWT令牌,而不是在服务器上持久化整个令牌,您可以持久化令牌标识符(JTI
声明)以及其他一些细节,如为其颁发令牌的用户、过期日期等。
持久化令牌时,请始终考虑删除旧令牌,以防止数据库无限期增长。
如果要撤消令牌,则必须保持对令牌的跟踪。您不需要在服务器端存储整个令牌,如果需要,只存储令牌标识符(必须是唯一的)和一些元数据。对于令牌标识符,可以使用UUID。
应该使用JTI
声明将令牌标识符存储在令牌上。验证令牌时,请根据服务器端的令牌标识符检查JTI
声明的值,以确保它没有被撤消。
出于安全目的,当用户更改密码时,请撤消其所有令牌。
我正在寻找一种在泽西岛启用基于令牌的身份验证的方法。我试着不使用任何特定的框架。有可能吗? 我的计划是:一个用户注册我的web服务,我的web服务生成一个令牌,发送给客户端,客户端将保留它。则客户端将针对每个请求发送令牌,而不是用户名和密码。 我曾考虑为每个请求使用自定义筛选器和,但我只是认为这会导致大量请求到数据库以检查令牌是否有效。 或者不创建筛选器并在每个请求中放置一个param令牌?以便每
问题内容: 我正在寻找一种在Jersey中启用基于令牌的身份验证的方法。我正在尝试不使用任何特定的框架。那可能吗? 我的计划是:用户注册我的Web服务,我的Web服务生成一个令牌,并将其发送给客户端,客户端将保留它。然后,对于每个请求,客户端将发送令牌,而不是用户名和密码。 我当时在考虑为每个请求使用自定义过滤器,但是我只是认为这会导致对数据库的大量请求检查令牌是否有效。 还是不创建过滤器,并在每
为了测试,我尝试下面的代码过滤包含用户的url参数,但是它没有在未经授权的情况下中止请求。最重要的是,我需要以这样的方式来实现它,即只有更新和删除需要用各自的用户名和密码来授权。其他我只是不想过滤的东西。我有一个user类,它具有username和password(加密)属性。因此,如果url包含Users/{userID}的PUT或delete方法,我希望它使用特定用户的用户名和密码进行验证。我
null 我的自定义rest筛选器: 上面的内容实际上会导致应用程序启动时出现一个错误:有人能告诉我如何最好地执行此操作吗?pre_auth筛选器是执行此操作的最好方法吗? 编辑 使用Spring-security实现解决方案 希望它能帮助其他人…
我正在使用C#实现一个REST web服务,它将作为云服务托管在Azure上。因为它是一个REST服务,所以它是无状态的,因此没有cookie或会话状态。 Web服务只能通过HTTPS(由StartSSL. com提供的证书)访问。 用户成功登录服务后,他们将获得一个安全令牌。该令牌将在未来的通信中提供身份验证。 令牌将包含客户端的时间戳、用户ID和ip地址。 所有通信都只能通过HTTPS进行,所
我很少使用jersey实现创建rest服务。出于安全考虑,服务可以由任何一个调用。所以我决定使用基于令牌的认证系统。我在spring security中编写了一个过滤器,它在每个请求到达服务器之前处理它。 创建了一个登录服务,这样用户就可以通过传递有效凭据的用户名和密码来调用该服务,它将生成访问令牌和到期日期,并将其保存在Hashmap和DB中,并作为响应返回给用户。 对于剩余的服务,用户必须传递