如何为 Spring Boot RESTful Web 服务配置多级身份验证?

L L*_* Li 0 java authentication spring-security spring-boot

我们正在使用 Spring Boot 构建一个 RESTful web 服务。我们希望使用 2 级身份验证来保护端点。

首先,对于每个请求,我们要检查请求头中是否有指定的 apiKey,如果没有,我们将拒绝该请求。如果请求有 apiKey,我们将使用用户名/密码登录对某些请求进行下一次身份验证。有只需要 apiKey 身份验证的公共端点,以及首先需要 apiKey 身份验证,然后需要用户名/密码身份验证才能访问它们的私有端点

对于apiKey auth,我在这里复制了代码,我还可以找到很多关于用户名/密码认证的例子。

我的问题是:如何在 WebSecurityConfigurerAdapter 中进行 Java 配置以将它们组合在一起。

现在我为这 2 个身份验证过滤器定义了 2 个扩展 WebSecurityConfigurerAdapter 的配置类,但请求只会通过其中一个,具体取决于我设置为 @Order(1) 的那个。

谢谢。

Fil*_*are 5

整个答案得到了一个有效的 Spring Boot 应用程序的支持,并带有单元测试来确认它。

如果你觉得这个答案有帮助,请给它投票。

简短的回答是您的安全配置可能如下所示

    http
        .sessionManagement()
            .disable()
        //application security
        .authorizeRequests()
            .anyRequest().hasAuthority("API_KEY")
            .and()
        .addFilterBefore(new ApiKeyFilter(), HeaderWriterFilter.class)
        .addFilterAfter(new UserCredentialsFilter(), ApiKeyFilter.class)
        .csrf().ignoringAntMatchers(
            "/api-key-only",
            "/dual-auth"
    )
        ;
        // @formatter:on
    }

}
Run Code Online (Sandbox Code Playgroud)

让我告诉你发生了什么。我鼓励您查看我的示例,特别是涵盖您的许多场景的单元测试

我们有两个安全级别 1. 每个 API 都必须由 ApiKey 保护 2. 只有一些 API 必须由 UserCredentials 保护

在我的示例项目中,我选择了以下解决方案

  1. 我使用 WebSecurityConfigurerAdapter 来满足 ApiKey 要求

    .authorizeRequests()
        .anyRequest().hasAuthority("API_KEY")
    
    Run Code Online (Sandbox Code Playgroud)
  2. 我通过启用它来使用方法级别的安全性

    @EnableGlobalMethodSecurity(prePostEnabled = true)

然后在我的控制器中要求它

    @PreAuthorize("hasAuthority('USER_CREDENTIALS')")
    public String twoLayersOfAuth() {
        //only logic here
    }
Run Code Online (Sandbox Code Playgroud)

ApiKey 过滤器超级简单

public class ApiKeyFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

        final String authorization = request.getHeader("Authorization");
        final String prefix = "ApiKey ";
        if (hasText(authorization) && authorization.startsWith(prefix)) {
            String key = authorization.substring(prefix.length());
            if ("this-is-a-valid-key".equals(key)) {
                RestAuthentication<SimpleGrantedAuthority> authentication = new RestAuthentication<>(
                    key,
                    Collections.singletonList(new SimpleGrantedAuthority("API_KEY"))
                );
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);

    }
}
Run Code Online (Sandbox Code Playgroud)

和第二层身份验证甚至简单(它依赖于第一层执行)

public class UserCredentialsFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        final String userCredentials = request.getHeader("X-User-Credentials");
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if ("valid-user".equals(userCredentials) && authentication instanceof RestAuthentication) {
            RestAuthentication<SimpleGrantedAuthority> restAuthentication =
                (RestAuthentication<SimpleGrantedAuthority>)authentication;
            restAuthentication.addAuthority(new SimpleGrantedAuthority("USER_CREDENTIALS"));
        }
        filterChain.doFilter(request, response);

    }
}
Run Code Online (Sandbox Code Playgroud)

请注意:当没有认证或认证不足时,每个过滤器如何不关心会发生什么。这一切都为您服务。您的过滤器只需要验证正确的数据;

Spring、Spring Boot 和 Spring Security 有一些一流的测试设施。

我可以调用具有两个安全级别的 api-only 端点

    mvc.perform(
        post("/api-key-only")
            .header("Authorization", "ApiKey this-is-a-valid-key")
            .header("X-User-Credentials", "valid-user")
    )
        .andExpect(status().isOk())
        .andExpect(authenticated()
            .withAuthorities(
                asList(
                    new SimpleGrantedAuthority("API_KEY"),
                    new SimpleGrantedAuthority("USER_CREDENTIALS")
                )
            )
        )
        .andExpect(content().string("API KEY ONLY"))
    ;
Run Code Online (Sandbox Code Playgroud)

否则我可以通过第一级安全并被第二级拒绝

    mvc.perform(
        post("/dual-auth")
            .header("Authorization", "ApiKey this-is-a-valid-key")
    )
        .andExpect(status().is4xxClientError())
        .andExpect(authenticated()
            .withAuthorities(
                asList(
                    new SimpleGrantedAuthority("API_KEY")
                )
            )
        )
    ;
Run Code Online (Sandbox Code Playgroud)

当然,我们总是有幸福的道路

    mvc.perform(
        post("/dual-auth")
            .header("Authorization", "ApiKey this-is-a-valid-key")
            .header("X-User-Credentials", "valid-user")
    )
        .andExpect(status().isOk())
        .andExpect(content().string("DUAL AUTH"))
        .andExpect(authenticated()
            .withAuthorities(
                asList(
                    new SimpleGrantedAuthority("API_KEY"),
                    new SimpleGrantedAuthority("USER_CREDENTIALS")
                )
            )
        )
    ;
Run Code Online (Sandbox Code Playgroud)

  • 更新:我发现我需要在 httpSecury 配置中禁用 csrf 才能使 POST 方法工作。像“httpSecurity.csrf().disable()”。 (2认同)