如何使用JAX-RS和Jersey实现基于REST令牌的身份验证

我正在寻找一种在Jersey中启用基于令牌的身份验证的方法。我尽量不使用任何特定的框架。可能吗

我的计划是:用户注册我的web服务,我的web服务生成一个令牌,发送给客户端,客户端将保留它。然后,对于每个请求,客户端将发送令牌,而不是用户名和密码

我曾考虑对每个请求使用一个自定义筛选器,并对其进行预授权("hasRole(’ROLE’)),但我只是认为这会导致大量对数据库的请求来检查令牌是否有效

还是不创建筛选器并在每个请求中放置一个param令牌?所以每个API首先检查令牌,然后执行一些操作来检索资源

基于令牌的身份验证工作原理

在基于令牌的身份验证中,客户端交换硬凭证(例如用户名和密码)以获取一段名为令牌的数据。对于每个请求,客户端将向服务器发送令牌以执行身份验证,然后再进行授权,而不是发送硬凭据

简而言之,基于令牌的身份验证方案遵循以下步骤:

  1. 客户端将其凭据(用户名和密码)发送到服务器
  2. 服务器验证凭据,如果凭据有效,则为用户生成令牌
  3. 服务器将先前生成的令牌与用户标识符和过期日期一起存储在某些存储器中
  4. 服务器将生成的令牌发送到客户端
  5. 客户端在每个请求中向服务器发送令牌
  6. 服务器在每个请求中从传入的请求中提取令牌。使用令牌,服务器查找用户详细信息以执行身份验证。
    • 如果令牌有效,服务器将接受请求
    • 如果令牌无效,服务器将拒绝该请求
  7. 执行身份验证后,服务器将执行授权
  8. 服务器可以提供一个端点来刷新令牌

使用JAX-RS2.0(Jersey、RESTEasy和ApacheCXF)可以做什么

此解决方案仅使用JAX-RS 2.0 API,避免任何特定于供应商的解决方案。因此,它应该与JAX-RS2.0实现配合使用,例如球衣RESTEasyApache CXF

值得一提的是,如果您使用的是基于令牌的身份验证,那么您就不依赖servlet容器提供的标准Java EE web应用程序安全机制,并且可以通过应用程序的web.xml描述符进行配置。这是一种自定义身份验证

使用用户名和密码验证用户并颁发令牌

创建一个JAX-RS资源方法,用于接收和验证凭据(用户名和密码),并为用户颁发令牌:

@Path("/authentication")
公共类身份验证端点{
@职位
@产生(MediaType.APPLICATION_JSON)
@使用(MediaType.APPLICATION\u FORM\u URLENCODED)
公共响应验证器(@FormParam("username")字符串用户名,
@FormParam(“密码”)字符串(密码){
试一试{
//使用提供的凭据对用户进行身份验证
验证(用户名、密码);
//为用户颁发令牌
字符串标记=issueToken(用户名);
//返回响应上的令牌
返回Response.ok(token.build();
}捕获(例外e){
返回Response.status(Response.status.forbidded).build();
}      
}
私有void身份验证(字符串用户名、字符串密码)引发异常{
//针对数据库、LDAP、文件或任何内容进行身份验证
//如果凭据无效,则引发异常
}
私有字符串issueToken(字符串用户名){
//发出令牌(可以是持久化到数据库的随机字符串或JWT令牌)
//颁发的令牌必须与用户关联
//返回已颁发的令牌
}
}

如果验证凭据时引发任何异常,将返回状态为403(禁止)的响应

如果成功验证凭据,则将返回状态为200(确定)的响应,并将发出的令牌发送到响应负载中的客户端。客户端必须在每个请求中向服务器发送令牌

使用应用程序/x-www-form-urlencoded时,客户端必须在请求负载中以以下格式发送凭据:

username=admin&密码=123456

可以将用户名和密码包装到一个类中,而不是form params:

公共类凭据实现可序列化{
私有字符串用户名;
私有字符串密码;
//省略了getter和setter
}

然后将其作为JSON使用:

@POST
@产生(MediaType.APPLICATION_JSON)
@使用(MediaType.APPLICATION_JSON)
公共响应验证器(凭据){
字符串username=credentials.getUsername();
字符串密码=凭据。getPassword();
//对用户进行身份验证,发出令牌并返回响应
}

使用此方法,客户端必须以以下格式在请求的有效负载中发送凭据:

{
&“用户名”:“管理员”;,
&“密码”:“123456”;
}

从请求中提取令牌并对其进行验证

客户端应在请求的标准HTTP授权头中发送令牌。例如:

授权:承载<代币在这里>

标准HTTP头的名称很不幸,因为它携带的是身份验证信息,而不是授权。但是,它是向服务器发送凭据的标准HTTP头

JAX-RS提供了@NameBinding,一种元注释,用于创建其他注释,将过滤器和拦截器绑定到资源类和方法。定义@Secured注释,如下所示:

@NameBinding
@保留(运行时)
@目标({TYPE,METHOD})
公共@接口安全{}

上面定义的名称绑定注释将用于修饰过滤器类,该过滤器类实现ContainerRequestFilter,允许您在资源方法处理请求之前拦截请求。ContainerRequestContext可用于访问HTTP请求头,然后提取令牌:

@Secured
@提供者
@优先级(Priorities.AUTHENTICATION)
公共类AuthenticationFilter实现ContainerRequestFilter{
私有静态最终字符串领域=“示例”;;
私有静态最终字符串身份验证方案=“承载人”;;
@凌驾
公共无效筛选器(ContainerRequestContext requestContext)引发IOException{
//从请求中获取授权标头
字符串授权标头=
getHeaderString(HttpHeaders.AUTHORIZATION);
//验证授权标头
如果(!isTokenBasedAuthentication(authorizationHeader)){
abortWithUnauthorized(请求上下文);
回来
}
//从授权标头中提取令牌
字符串标记=authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
试一试{
//验证令牌
validateToken(令牌);
}捕获(例外e){
abortWithUnauthorized(请求上下文);
}
}
私有布尔值isTokenBasedAuthentication(字符串授权标头){
//检查授权标头是否有效
//它不能为空,并且必须以“Bearer”加空格作为前缀
//验证方案比较必须不区分大小写
return authorizationHeader!=null&authorizationHeader.toLowerCase()
.startsWith(身份验证方案.toLowerCase()+);
}
私有void abortWithUnauthorized(ContainerRequestContext requestContext){
//使用401状态代码响应中止筛选器链
//WWW Authenticate标头随响应一起发送
requestContext.abortWith(
响应.状态(响应.状态.未授权)
.header(HttpHeaders.WWW_),
身份验证方案+“领域=\”领域+“领域+”)
.build());
}
私有void validateToken(字符串令牌)引发异常{
//检查令牌是否由服务器颁发,以及是否未过期
//如果令牌无效,则引发异常
}
}

如果在令牌验证过程中出现任何问题,则响应状态为401</cod

发表评论