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

时间:2019-01-10 17:46:30

标签: java spring-boot authentication spring-security

我们正在使用Spring Boot构建RESTful Web服务。我们希望进行2级身份验证以保护端点。

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

对于apiKey身份验证,我复制了代码here,我还可以找到许多有关用户名/密码身份验证的示例。

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

现在,我为这2个身份验证筛选器定义了2个扩展WebSecurityConfigurerAdapter的配置类,但是请求将仅通过其中之一,具体取决于我将哪个设置为@Order(1)。

谢谢。

1 个答案:

答案 0 :(得分:0)

这个完整的答案由运行中的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
    }

}

让我告诉您发生了什么事。我鼓励您查看我的样本,尤其是涵盖您许多情况的unit tests

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

在我的example project中,我选择了以下解决方案

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

    .authorizeRequests()
        .anyRequest().hasAuthority("API_KEY")
    
  2. 我通过启用方法级别的安全性

    @EnableGlobalMethodSecurity(prePostEnabled = true)

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

    @PreAuthorize("hasAuthority('USER_CREDENTIALS')")
    public String twoLayersOfAuth() {
        //only logic here
    }

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);

    }
}

,第二层身份验证甚至很简单(并且依赖于第一层身份执行)

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);

    }
}

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

Spring,Spring Boot和Spring Security具有一些出色的测试工具。

我可以调用具有两种安全级别的仅api端点

    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"))
    ;

或者我可以通过第一级安全保护并被第二级拒绝

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

当然,我们总有一条快乐的路

    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")
                )
            )
        )
    ;