【问题标题】:Using JSR-250 (@RolesAllowed) annotations Spring MVC and container authentication使用 JSR-250 (@RolesAllowed) 注解 Spring MVC 和容器认证
【发布时间】:2017-05-28 09:13:16
【问题描述】:

我有一个使用 Spring 框架和 Spring MVC 来创建 REST 端点的 Web 应用程序。身份验证由容器使用 Java EE 安全性(特别是 Wildfly 9 中由 Active Directory 支持的安全领域)提供,并且从应用程序数据库加载角色。我有一个过滤器应用的请求包装器,以通过普通的 Servlet API 提供用户详细信息。

web.xml:

<web-app ... version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Unauthenticated Resources</web-resource-name>
            <url-pattern>/favicon.ico</url-pattern>
            <url-pattern>/NoAccess.html</url-pattern>
            <url-pattern>/login</url-pattern>
            <url-pattern>/css/*</url-pattern>
            <url-pattern>/font/*</url-pattern>
            <url-pattern>/images/*</url-pattern>
            <url-pattern>/js/*</url-pattern>
            <url-pattern>/lib/*</url-pattern>
            <url-pattern>/partials/*</url-pattern>
        </web-resource-collection>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>All Resources</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>*</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>App Realm</realm-name>
        <form-login-config>
            <form-login-page>/Login.jsp</form-login-page>
            <form-error-page>/NoAccess.html</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <role-name>*</role-name>
    </security-role>

</web-app>

SecurityFilter.java

@WebFilter(urlPatterns = { "*" })
public class SecurityFilter implements Filter {

    ...

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // Don't wrap unauthenticated requests
        if (httpRequest.getRemoteUser() == null) {
            chain.doFilter(httpRequest, httpResponse);
            return;
        }

        Person user = this.personRepository.findByLogin(httpRequest.getRemoteUser());
        if (user != null) {
            List<String> roles = this.personRepository.getRoles(user.getId());
            HttpServletRequest wrappedRequest = new RequestWrapper(httpRequest, user, roles);
            chain.doFilter(wrappedRequest, httpResponse);
        } else {
            httpResponse.sendRedirect(httpRequest.getContextPath() + "/NoAccess.html");
        }
    }
}

身份验证有效,并且在控制器中,我可以执行 request.getUserPrincipal()request.isUserInRole("SYSTEM_ADMIN") 并且它们正确返回。现在我想为应用程序添加授权。我想在我的控制器方法上使用 JSR-250 @RolesPermitted 注释应用授权规则;我不想在单独的配置文件/类中列出所有规则。控制器示例:

@RestController
@RequestMapping("/people")
public class PersonController {

    ...

    @RolesAllowed({Role.SYSTEM_ADMIN, Role.DEPARTMENT_ADMIN, Role.SYSTEM_AUDITOR, Role.AUDITOR})
    @RequestMapping
    public List<Person> listPeople(HttpServletRequest request) {
        return this.personRepository.getPeople();
    }
}

当然,这本身没有任何作用,所以我添加了一个带有@EnableWebSecurity@EnableGlobalMethodSecurity(jsr250Enabled = true) 的配置类。现在,当我点击该资源时,我收到错误org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext。很公平,所以我按照https://gist.github.com/virgium03/86938e43219bc28632d0 的示例进行操作,但我不断收到相同的错误。似乎过滤器永远不会添加到过滤器链中。

我已经阅读了许多教程和 SO 帖子,但它们几乎都说了同样的话,并且对我不起作用。我一定缺少一些简单的东西——毕竟,Spring Security 的所有信息都可以用来做出授权决定。有可能(可能?)我在这里遗漏了一些简单的东西。非常感谢所有帮助!

编辑

我已将以下代码添加到我的 SecurityFilter 类中以填充 Spring 安全上下文。它有效,但我觉得这不是理想的解决方案。

List<GrantedAuthority> authorities = roles.stream()
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());
PreAuthenticatedAuthenticationToken auth = new PreAuthenticatedAuthenticationToken(user, "", authorities);
auth.setDetails(new WebAuthenticationDetails(wrappedRequest));
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);

【问题讨论】:

    标签: java spring-mvc jakarta-ee spring-security


    【解决方案1】:

    问题出在配置的身份验证提供程序上。您正在使用 PreAuthenticatedAuthenticationProvider 从 authenticate 方法返回现有的身份验证令牌。因此,如果主体为空,则它会忽略请求并让其他提供者对其进行身份验证。

    验证

    public Authentication authenticate(鉴权认证) throws AuthenticationException 对给定的 PreAuthenticatedAuthenticationToken 进行身份验证。如果 身份验证对象中包含的主体为空,请求 将被忽略以允许其他提供者对其进行身份验证。

    基本上,您需要配置另一个 AuthenticationProvider 来生成身份验证令牌并在 SecurityContext 中设置此令牌。

    【讨论】:

    • 但是查看 J2eePreAuthenticatedProcessingFilter 的源代码显示它从 HttpRequest 创建身份验证对象并将其放入安全上下文中(通过 AbstractPreAuthenticatedProcessingFilter)。这不应该是我所需要的吗?
    • 我查看了 J2eePreAuthenticatedProcessingFilter 的来源,它没有创建身份验证对象。它只是从 HttpRequest 返回用户主体。
    • 参见github.com/spring-projects/spring-security/blob/master/web/src/…(J2eePreAuthenticatedProcessingFilter 的超类)
    • 好的。我的错。我没有看超类。你能调试这个 AbstractPreAuthenticatedProcessingFilter 并看看你在 authResult 中得到了什么吗?此外,J2eePreAuthenticatedProcessingFilter 文档中提到没有通用方法来检索凭据,因为 getPreAuthenticatedCredentials 方法返回一个固定的虚拟值,这可能是 AuthenticationCredentialsNotFoundException 背后的原因。
    • 我现在已经超越了这个(虽然仍然很好奇为什么它不起作用)但我非常感谢您提供帮助的尝试。请参阅我从 AbstractPreAuthenticatedProcessingFilter 中获取的问题编辑中的代码。我设置了一个空白凭据字符串,但它仍然有效。在我测试时,从 AbstractPreAuthenticatedProcessingFilter 设置身份验证的代码从未被调用(它从未达到我的断点)。
    猜你喜欢
    • 1970-01-01
    • 2011-05-10
    • 1970-01-01
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 2014-08-08
    • 2011-04-08
    • 2022-01-20
    相关资源
    最近更新 更多