1

我正在尝试在 TomEE 8(基于 Tomcat 9)中实现基于 JWT 的身份验证。我使用 org.glassfish.soteria:jakarta.security.enterprise:1.0.1 作为 Jakarta Security 的实现。按照本教程https://github.com/payara/Payara-Examples/blob/master/javaee/security-jwt-example/src/main/java/fish/payara/examples/security/JWTAuthenticationMechanism.java java 类看起来像这个:

@RequestScoped 
public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism 

private static final Logger LOGGER = Logger.getLogger(JWTAuthenticationMechanism.class.getName());

/**
 * Access to the
 * IdentityStore(AuthenticationIdentityStore,AuthorizationIdentityStore) is
 * abstracted by the IdentityStoreHandler to allow for multiple identity
 * stores to logically act as a single IdentityStore
 */
@Inject
private IdentityStoreHandler identityStoreHandler;

@Inject
private TokenProvider tokenProvider;

@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {

    LOGGER.log(Level.INFO, "validateRequest: {0}", request.getRequestURI());
    // Get the (caller) name and password from the request
    // NOTE: This is for the smallest possible example only. In practice
    // putting the password in a request query parameter is highly insecure
    String name = request.getParameter("name");
    String password = request.getParameter("password");
    String token = extractToken(context);

    if (name != null && password != null) {
        LOGGER.log(Level.INFO, "credentials : {0}, {1}", new String[]{name, password});
        // validation of the credential using the identity store
        CredentialValidationResult result = identityStoreHandler.validate(new UsernamePasswordCredential(name, password));
        if (result.getStatus() == CredentialValidationResult.Status.VALID) {
            // Communicate the details of the authenticated user to the container and return SUCCESS.
            return createToken(result, context);
        }
        // if the authentication failed, we return the unauthorized status in the http response
        return context.responseUnauthorized();
    } else if (token != null) {
        // validation of the jwt credential
        return validateToken(token, context);
    } else if (context.isProtected()) {
        // A protected resource is a resource for which a constraint has been defined.
        // if there are no credentials and the resource is protected, we response with unauthorized status
        return context.responseUnauthorized();
    }
    // there are no credentials AND the resource is not protected, 
    // SO Instructs the container to "do nothing"
    return context.doNothing();
}
...

用户使用用户名和密码发送登录请求,identityStoreHandler 对其进行验证。然后我们生成 JWT 令牌并将其发回。前端将它附加到每个下一个请求。
这行得通。
validateRequest() 为每个受保护或不受保护的请求触发。据我了解,它来自规范,是一种理想的行为。
现在,如果令牌过期并且用户向不受保护的 url 发送请求 - 它将被拒绝,因为令牌存在且无效。
我想首先检查 url 是否受保护,并且仅在受保护的情况下检查令牌的存在和有效性。但是 ((HttpMessageContext ) context.isProtected()) 总是返回 false。在控制器中,受保护的方法使用 @RolesAllowed 和 @PermitAll 注释进行注释。我也尝试使用基于 web.xml 的约束,但 isProtected() 仍然是错误的。
为什么总是假的?

更新
我的错误印象是基于注释的安全性和通过描述符(web.xml)是可以互换的。如果 web.xml 不包含任何安全约束 - 未经身份验证的用户对具有 @RolesAllowed 的资源的请求被拒绝并出现 403 错误,则对具有 @PermitAll 的资源的请求得到满足。这是一种奇怪的行为,两者都需要经过身份验证的用户并且应该被拒绝。
如果 web.xml 具有 auth-constraint 标记,则 context.isProtected() 为该 url 模式返回 true。
但是对于使用@RolesAllowed 和@PermitAll 注释的方法,如果这些方法中的路径与web.xml 中的url-pattern 不匹配,它仍然返回false。
根据这个https://www.ibm.com/support/knowledgecenter/SSEQTP_8.5.5/com.ibm.websphere.base.doc/ae/twbs_jaxrs_impl_securejaxrs_annotations.html

带注释的约束是任何已配置的安全约束的附加。在 Web 容器运行时环境检查了 web.xml 文件中配置的安全约束之后,JAX-RS 运行时环境会检查带注释的约束。

这是否意味着基于容器的安全性(Soteria)不会将带注释的方法视为受保护的?

4

0 回答 0