Jersey基于令牌的认证: ContainerRequestFilter 无法触发
因为正在使用Jersey框架搭建一个后端数据服务,现在正在使用Jersey 2.26 (JAX-RS 2.1的实现)
由于想要实现一个基于令牌的认证服务
于是Google到如下链接,似乎是一个非常好的办法,然而试验之后在ContainerRequestFilter内部添加断点,发现filter实际上并没有成功触发.
Jersey和JAX-RS基于令牌认证方式的最佳实践(英文)
试过StackOverFlow上的很多好办法,包括验证JAX-RS API版本,加上Provider等等,由于并不是基于servlet的web服务,所以也没有使用web.xml
相关源代码如下
AuthenticationFilter.kt
package mybackend.server.util
import java.security.Principal
import javax.annotation.Priority
import javax.inject.Inject
import javax.ws.rs.Priorities
import javax.ws.rs.container.ContainerRequestContext
import javax.ws.rs.container.ContainerRequestFilter
import javax.ws.rs.container.PreMatching
import javax.ws.rs.core.HttpHeaders
import javax.ws.rs.core.Response
import javax.ws.rs.core.SecurityContext
import javax.ws.rs.ext.Provider
@Secured
@Priority(Priorities.AUTHENTICATION)
@Provider
class AuthenticationFilter: ContainerRequestFilter {
@Inject
lateinit var userLoginDaoService: UserLoginDaoService
companion object {
@JvmStatic
private val REALM = "example"
@JvmStatic
private val AUTHENTICATION_SCHEME = "Bearer"
}
override fun filter(requestContext: ContainerRequestContext): Unit {
System.out.println("authenticating...")
var authorizationHeader: String = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION)
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext)
return
}
var token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length).trim()
try {
var username = validateToken(token)
val currentSecurityContext = requestContext.securityContext
requestContext.securityContext = object : SecurityContext {
override fun getUserPrincipal(): Principal {
return object : Principal {
override fun getName(): String {
return username
}
}
}
override fun isUserInRole(role: String): Boolean {
return true
}
override fun isSecure(): Boolean {
return currentSecurityContext.isSecure
}
override fun getAuthenticationScheme(): String {
return AUTHENTICATION_SCHEME
}
}
} catch (e: Exception) {
abortWithUnauthorized(requestContext)
}
}
private fun isTokenBasedAuthentication(authorizationHeader: String): Boolean {
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ")
}
private fun abortWithUnauthorized(requestContext: ContainerRequestContext): Unit {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, "$AUTHENTICATION_SCHEME realm=\" $REALM\"").build())
}
private fun validateToken(token: String): String {
// validate token
var reslist = userLoginDaoService.findByProperty("token", token)
if (reslist.size > 1) {
throw IllegalTokenValidationResultSize("More than 1 entity containing this token found")
} else if (reslist.isEmpty()) {
throw TokenValidationFailedException("Token validation failed, need to re-authenticate")
}
return reslist[0].getUsername()
}
/* Exceptions */
internal class IllegalTokenValidationResultSize(override var message : String): Exception(message)
internal class TokenValidationFailedException(override var message : String): Exception(message)
}
UserResource.kt
认证入口在 /auth_test 路径
package mybackend.server.resources
import javax.ws.rs.GET
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.Produces
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.LogManager
import java.lang.Exception
import java.math.BigInteger
import java.security.SecureRandom
import javax.inject.Inject
import javax.ws.rs.core.Context
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.SecurityContext
@Path("users")
class UserResource {
// File logger
val log2f: Logger = LogManager.getLogger("fileLog")
// Root logger
val log2c: Logger = LogManager.getRootLogger()
@Inject
lateinit var userLoginDAO: UserLoginDAO
@Inject
lateinit var userLoginDaoService: UserLoginDaoService
@Path("auth_user")
@POST
@Produces(MediaType.APPLICATION_JSON)
fun authenticateUser(userLogin: UserLogin): Response {
try {
authenticate(userLogin.getUsername(), userLogin.getPassword())
var token = issueToken(userLogin.getUsername())
return Response.ok(token).build()
} catch (e: Exception) {
return Response.status(Response.Status.FORBIDDEN).build()
}
}
private fun authenticate(username: String, password: String): Unit {
var stored_psw = userLoginDaoService.
findByProperty("loginName", username)[0].getPassword()
// TODO: Test if username does not exist (testing about kotlin null handling)
if (!stored_psw.equals(password)) {
throw Exception("password doesn't match, authentication failed")
}
}
private fun issueToken(username: String): String {
var random = SecureRandom()
var token = BigInteger(130, random).toString(32)
var curr_user = userLoginDaoService.findByProperty("loginName", username)[0]
userLoginDaoService.setToken(curr_user, token)
return token
}
@Path("auth_test")
@GET
@Secured
@Produces("text/plain")
fun testAuthentication(@Context securityContext: SecurityContext): String {
val auth_principal = securityContext.userPrincipal
val name = auth_principal.name
return name
}
}
Secured.java
这是一个标记,用来标记需要在执行前执行Filter的相关资源
package mybackend.server.annotation;
import javax.ws.rs.NameBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Secured{}
请问有人有解决方案吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论