使用Spring Boot实现双向SSL认证

19
我正在创建一些RESTful Web服务,并使用Spring Boot创建嵌入式Tomcat容器。
其中一个要求是实现双向SSL。我一直在查看HttpSecurity对象,并可以使用以下方式使其仅在SSL通道上运行Web服务:
@Override
protected void configure(HttpSecurity http) throws Exception {

    System.out.println("CONFIGURED");

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

我找不到一种方式,使得仅提供有效客户端证书的应用程序才能访问我们的Web服务。

我的SSL知识很基础,因此即使是一个指向正确方向的普通提示也会受益匪浅。

部署该服务的服务器将有各种应用程序 - 这是唯一需要通过双向SSL进行锁定的应用程序。我真正寻找的是一种方法,只允许单个应用程序接受客户端证书。


谢谢回复。提供的链接似乎是针对完全锁定Tomcat 7的,但是将部署此应用程序的服务器是共享资源,因此会在其中混合使用受保护和非受保护的项。我真正需要的是使用Spring Security和客户端证书锁定单个Web应用程序的方法。 - Andrew Mc
所以也许我需要更深入地研究Spring Boot中的嵌入式Tomcat容器。 - Andrew Mc
2个回答

17

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

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

我与您略有不同的地方是,我将我的Spring Boot应用程序打包成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(仅限)的证书/私钥对,而 truststoreFile 包含客户端 SSL 认证所允许的 CA 证书(请注意,您也可以直接将客户端证书添加到该 TrustStore 中)。

如果您正在使用嵌入式 Tomcat 容器与 Spring Boot 应用程序一起使用,则应能够在应用程序的属性文件中配置这些设置,使用以下属性键/值:

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

然后,在我的Web应用程序上,我声明特定的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");
    }

}

我接下来需要声明UserDetailsService bean(例如在我的应用程序的主类中):

@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')")注释的方法,应用程序会在方法级别检查用户是否具有所需的角色。

8
您可以配置clientAuth=want,请参阅Apache Tomcat 8 Configuration Reference
如果您想要SSL堆栈在接受连接之前需要客户端提供有效的证书链,请将其设置为true。 如果您想要SSL堆栈请求客户端证书但未提供证书也不会失败,请将其设置为want。 默认值为false,除非客户端请求使用CLIENT-CERT身份验证保护的资源,否则不需要证书链。
然后使用Spring Security - X.509 Authentication读取客户端证书:
您还可以使用带有“相互验证”的SSL;服务器将在SSL握手期间请求来自客户端的有效证书。 服务器将通过检查其证书是否由可接受的机构签名来对客户端进行身份验证。 如果提供了有效的证书,则可以在应用程序中通过servlet API获取它。 Spring Security X.509模块使用过滤器提取证书。 它将证书映射到应用程序用户,并加载该用户的授予权限集以与标准Spring Security基础架构一起使用。
并且
如果您仍希望SSL连接成功,即使客户端没有提供证书,也可以将clientAuth设置为want。 未提供证书的客户端将无法访问由Spring Security保护的任何对象,除非您使用非X.509身份验证机制(例如表单身份验证)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接