Jersey基于令牌的认证: ContainerRequestFilter 无法触发

发布于 2022-09-06 15:26:28 字数 6798 浏览 25 评论 0

因为正在使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文