Spring Boot - 需要 api 密钥和 x509,但不适用于所有端点

Java 11、Spring 启动 2.1.3、Spring 5.1.5


我有一个 Spring Boot 项目,其中某些端点由 API 密钥保护。目前使用以下代码效果很好:


@Component("securityConfig")

@ConfigurationProperties("project.security")

@EnableWebSecurity

@Order(1)

public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {


    private static final Logger LOG = LoggerFactory.getLogger(SecurityJavaConfig.class);

    private static final String API_KEY_HEADER = "x-api-key";


    private String apiKey;


    @Override

    protected void configure(HttpSecurity httpSecurity) throws Exception {

        APIKeyFilter filter = new APIKeyFilter(API_KEY_HEADER);

        filter.setAuthenticationManager(authentication -> {

            String apiKey = (String) authentication.getPrincipal();

            if (this.apiKey != null && !this.apiKey.isEmpty() && this.apiKey.equals(apiKey)) {

                authentication.setAuthenticated(true);

                return authentication;

            } else {

                throw new BadCredentialsException("Access Denied.");

            }


        });


        httpSecurity

            .antMatcher("/v1/**")

            .csrf()

            .disable()

            .sessionManagement()

            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

            .and()

            .addFilter(filter)

            .authorizeRequests()

            .anyRequest()

            .authenticated();

    }

}

这成功地需要一个包含 API 密钥的标头,但仅适用于以下端点:/v1/...

我有一个新的要求,需要证书进行身份验证。我按照以下指南在我的项目中进行了 X.509 身份验证设置:

  • 秃顶

  • 迪区

  • 以代码为中心

但是,我遇到了一些问题:

  1. 证书始终是必需的,而不仅仅是/v1/*端点

  2. API 密钥过滤器不再起作用


慕斯709654
浏览 102回答 2
2回答

一只名叫tom的猫

在您的要求中,由于没有角色(不同的客户端具有不同的访问级别),因此不需要 UserDetailService。APIKeyFilter 足以与 X509 和 API 密钥配合使用。考虑APIKeyFilter扩展X509AuthenticationFilter,如果存在没有有效证书的请求,则过滤器链将被破坏,并且将发送403/的错误响应。 如果证书有效,则过滤器链继续并进行身份验证。而验证我们所拥有的只有来自身份验证对象的两个方法 - - 。其中主题是 (EMAIL=, CN=, OU=, O=, L=, ST=, C=) (APIKeyFilter 应配置为返回主体和凭证对象) 您可以使用主体(您的 API 密钥)来验证 api 密钥由客户发送。您 可以使用凭据(证书主题)作为增强功能来单独识别每个客户端,如果需要,您可以为不同的客户端授予不同的权限。ForbiddengetPrincipal()header:"x-api-key"getCredential()certificate subject回顾您的要求1. API V1 - 仅当证书和 API 密钥有效时才可访问。2. 其他API - 无限制

繁星淼淼

public class APIKeyFilter extends X509AuthenticationFilter{    private String principalRequestHeader;    public APIKeyFilter(String principalRequestHeader)     {        this.principalRequestHeader = principalRequestHeader;    }    @Override    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request)    {        return request.getHeader(principalRequestHeader);    }    @Override    protected Object getPreAuthenticatedCredentials(HttpServletRequest request)    {        X509Certificate[] certs = (X509Certificate[]) request                .getAttribute("javax.servlet.request.X509Certificate");        if(certs.length > 0)        {            return certs[0].getSubjectDN();        }        return super.getPreAuthenticatedCredentials(request);    }}@Configuration@EnableWebSecuritypublic class SpringSecurityConfig extends WebSecurityConfigurerAdapter {    private static final String API_KEY_HEADER = "x-api-key";    private String apiKey = "SomeKey1234567890";    @Override    protected void configure(HttpSecurity http) throws Exception     {        APIKeyFilter filter = new APIKeyFilter(API_KEY_HEADER);        filter.setAuthenticationManager(authentication -> {            if(authentication.getPrincipal() == null) // required if you configure http            {                throw new BadCredentialsException("Access Denied.");            }            String apiKey = (String) authentication.getPrincipal();            if (authentication.getPrincipal() != null && this.apiKey.equals(apiKey))             {                authentication.setAuthenticated(true);                return authentication;            }            else            {                throw new BadCredentialsException("Access Denied.");            }        });        http.antMatcher("/v1/**")                .csrf().disable()                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)            .and()                .addFilter(filter)                .authorizeRequests()                .anyRequest()                .authenticated();    }    @Bean    public PasswordEncoder passwordEncoder()     {        return new BCryptPasswordEncoder();    }}验证 API 响应https - 用于数据加密(服务器向客户端发送的 ssl 证书)X509 - 用于客户端识别(使用服务器 ssl 证书生成的 ssl 证书,但不同客户端不同)API key - 用于安全检查的共享密钥。出于验证目的,假设您有 3 个版本,如下所示@RestControllerpublic class HelloController{    @RequestMapping(path = "/v1/hello")    public String helloV1()    {        return "HELLO Version 1";    }    @RequestMapping(path = "/v0.9/hello")    public String helloV0Dot9()    {        return "HELLO Version 0.9";    }    @RequestMapping(path = "/v0.8/hello")    public String helloV0Dot8()    {        return "HELLO Version 0.8";    }}下面给出不同情况下的响应。CASE 1.a 版本 1,标头中包含有效的 X509 和 API 密钥curl -ik --cert pavel.crt --key myPrivateKey.pem -H "x-api-key:SomeKey1234567890" "https://localhost:8443/v1/hello"回复HTTP/1.1 200HELLO Version 1CASE 1.b 版本 1 仅适用于 X509(无 API 密钥)curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1/hello"回复HTTP/1.1 403{"timestamp":"2019-09-13T11:53:29.269+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/v1/hello"}注意:在您的情况下,有两种类型的证书i。带有 X509 的客户端证书ii:如果客户端不包含证书,则将使用服务器中使用的数据交换证书,即不带 X509 的证书2. 版本 X 不带 X509,标头中不带 API 密钥。curl "https://localhost:8443/v0.9/hello"如果服务器证书是自签名证书(没有 CA 即证书颁发机构,证书无效)curl performs SSL certificate verification by default, using a "bundle" of Certificate Authority (CA) public keys (CA certs). If the default bundle file isn't adequate, you can specify an alternate file using the --cacert option.If this HTTPS server uses a certificate signed by a CA represented in the bundle, the certificate verification probably failed due to a problem with the certificate (it might be expired, or the name might not match the domain name in the URL).If you'd like to turn off curl's verification of the certificate, use the -k (or --insecure) option.如果服务器 SSL 证书有效(CA 认证)则curl "https://localhost:8443/v0.9/hello"你好版本0.9curl "https://localhost:8443/v0.8/hello"你好版本0.8注意:如果您在开发环境中没有 CA 认证的 SSL 证书,请测试 Hack使用服务器证书(.crt)和serverPrivateKey(.pem 文件)以及请求,如下所示curl -ik --cert server.crt --key serverPrivateKey.pem "https://localhost:8443/v0.9/hello"这也可以在 Mozilla 中进行验证(对于自签名证书),并且可以在 google chrome 中进行相同的验证(如果 CA 认证的 SSL)给出的屏幕截图,在第一次访问期间添加服务器发送的证书后。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java