本文介绍了如何为Spring Boot RESTful Web服务配置多级身份验证?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

We are building a RESTful webservice using Spring Boot. We want to have 2-level authentication to secure the endpoints.

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

First, for every request, we want to check if there is specified apiKey inside the request header, if not, we will deny the request. If the request have a the apiKey, we will go to next authentication using username/password login for some of the requests. There are public endpoints which only need apiKey authentication, and private endpoints which require apiKey auth first, then need username/password auth to access them.

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

For the apiKey auth, I copied the code here, I can also find many examples regarding username/password authentication.

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

My question is: how to do Java config inside WebSecurityConfigurerAdapter to combine them together.

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

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

谢谢.

推荐答案

整个答案由一个运行正常的Spring Boot应用程序提供支持,并进行了单元测试以确认该问题.

This entire answer is backed by a working Spring Boot application with unit tests to confirm it.

如果您认为此答案有帮助,请对其进行投票.

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.

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

We have two levels of security1. Every API must be secured by ApiKey2. Only some APIs must be secured by UserCredentials

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

In my example project I opted for the following solution

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

  1. I use a WebSecurityConfigurerAdapter to meet the ApiKey requirement

.authorizeRequests()
    .anyRequest().hasAuthority("API_KEY")

  • 我通过启用方法级别的安全性

  • 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
        }
    

    ApiKey过滤器非常简单

    The ApiKey filter is super simple

    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和Spring Security具有一些出色的测试工具.

    Spring, Spring Boot and Spring Security have some stellar testing facilities.

    我可以在两种安全级别下调用仅api端点

    I can invoke api-only endpoint with both level of security

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

    这篇关于如何为Spring Boot RESTful Web服务配置多级身份验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!