Spring Boot/Security - 我可以使用 X509 证书作为身份验证的额外层吗?

问题描述

我正在构建一个 Android 应用程序,该应用程序与受 Spring Security 保护的 REST API 进行通信。

由于 Android 应用程序是“公开的”并且没有密钥等是安全的,我想创建不同的障碍并使事情变得复杂以尽可能地保护我的 API。

我想增加更多安全性的一种方法是确保调用我的 API 的人拥有证书。我不想在我的 API 信任库中创建数千个证书,所以我只想确保调用者拥有一个我隐藏在我的 Android 应用程序的密钥库中的单个证书。

在我发现的示例中,Spring Security 中的“普通”X509Certificate 身份验证似乎需要每个用户的唯一证书,然后该证书替换基本身份验证或 JWT 身份验证。我想要单独的客户端 JWT 令牌,但要确保每次调用都带有我的一个 Android 应用程序证书,以确保(更多)确保有人从我的 Android 应用程序调用我的 API。

这是可能的还是不是它的用途?

当您创建 RestTemplate 时,您可以使用密钥库和信任库对其进行配置,因此这应该很容易。但至于保护我的 REST API 似乎更困难,因为我想要证书 + JWT 令牌或基本身份验证。

我的 securityconfig 没有使用 XML 配置。我改为扩展 WebSecurityConfigurerAdapter。如果这可以在 configure(HttpSecurity http) 方法中进行配置,那就太好了,但我想也许我可以以某种方式在 OncePerRequestFilter 中实现这一点?也许在我的 JwtAuthFilter 之前配置一个过滤器?

编辑: 在我发现的用于配置 spring 安全性的所有示例中,它们似乎总是使用证书作为身份验证。我只想进行配置,以便当有人调用 example.com/api/** 时,它会检查证书是否得到我的自定义信任存储区的批准(以便我“知道”这可能是来自我的应用程序的调用)但如果有人调用 example.com/website 它应该使用认的 java 信任存储。

如果有人调用 example.com/api/** 我希望我的服务器

  1. 如果证书未在我的自定义信任库中获得批准,请检查证书并终止连接。
  2. 如果证书没问题,请使用 Basic-/JWT-authentication 为用户身份验证建立 https(如果我无法在它已经建立 https 连接之前终止连接,则继续)。

解决方法

我想我明白了。这是我配置它的方式,它似乎可以工作。

"/**" 端点是可以在没有任何特定证书的情况下与任何浏览器一起使用的网站,但它需要管理员权限(您需要以管理员身份登录)。

"/api/**""/connect/**" 端点需要正确的证书、正确的 API 密钥和有效的基本或 JWT 令牌身份验证。

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    http.authorizeRequests()
            .antMatchers("/**").hasRole("ADMIN")
        .and()
            .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/authenticateTheUser")
                .permitAll()
        .and()
            .logout()
                .permitAll().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
    
    http.requestMatchers()
            .antMatchers("/connect/**","/api/**")
        .and()
            .addFilterBefore(new APIKeyFilter(null),UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(new JwtAuthorizationFilter(),BasicAuthenticationFilter.class)
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .httpBasic()
            .authenticationEntryPoint(authenticationEntryPoint)
        .and()
            .authorizeRequests()
            .antMatchers("/connect/**").hasAnyRole("MASTER,APPCALLER,NEGOTIATOR,MEMBER")
            .antMatchers("/api/**").hasAnyRole("MASTER,MEMBER,ANONYMOUS");
    
}

ApiKeyFilter 类用于检查 api-key 并确保调用中使用的证书在我的服务器信任库中得到批准。我只需要配置 api-key 检查,扩展的 X509AuthenticationFilter 将自动检查请求证书。我的 ApiKeyFilter 看起来像这样:

    public class APIKeyFilter extends X509AuthenticationFilter {
    
    private String principalRequestHeader = "x-api-key";
    
    private String apiKey = "XXXX";
    
    public APIKeyFilter(String principalRequestHeader) {
        if (principalRequestHeader != null) {
            this.principalRequestHeader = principalRequestHeader;
        }
        setAuthenticationManager(new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if(authentication.getPrincipal() == null) {
                    throw new BadCredentialsException("Access Denied.");
                }
                String rApiKey = (String) authentication.getPrincipal();
                if (authentication.getPrincipal() != null && apiKey.equals(rApiKey)) {
                    return authentication;
                } else {
                    throw new BadCredentialsException("Access Denied.");
                }
            }
        });
    }
    
    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        return request.getHeader(principalRequestHeader);
    }
    
    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
        if (certificates != null && certificates.length > 0) {
            return certificates[0].getSubjectDN();
        }
        return super.getPreAuthenticatedCredentials(request);
    } 
}

Cred 访问了这些帮助我将事情整合在一起的资源:

Spring Boot - require api key AND x509,but not for all endpoints

spring security http antMatcher with multiple paths