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

Spring Boot/Security - can I use X509 Certificate as an extra layer in authentication?

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

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

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

在示例中,我发现它似乎是 Spring 中的“正常”X509Certificate 身份验证安全性要求每个用户都有一个唯一的证书,然后此证书将替换 Basic auth 或 JWT auth。我想要单独的客户端 JWT 令牌,但要确保每次调用都带上我的一个 Android 应用程序证书,以确保(更多)有人从我的 Android 应用程序调用我的 API。

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

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

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

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

如果有人调用示例。com/api/** 我希望我的服务器

  1. 如果证书在我的自定义信任库中未获批准,请检查证书并终止连接。
  2. 如果证书没问题,请使用 Basic-/JWT-authentication.
  3. 建立 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 class 是检查 api 密钥并确保调用中使用的证书在我的服务器信任库中得到批准的过滤器。 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 归功于这些帮助我整理东西的资源: