Spring websocket ssl和摘要认证

来源:互联网 发布:太极拳威力知乎 编辑:程序博客网 时间:2024/05/20 04:13

前段时间公司的项目要给websocket连接加ssl和digest认证,我们用的是spring websocket的实现。网上介绍了两种给websocket加ssl的方法,一种是websocketClient.setWebsocketFactory(websocketFactory),另一种是websocketClient.getUserProperties().put("org.apache.tomcat.websocket.SSL_CONTEXT", sslContext)。但是这两种方法是针对纯websocket的,而不是spring websocket。前一种方法在spring websocket中没有对应的实现,后一种方法我试过但是没有奏效。后来通过阅读源码发现一个简单有效的办法,在这里分享大家以供参考。

SSL

websocket协议是从http upgrade上来的,websocket的安全和认证也是基于http。给websocket加ssl,实际是把http转变成https,只要让tomcat运行在8443端口(我配的是8443)即可。server的代码如下:

import org.apache.coyote.http11.Http11NioProtocol;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Profile;import org.springframework.core.io.Resource;import org.springframework.scheduling.annotation.EnableScheduling;@Configuration@EnableAutoConfiguration@EnableScheduling@ComponentScan@SpringBootApplicationpublic class WssBrokerApplication {public static void main(String[] args) {SpringApplication.run(WssBrokerApplication.class, args);}@Profile("ssl")@BeanEmbeddedServletContainerCustomizer containerCustomizer(@Value("${keystore.file}") Resource keystoreFile,@Value("${keystore.pass}") String keystorePass) throws Exception {String absoluteKeystoreFile = keystoreFile.getFile().getAbsolutePath();return (ConfigurableEmbeddedServletContainer container) -> {if (container instanceof TomcatEmbeddedServletContainerFactory) {TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;tomcat.addConnectorCustomizers((connector) -> {connector.setPort(8443);connector.setSecure(true);connector.setScheme("https");Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();proto.setSSLEnabled(true);proto.setKeystoreFile(absoluteKeystoreFile);proto.setKeystorePass(keystorePass);proto.setKeystoreType("PKCS12");proto.setKeyAlias("tomcat");});}};}}
配置文件application.porperties放在resources目录下:

spring.profiles.active: sslkeystore.file: demo.keystorekeystore.pass: changeit

Digest

给websocket配置digest认证:

spring security的配置参照上一篇博文digest认证:http://blog.csdn.net/xiaoyaoyulinger/article/details/60881279,然后给websocket加digest支持。

import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.messaging.simp.config.MessageBrokerRegistry;import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;@Configuration@EnableWebSocketMessageBrokerpublic class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer  {@Override    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {messages            .nullDestMatcher().authenticated()            .simpSubscribeDestMatchers("/topic/notification").permitAll()            .simpDestMatchers("/**").authenticated()            .anyMessage().denyAll();    }    @Override    public void configureMessageBroker(MessageBrokerRegistry config) {        config.enableSimpleBroker("/topic");        config.setApplicationDestinationPrefixes("/ws");    }    @Override    public void registerStompEndpoints(StompEndpointRegistry registry) {        registry.addEndpoint("/hpdm-ws").setAllowedOrigins("*").withSockJS();    }    @Bean    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {        ObjectMapper mapper = new ObjectMapper();        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);        MappingJackson2HttpMessageConverter converter =                new MappingJackson2HttpMessageConverter(mapper);        return converter;    }        @Override    protected boolean sameOriginDisabled() {return true;}}

Client

server配置完后,websocket client也要支持ssl和digest。在rest template中配置ssl和digest信息,然后把rest template加到sockJs client中。下面是client的代码:

1. 给rest template配置ssl和digest信息:

import java.security.KeyStore;import javax.net.ssl.SSLContext;import org.apache.http.auth.AuthScope;import org.apache.http.auth.UsernamePasswordCredentials;import org.apache.http.client.CredentialsProvider;import org.apache.http.client.HttpClient;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.conn.ssl.TrustSelfSignedStrategy;import org.apache.http.impl.client.BasicCredentialsProvider;import org.apache.http.impl.client.HttpClients;import org.apache.http.ssl.SSLContexts;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.Resource;import org.springframework.http.client.ClientHttpRequestFactory;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.web.client.RestOperations;import org.springframework.web.client.RestTemplate;import com.hpi.hpdm.rest.digest.HttpComponentsClientHttpRequestFactoryDigestAuth;/** * SSL and digest config for rest template */@Configurationpublic class SSLAndDigestConfig {    @Bean    public RestOperations restOperations(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception {        return new RestTemplate(clientHttpRequestFactory);    }     @Bean    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {        return new HttpComponentsClientHttpRequestFactoryDigestAuth(httpClient);    }    @Bean    public HttpClient httpClient(@Value("${keystore.file}") String file,                                 @Value("${keystore.pass}") String password) throws Exception {KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());Resource resource = new ClassPathResource(file);trustStore.load(resource.getInputStream(), password.toCharArray());        SSLContext sslcontext =                SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();        @SuppressWarnings("deprecation")SSLConnectionSocketFactory sslsf =                new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null,                                               null);        return HttpClients.custom().setDefaultCredentialsProvider(provider()).setSSLSocketFactory(sslsf).useSystemProperties().build();    }    @Bean    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {        return new PropertySourcesPlaceholderConfigurer();    }        private CredentialsProvider provider() {CredentialsProvider provider = new BasicCredentialsProvider();UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin");provider.setCredentials(AuthScope.ANY, credentials);return provider;}}
import java.net.URI;import org.apache.http.HttpHost;import org.apache.http.client.AuthCache;import org.apache.http.client.HttpClient;import org.apache.http.client.protocol.ClientContext;import org.apache.http.impl.auth.DigestScheme;import org.apache.http.impl.client.BasicAuthCache;import org.apache.http.protocol.BasicHttpContext;import org.apache.http.protocol.HttpContext;import org.springframework.http.HttpMethod;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;public class HttpComponentsClientHttpRequestFactoryDigestAuth extends HttpComponentsClientHttpRequestFactory {     public HttpComponentsClientHttpRequestFactoryDigestAuth(HttpClient client) {        super(client);    }     @Override    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {        return createHttpContext(uri);    }     private HttpContext createHttpContext(URI uri) {        // Create AuthCache instance        AuthCache authCache = new BasicAuthCache();        // Generate DIGEST scheme object, initialize it and add it to the local auth cache        DigestScheme digestAuth = new DigestScheme();        // If we already know the realm name        digestAuth.overrideParamter("realm", "myrealm");        HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort());        authCache.put(targetHost, digestAuth);         // Add AuthCache to the execution context        BasicHttpContext localcontext = new BasicHttpContext();        localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);        return localcontext;    }}

client这边也要有配置文件application.properties,指定keystore和password,配置同上。

2. 把rest template加到sockJs client中:

@Autowiredprivate RestOperations rest;SockJsClient sockJsClient;WebSocketStompClient stompClient;List<Transport> transports = new ArrayList<>();final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();            StandardWebSocketClient websocketClient = new StandardWebSocketClient();transports.add(new RestTemplateXhrTransport(rest));transports.add(new WebSocketTransport(websocketClient));sockJsClient = new SockJsClient(transports);stompClient = new WebSocketStompClient(sockJsClient);stompClient.setMessageConverter(new MappingJackson2MessageConverter());

注意,要先加RestTemplateXhrTransport,然后加WebsocketTransport,因为sockJs client会按如下规则去构造URL:http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}。

详情参考http://stackoverflow.com/questions/30413380/websocketstompclient-wont-connect-to-sockjs-endpoint



 
 








0 0
原创粉丝点击