You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JSF应用中如何确保HttpAuthenticationMechanism优先于CDI拦截器执行?

问题:未认证用户访问受保护页面时,CDI拦截器先于HttpAuthenticationMechanism触发导致异常

我们的JSF应用使用自定义HttpAuthenticationMechanism实现认证逻辑:未认证用户访问受保护资源时自动跳转登录页,已认证用户通过@AutoApplySession自动应用会话。近期新增CDI拦截器@RolesPermitted,用于在CDI Bean的类/方法级别检查角色(功能与EJB的@RolesAllowed一致),无对应角色则跳转错误页。

但未认证用户请求/admin/secured-page.xhtml时,页面关联的后台Bean带有类级别@RolesPermitted({"admin"})注解,此时RolesPermitted拦截器会先于HttpAuthenticationMechanism触发,抛出安全异常而非跳转登录页。核心原因是JSF视图处理流程早于容器认证机制的执行时机。

需求:确保HttpAuthenticationMechanism在自定义安全拦截器前触发,让未认证用户正确跳转登录页。


解决方案

1. 调整CDI拦截器优先级

CDI拦截器的@Priority注解决定执行顺序,当前拦截器使用的Interceptor.Priority.APPLICATION优先级高于容器认证机制的执行时机。将拦截器优先级调低,确保容器认证先完成:

@Interceptor
@RolesPermitted
@Priority(Interceptor.Priority.LIBRARY_BEFORE) // 或使用数值(如500),需低于APPLICATION的2000
public class RoleInterceptor implements Serializable {
    // 原有代码不变
}

2. 在拦截器中先检查用户认证状态

在角色检查逻辑前,先判断用户是否已认证。若未认证,抛出特定异常并通过全局异常处理器跳转登录页:
修改RoleInterceptor的checkRole方法:

@AroundInvoke
public Object checkRole(InvocationContext ctx) throws Exception {
    // 先校验用户是否已认证
    if (sec.getCallerPrincipal() == null) {
        throw new AuthenticationException("用户未认证");
    }

    // 原有角色检查逻辑...
}

同时添加全局异常处理器(如JSF的ExceptionHandler或CDI的ExceptionMapper),捕获AuthenticationException后跳转登录页。

3. 确保web.xml安全约束完全生效

已配置的/admin/*安全约束应在请求到达JSF Bean前触发容器认证,需补充完善web.xml的登录配置:

<login-config>
    <auth-method>CUSTOM</auth-method> <!-- 对应自定义HttpAuthenticationMechanism -->
    <realm-name>CustomRealm</realm-name>
</login-config>

确认@AutoApplySession注解正常工作,容器会在每个请求自动应用会话认证,确保安全约束优先触发认证流程。

4. 添加Servlet Filter提前拦截

若上述方案不生效,可添加优先级高于JSF过滤器的Servlet Filter,提前触发认证检查:

@WebFilter(urlPatterns = "/admin/*")
public class AuthenticationFilter implements Filter {

    @Inject
    private HttpAuthenticationMechanism authMechanism;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        HttpMessageContext context = // 通过CDI注入或自定义实现获取HttpMessageContext

        AuthenticationStatus status = authMechanism.validateRequest(req, res, context);
        if (status == AuthenticationStatus.SEND_CONTINUE || status == AuthenticationStatus.SEND_FAILURE) {
            // 认证未完成或失败,终止请求链
            return;
        }

        chain.doFilter(request, response);
    }

    // 实现Filter的init、destroy方法
}

相关代码

角色拦截器代码

@Interceptor
@RolesPermitted
@Priority(Interceptor.Priority.APPLICATION)
public class RoleInterceptor implements Serializable {
 
    private static final long serialVersionUID = 1L;
    @Inject private SecurityContext sec;
    @Inject private Logger log;
 
    @AroundInvoke
    public Object checkRole(InvocationContext ctx) throws Exception {
        
        if(ctx.getMethod().getDeclaringClass().isAnnotationPresent(RolesPermitted.class)) {
            String[] classRoles = ctx
                .getMethod().getDeclaringClass()
                .getAnnotation(RolesPermitted.class)
                .value();
            boolean hasRole = false;
            log.debug("Checking if user has {} roles to execute {}",Arrays.asList(classRoles).toString(),ctx.getMethod().getDeclaringClass().getName());
            for(String r:classRoles) {
                if(sec.isCallerInRole(r)) {
                    hasRole=true;
                }
            }
            if(!hasRole) {
                throw new java.security.GeneralSecurityException("User does not have any of the required roles "+Arrays.asList(classRoles).toString());
            }
        }
        if(ctx.getMethod().isAnnotationPresent(RolesPermitted.class)) {         
            String[] methodRoles = ctx
                .getMethod()
                .getAnnotation(RolesPermitted.class)
                .value();
            if(methodRoles.length>0) {
                log.debug("Checking if user has {} roles to execute {}",Arrays.asList(methodRoles).toString(),ctx.getMethod().getName());
                for(String r:methodRoles) {
                    if(sec.isCallerInRole(r)) {
                        return ctx.proceed();
                    }
                }
                
                throw new java.security.GeneralSecurityException("User does not have any of the required roles "+Arrays.asList(methodRoles).toString());

            }
        }
        
        return ctx.proceed();
        
    }
 
}

后台Bean代码

@Named
@ViewScoped
@RolesPermitted({"admin"})
@Transactional(Transactional.TxType.REQUIRED)
public class AdminActions implements Serializable {

    // 页面逻辑代码

}

自定义认证机制代码(简化版)

@RequestScoped
@AutoApplySession
public class CustomAuthentication implements Serializable, HttpAuthenticationMechanism {
    
    private static final long serialVersionUID = 1L;
    
    @Inject private PasswordEncryptorEntities passwordEncryptor;
    @Inject private Logger log;

    @Override
    public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
            HttpMessageContext httpMessageContext) throws AuthenticationException {
        
        log.trace("Validating request {}",request.getRequestURI());
        
        if(httpMessageContext.isAuthenticationRequest()) {
            Set<String> roles = new HashSet<String>();
            
            if(loginPrincipal.coreRole().equals(Role.admin)) {
                Admin admin = (Admin) loginPrincipal;
                if(admin.isSuperUser()) {
                    roles.add(Role.adminSuperuser.getRole());
                }
            }
            UserPrincipal up = new UserPrincipal(loginPrincipal);
            httpMessageContext.getClientSubject().getPrincipals().add(up); 
            httpMessageContext.setRegisterSession(up.getName(), roles);
            log.debug("Login successful for {} with roles {}",loginPrincipal.getFullname(),roles.toString());
            return httpMessageContext.notifyContainerAboutLogin(up,roles);

        }else if(httpMessageContext.isProtected() && request.getUserPrincipal()==null) {
            // 未认证用户访问受保护资源,跳转登录页
            return httpMessageContext.forward(loginUrl);
            
        }else if(httpMessageContext.isProtected() && request.getUserPrincipal()!=null) {
            // 已认证但角色不符,跳转对应登录页
            UserPrincipal up = (UserPrincipal) request.getUserPrincipal();
            return httpMessageContext.forward(up.getAppUser().coreRole().getLoginPage().getPath());
        }
        
        return httpMessageContext.doNothing();
        
    }
    

}

web.xml安全约束配置

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Admin User Login Area</web-resource-name>
        <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
    <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
   </user-data-constraint>
</security-constraint>

内容的提问来源于stack exchange,提问作者DaveB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:45:30