简体   繁体   English

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

[英]How to config multiple level authentication for spring boot RESTful web service?

We are building a RESTful webservice using Spring Boot.我们正在使用 Spring Boot 构建一个 RESTful web 服务。 We want to have 2-level authentication to secure the endpoints.我们希望使用 2 级身份验证来保护端点。

First, for every request , we want to check if there is specified apiKey inside the request header, if not, we will deny the request.首先,对于每个请求,我们要检查请求头中是否有指定的 apiKey,如果没有,我们将拒绝该请求。 If the request have a the apiKey, we will go to next authentication using username/password login for some of the requests .如果请求有 apiKey,我们将使用用户名/密码登录对某些请求进行下一次身份验证。 There are public endpoint s which only need apiKey authentication, and private endpoints which require apiKey auth first, then need username/password auth to access them.有只需要 apiKey 身份验证的公共端点,以及首先需要 apiKey 身份验证,然后需要用户名/密码身份验证才能访问它们的私有端点

For the apiKey auth, I copied the code here , I can also find many examples regarding username/password authentication.对于apiKey auth,我在这里复制了代码,我还可以找到很多关于用户名/密码认证的例子。

My question is: how to do Java config inside WebSecurityConfigurerAdapter to combine them together.我的问题是:如何在 WebSecurityConfigurerAdapter 中进行 Java 配置以将它们组合在一起。

Right now I defined 2 config classes extending WebSecurityConfigurerAdapter for those 2 authentication filter, but the request will only go through one of them depending on which one I set as @Order(1).现在我为这 2 个身份验证过滤器定义了 2 个扩展 WebSecurityConfigurerAdapter 的配置类,但请求只会通过其中一个,具体取决于我设置为 @Order(1) 的那个。

Thanks.谢谢。

This entire answer is backed by a working Spring Boot application with unit tests to confirm it.整个答案得到了一个有效的 Spring Boot 应用程序的支持,并带有单元测试来确认它。

If you find this answer helpful, please up vote it.如果你觉得这个答案有帮助,请给它投票。

The short answer is that your security configuration could look like this简短的回答是您的安全配置可能如下所示

    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
    }

}

Let me tell you a little bit what is going on.让我告诉你发生了什么。 I encourage you to review my sample, specifically the unit tests that cover many of your scenarios.我鼓励您查看我的示例,特别是涵盖您的许多场景的单元测试

We have two levels of security 1. Every API must be secured by ApiKey 2. Only some APIs must be secured by UserCredentials我们有两个安全级别 1. 每个 API 都必须由 ApiKey 保护 2. 只有一些 API 必须由 UserCredentials 保护

In my example project I opted for the following solution在我的示例项目中,我选择了以下解决方案

  1. I use a WebSecurityConfigurerAdapter to meet the ApiKey requirement我使用 WebSecurityConfigurerAdapter 来满足 ApiKey 要求

    .authorizeRequests() .anyRequest().hasAuthority("API_KEY")
  2. I use method level security by enabling it我通过启用它来使用方法级别的安全性

    @EnableGlobalMethodSecurity(prePostEnabled = true)

and then requiring it in my controller然后在我的控制器中要求它

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

The ApiKey filter is super simple 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);

    }
}

and the second tier of authentication even simple (and it relies on the first tier to have performed)和第二层身份验证甚至简单(它依赖于第一层执行)

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

    }
}

Please note : How each filter is not concerned about what happens when there is no authentication or insufficient authentication.请注意:当没有认证或认证不足时,每个过滤器如何不关心会发生什么。 That is all taken care of for you.这一切都为您服务。 Your filter only has to validate correct data;您的过滤器只需要验证正确的数据;

Spring, Spring Boot and Spring Security have some stellar testing facilities. Spring、Spring Boot 和 Spring Security 有一些一流的测试设施。

I can invoke api-only endpoint with both level of 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"))
    ;

or I can pass the first level of security and be rejected by the 2nd否则我可以通过第一级安全并被第二级拒绝

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

of course, we always have a happy path当然,我们总是有幸福的道路

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM