Spring websocket ssl和摘要认证

前段时间公司的项目要给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
@SpringBootApplication
public class WssBrokerApplication {
	public static void main(String[] args) {
		SpringApplication.run(WssBrokerApplication.class, args);
	}

	@Profile("ssl")
	@Bean
	EmbeddedServletContainerCustomizer 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: ssl
keystore.file: demo.keystore
keystore.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
@EnableWebSocketMessageBroker
public 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
 */
@Configuration
public 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中:

@Autowired
private 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



 
 








  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值