使用 spring 启动实现 2 路 SSL

Implementing 2 way SSL using spring boot

我正在创建一些 restful 网络服务并使用 Spring-Boot 创建嵌入式 tomcat 容器。

要求之一是实现 2 路 SSL。我一直在查看 HttpSecurity 对象,并且只能通过 SSL 通道将其 运行 Web 服务获取:-

@Override
protected void configure(HttpSecurity http) throws Exception {

    System.out.println("CONFIGURED");

    http
        // ...
        .requiresChannel()
            .anyRequest().requiresSecure();
}

我似乎找不到一种方法,使网络服务只能由提供有效客户端证书的应用程序访问。

我只有 SSL 的基本知识,所以即使是正确方向的一般指导也会受到赞赏。

要部署到的服务器将有多种应用程序 - 这是唯一需要使用 2-way SSL 锁定的应用程序。我真正想要的是一种锁定单个应用程序以仅接受客户端证书的方法。

您可以配置 clientAuth=want,请参阅 Apache Tomcat 8 Configuration Reference

Set to true if you want the SSL stack to require a valid certificate chain from the client before accepting a connection. Set to want if you want the SSL stack to request a client Certificate, but not fail if one isn't presented. A false value (which is the default) will not require a certificate chain unless the client requests a resource protected by a security constraint that uses CLIENT-CERT authentication.

然后用Spring Security - X.509 Authentication读取客户端证书:

You can also use SSL with "mutual authentication"; the server will then request a valid certificate from the client as part of the SSL handshake. The server will authenticate the client by checking that its certificate is signed by an acceptable authority. If a valid certificate has been provided, it can be obtained through the servlet API in an application. Spring Security X.509 module extracts the certificate using a filter. It maps the certificate to an application user and loads that user’s set of granted authorities for use with the standard Spring Security infrastructure.

clientAuth can also be set to want if you still want SSL connections to succeed even if the client doesn’t provide a certificate. Clients which don’t present a certificate won’t be able to access any objects secured by Spring Security unless you use a non-X.509 authentication mechanism, such as form authentication.

我遇到了类似的问题,想分享我的解决方案。

首先,您需要了解 SSL 证书身份验证将在您的 Web 服务器端处理(参见 dur 的解释,使用“clientAuth=want”设置)。 然后,必须配置您的 Web 应用程序以处理提供的(和允许的)证书,将其映射到用户等。

我与您的细微差别是,我将我的 spring 启动应用程序打包到 WAR 存档中,然后将其部署到现有的 Tomcat 应用程序服务器上。

我的 Tomcat 的 server.xml 配置文件定义了一个 HTTPS 连接器,如下所示:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
    maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
    keystoreFile="/opt/tomcat/conf/key-stores/ssl-keystore.jks"
    keystorePass=“some-complex-password“
    clientAuth="want" sslProtocol="TLS"
    truststoreFile="/opt/tomcat/conf/trust-stores/ssl-truststore.jks"
    truststorePass=“some-other-complex-password” />

避免混淆的小注释:keystoreFile 包含用于 SSL(仅)的 certificate/private 密钥对,而 truststoreFile 包含允许的用于客户端 SSL 身份验证的 CA 证书(请注意,您还可以添加客户端证书直接进入那个信任商店)。

如果您在 spring 引导应用程序中使用嵌入式 tomcat 容器,您应该能够使用以下 属性 在应用程序的属性文件中配置这些设置key/values:

server.ssl.key-store=/opt/tomcat/conf/key-stores/ssl-keystore.jks
server.ssl.key-store-password=some-complex-password
server.ssl.trust-store=/opt/tomcat/conf/trust-stores/ssl-truststore.jks
server.ssl.trust-store-password=some-other-complex-password
server.ssl.client-auth=want

然后,在我的网络应用程序中,我声明了一个特定的 SSL 配置如下:

@Configuration
@EnableWebSecurity
//In order to use @PreAuthorise() annotations later on...
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SSLAuthConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${allowed.user}")
    private String ALLOWED_USER;

    @Value("${server.ssl.client.regex}")
    private String CN_REGEX;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure (final HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                .antMatchers("/url-path-to-protect").authenticated() //Specify the URL path(s) requiring authentication...
            .and()
                .x509() //... and that x509 authentication is enabled
                    .subjectPrincipalRegex(CN_REGEX)
                    .userDetailsService(userDetailsService);
    }

    @Autowired
    //Simplified case, where the application has only one user...
    public void configureGlobal (final AuthenticationManagerBuilder auth) throws Exception {
        //... whose username is defined in the application's properties.
        auth
            .inMemoryAuthentication()
                .withUser(ALLOWED_USER).password("").roles("SSL_USER");
    }

}

然后我需要声明 UserDetailsS​​ervice bean(例如在我的应用程序的主 class 中):

@Value("${allowed.user}")
private String ALLOWED_USER;

@Bean
public UserDetailsService userDetailsService () {

    return new UserDetailsService() {

        @Override
        public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
            if (username.equals(ALLOWED_USER)) {
                final User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_SSL_USER"));
                return user;
            }
            return null;
        }
    };
}

就是这样!然后,我可以将 @PreAuthorize(“hasRole(‘ROLE_SSL_USER’)”) 注释添加到我想要保护的方法中。

总结一下,认证流程如下:

  1. 用户提供 SSL 证书;
  2. Tomcat 根据其信任库进行验证;
  3. 自定义 WebSecurityConfigurerAdapter 从证书的 CN 中检索“用户名”;
  4. 应用程序验证与检索到的用户名关联的用户;
  5. 在方法级别,如果使用@PreAuthorize("hasRole('SSL_USER')") 注释,应用程序将检查用户是否具有所需的角色。