websocket と STOMP を使用して春を設定しようとしています。
クライアントで、ヘッダー変数 'simpSessionId':%session_id% を送信します
ただし、メッセージを受信すると、Spring は常に提供されたヘッダーを nativeHeaders と呼ばれるキーに配置し、デフォルトの simpSessionId をヘッダー ルートに配置します。
{simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={SPRING.SESSION.ID=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], Content-Type=[application/json], X-Requested-With=[XMLHttpRequest], simpSessionId=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], accept-version=[1.2,1.1,1.0], heart-beat=[0,0], destination=[/mobile-server/ping], content-length=[15]}, simpSessionAttributes={}, simpSessionId=1, simpDestination=/mobile-server/ping}
代わりに提供されたセッションIDを春に取得させる方法はありますか?
編集済み
OK、携帯電話アプリとウェブサイトが同じサーバーにアクセスしています。携帯電話アプリで Webocket をセットアップできる必要があります。
携帯電話アプリで、従来の REST エンドポイントを介してサーバーにログインし、成功すると応答でセッション ID を受け取ります。
携帯電話で webstomp-client、Spring 4.1.9、Spring Security 4.1、Spring Session 1.2.0 を使用しています。
トークンを使用してソケット CONNECT で STOMP Websocket にログインするのが理想的ですが、webstomp-client は CONNECT でカスタム ヘッダーを渡さないため、現在のところそれが不可能であることを理解しています。
2 つの問題があります。
後続のリクエストで REST ログインで取得したセッション ID を渡すにはどうすればよいですか? SPRING.SESSION.ID などのヘッダーを追加しようとしましたが、コードをステップ実行すると、メッセージ処理が常に 1、2 などにデフォルト設定されている simpSessionId に戻ることがわかります。 AbstractSessionWebsocketMessageBrokerConfigurer を拡張しようとしましたが、私のセッションIDを取得しません。常に空のsimpSessionAttributesを調べます。
コードは、Web ブラウザーのシナリオである http セッションを取得しようとしているようにも見えます。これは無視するしかないと思います
セッションの有効期限が切れます。有効期限が切れた可能性のあるセッションの戦略は何ですか? remember-me スタイルの認証トークンも渡すべきではないですか? それとも、永続的なステートレス セッションに依存する必要がありますか? これは私には明確ではなく、この側面は文書化されていないようです。
明らかに、私は何か非常に間違ったことをしています。これが私の設定です:
@Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds=1200) public class SessionConfig {
@Inject
ContentNegotiationManager contentNegotiationManager;
@Bean
public RedisConnectionFactory redisConnectionFactory(
@Value("${spring.redis.host}") String host,
@Value("${spring.redis.password}") String password,
@Value("${spring.redis.port}") Integer port) {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setUsePool(true);
redis.setHostName(host);
redis.setPort(port);
redis.setPassword(password);
redis.afterPropertiesSet();
return redis;
}
@Bean
public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public <S extends ExpiringSession>SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter(SessionRepository<S> sessionRepository) {
return new SessionRepositoryFilter<S>(sessionRepository);
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public HttpSessionStrategy httpSessionStrategy(){
return new SmartSessionStrategy();
}
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setUseSecureCookie(true);
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
}
===
public class SessionWebApplicationInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionWebApplicationInitializer() {
}
public SessionWebApplicationInitializer(Class<?>... configurationClasses) {
super(configurationClasses);
}
@Override
protected void beforeSessionRepositoryFilter(ServletContext servletContext) {
Dynamic registration = servletContext.addFilter("openSessionInViewFilter", new OpenSessionInViewFilter());
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for openSessionInViewFilter. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(false);
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, false,"/*");
}
}
==
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebsocketMessageBrokerConfigurer<S>{
@Inject
SessionRepository<S> sessionRepository;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/mobile-server");
config.setUserDestinationPrefix("/mobile-user");
}
@Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setHandshakeHandler(new SessionHandShakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOrigins("*")
.withSockJS()
.setSessionCookieNeeded(false)
;
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(512 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(40000);
}
@Bean
public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, UsorManager userMgr) {
return new WebSocketConnectHandler<S>(messagingTemplate, userMgr);
}
@Bean
public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, WebSocketManager repository) {
return new WebSocketDisconnectHandler<S>(messagingTemplate, repository);
}
}
====
@Configuration
public class WebSocketSecurity extends AbstractSecurityWebSocketMessageBrokerConfigurer{
ApplicationContext context = null;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().permitAll()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/mobile-server/ping").authenticated()
.simpDestMatchers("/mobile-server/csrf").authenticated()
.simpDestMatchers("/mobile-server/**").hasRole("ENDUSER")
.simpSubscribeDestMatchers("/user/**", "/topic/**").hasRole("ENDUSER")
.anyMessage().denyAll();
}
}
=== 簡潔にするために、ここにあるいくつかの追加のセキュリティ構成を削除しました。
@Configuration @EnableWebSecurity @Order(100) public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REMEMBER_ME_COOKIE = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
@Inject
FilterInvocationSecurityMetadataSource securityMetadataSource;
@Inject
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setSaltSource(saltSource);
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setMessageSource(messages);
auth.authenticationProvider(provider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter() throws Exception{
return new AuthenticationTokenProcessingFilter(authenticationManagerBean());
}
@Bean
public FilterSecurityInterceptor myFilterSecurityInterceptor(
AuthenticationManager authenticationManager,
AccessDecisionManager accessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource){
FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
interceptor.setAuthenticationManager(authenticationManager);
interceptor.setAccessDecisionManager(accessDecisionManager);
interceptor.setSecurityMetadataSource(securityMetadataSource);
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
@Bean
public AccessDecisionManager accessDecisionManager(SiteConfig siteConfig){
URLBasedSecurityExpressionHandler expressionHandler = new URLBasedSecurityExpressionHandler();
expressionHandler.setSiteConfig(siteConfig);
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(expressionHandler);
return new AffirmativeBased(Lists.newArrayList(
webExpressionVoter,
new RoleVoter(),
new AuthenticatedVoter()
));
}
public PasswordFixingAuthenticationProvider customAuthenticationProvider(PasswordEncoder passwordEncoder, SaltSource saltSource){
PasswordFixingAuthenticationProvider provider = new PasswordFixingAuthenticationProvider();
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setSaltSource(saltSource);
return provider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class)
.antMatcher("/ws/**")
.exceptionHandling()
.accessDeniedPage("/mobile/403")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ws").permitAll()
.antMatchers("/ws/websocket").permitAll()
.antMatchers("/ws/**").denyAll();
.anyRequest().requiresSecure()
;
}
}
===
public class SmartSessionStrategy implements HttpSessionStrategy {
private HttpSessionStrategy browser;
private HttpSessionStrategy api;
private RequestMatcher browserMatcher = null;
public SmartSessionStrategy(){
this.browser = new CookieHttpSessionStrategy();
HeaderHttpSessionStrategy headerSessionStrategy = new HeaderHttpSessionStrategy();
headerSessionStrategy.setHeaderName(CustomSessionRepositoryMessageInterceptor.SPRING_SESSION_ID_ATTR_NAME);
this.api = headerSessionStrategy;
}
@Override
public String getRequestedSessionId(HttpServletRequest request) {
return getStrategy(request).getRequestedSessionId(request);
}
@Override
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onNewSession(session, request, response);
}
@Override
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onInvalidateSession(request, response);
}
private HttpSessionStrategy getStrategy(HttpServletRequest request) {
if(this.browserMatcher != null)
return this.browserMatcher.matches(request) ? this.browser : this.api;
return SecurityRequestUtils.isApiRequest(request) ? this.api : this.browser;
}
}