How to prevent Redis writes for anonymous user sessions - spring-boot

I have this sample application:
package com.example.session;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class DemoRedisDataSessionApplication {
#Configuration
#EnableWebSecurity
#EnableRedisHttpSession(redisNamespace = "demo-redis-data-session")
public static class AppConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("0000").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and()
.authorizeRequests().antMatchers("/ping").permitAll().and()
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
#RestController
public static class AppController {
#GetMapping("/ping")
public String ping() {
return "pong";
}
#GetMapping("/secured")
public String secured() {
return "secured";
}
}
public static void main(String[] args) {
SpringApplication.run(DemoRedisDataSessionApplication.class, args);
}
}
When I hit /secured I get 302 redirected to the /login form, which is what I expect if I am not logged in, but I get some unwanted entries in Redis:
127.0.0.1:6379> keys *
1) "spring:session:demo-redis-data-session:sessions:expires:dbb124b9-c37d-454c-8d67-409f28cb88a6"
2) "spring:session:demo-redis-data-session:expirations:1515426060000"
3) "spring:session:demo-redis-data-session:sessions:dbb124b9-c37d-454c-8d67-409f28cb88a6"
I don't want to create this data for every anonymous user (read crawler), so is there a way to prevent these Redis entries when hitting a secured endpoint/page with an anonymous user?
Additional data used for this sample project
 docker-compose.yml
version: "2"
services:
redis:
image: redis
ports:
- "6379:6379"
Spring Boot version
1.5.9.RELEASE

This is not the optimal solution since it creates only one session for all crawlers, but at least I don't get Redis full of unwanted session.
import lombok.extern.log4j.Log4j;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#Log4j
#Component
public class CrawlerManagerSessionStrategyWrapper implements MultiHttpSessionStrategy {
private CookieHttpSessionStrategy delegate;
private volatile String crawlerSessionId;
public CrawlerManagerSessionStrategyWrapper() {
this.delegate = new CookieHttpSessionStrategy();
}
public String getRequestedSessionId(HttpServletRequest request) {
String sessionId = getSessionIdForCrawler(request);
if (sessionId != null)
return sessionId;
else {
return delegate.getRequestedSessionId(request);
}
}
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
delegate.onNewSession(session, request, response);
if (isCrawler(request)) {
crawlerSessionId = session.getId();
}
}
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
delegate.onInvalidateSession(request, response);
}
public HttpServletRequest wrapRequest(HttpServletRequest request, HttpServletResponse response) {
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request, HttpServletResponse response) {
return response;
}
private String getSessionIdForCrawler(HttpServletRequest request) {
if (isCrawler(request)) {
SessionRepository<Session> repo = (SessionRepository<Session>) request.getAttribute(SessionRepository.class.getName());
if (crawlerSessionId != null && repo != null) {
Session session = repo.getSession(crawlerSessionId);
if (session != null) {
return crawlerSessionId;
}
}
}
return null;
}
private boolean isCrawler(HttpServletRequest request) {
// Here goes the logic to understand if the request comes from a crawler, for example by checking the user agent.
return true;
}
}
The only thing to implement is the isCrawler method to state if the request comes from a crawler.

Related

Spring websocket - stomp - Unable to receive messages for user mapping

I am trying to send one to one messages between the clients I am able to send boradcast messages and app destination messages (validated through debug). But I am unable to to do one to one communication.
Here my server side implementation :
package com.example.socketdemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Autowired
private ChannelInterceptor channelInterceptor;
#Autowired
private HandshakeInterceptorTest handshakeInterceptorTest;
#Autowired
private CustomHandshakeHandler customHandshakeHandler;
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic/user-messages");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/test")
.addInterceptors(handshakeInterceptorTest)
.setHandshakeHandler(customHandshakeHandler)
.setAllowedOrigins("http://127.0.0.1:5500")
.withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// Add our interceptor for authentication/authorization
registration
.interceptors(channelInterceptor);
}
}
package com.example.socketdemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
#Controller
public class SocketController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/send.message")
public void sendMessage(#Payload MessageDto message) {
String to = message.getTo();
simpMessagingTemplate.convertAndSendToUser(to, "/my.messages", message);
}
}
package com.example.socketdemo.config;
import lombok.Data;
#Data
public class MessageDto {
String from;
String message;
String to;
}
package com.example.socketdemo.config;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
#Component
public class HandshakeInterceptorTest implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("before handshake");
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
String id = servletServerHttpRequest.getServletRequest().getSession().getId();
attributes.put("sessionId", id);
return true;
}
#Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("after handshake");
}
}
package com.example.socketdemo.config;
import com.sun.security.auth.UserPrincipal;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;
#Component
public class CustomHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
String userId = UUID.randomUUID().toString();
System.out.println("created new user with user id : " + userId);
return new UserPrincipal(userId);
}
}
package com.example.socketdemo.config;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import java.util.Map;
#Service
public class ChannelInterceptor implements org.springframework.messaging.support.ChannelInterceptor {
// Processes a message before sending it
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
System.out.println("here in presend");
MessageHeaders headers = message.getHeaders();
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
MultiValueMap<String, String> multiValueMap = headers.get(StompHeaderAccessor.NATIVE_HEADERS, MultiValueMap.class);
System.out.println(multiValueMap);
Map<String, Object> sessionAttributes = accessor.getSessionAttributes();
System.out.println(sessionAttributes);
System.out.println("user : " + accessor.getUser());
/* if (accessor.getCommand() == StompCommand.CONNECT) {
accessor.setUser(new User("omegleUser"));
}*/
System.out.println("exit in presend");
return message;
}
#Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
System.out.println("post send");
}
#Override
public boolean preReceive(MessageChannel channel) {
System.out.println("pre receive");
return true;
}
#Override
public Message<?> postReceive(Message<?> message, MessageChannel channel) {
System.out.println("post receive");
return message;
}
#Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
System.out.println("after send completion");
}
#Override
public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
System.out.println("after receive completion");
}
}
client side implementation :
let sock = new SockJS("http://localhost:8080/test");
let client = Stomp.over(sock);
client.connect({}, frame => {
console.log(frame+ "connected");
client.subscribe("/user/my.messages");
});
further I am trying to send message to a client using command :
client.send("/app/send.message", {}, JSON.stringify({"from" : "sender", "message" : "Hey user, was up", "to": "76eb1343-ee50-42c4-90ab-c0ace808c219"}));
All working fine untill
simpMessagingTemplate.convertAndSendToUser(to, "/my.messages", message);
but I am not receiving any message on the receiver side

Error during WebSocket handshake: Unexpected response code: 200 In Jhipster application

I am trying to connect to websocket in my existing Jhipster application which is build using Java spring and Angular 8. At the time of creation of the project I didn't select the websocket option and now there is a need to implement the websockets in the project. I took the help from "https://www.jhipster.tech/using-websockets/" and tried to implement the same solution but I am getting the handshake failure message.
I am using STOMP and sockJS for in websockets.
Please let me know what could be the reasons for handshake failure.
Here is the websocket configuration.java file.
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import hr.nic.vc.security.AuthoritiesConstants;
import java.security.Principal;
import org.springframework.http.server.*;
import java.util.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.*;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config)
{
config.enableSimpleBroker("/topic");
//config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry
registry) {
registry.addEndpoint("/websocket/blog").setHandshakeHandler(defaultHandshakeHandler())
.setAllowedOrigins("*").withSockJS();
}
#Bean
public HandshakeInterceptor httpSessionHandshakeInterceptor() {
return new HandshakeInterceptor() {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
attributes.put(IP_ADDRESS, servletRequest.getRemoteAddress());
}
return true;
}
#Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
};
}
private DefaultHandshakeHandler defaultHandshakeHandler() {
return new DefaultHandshakeHandler() {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS));
principal = new AnonymousAuthenticationToken("WebsocketConfiguration", "anonymous", authorities);
}
return principal;
}
};
}
}
And this is the websocket security file.
public class WebsocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages)
{
messages.nullDestMatcher().authenticated().simpDestMatchers("/topic/blog")
.authenticated()
// matches any destination that starts with /topic/
// (i.e. cannot send messages directly to /topic/)
// (i.e. can’t subscribe to /topic/messages/* to get messages which is sent to
// /topic/messages-user)
.simpDestMatchers("/topic/**").authenticated();
// message types other than MESSAGE and SUBSCRIBE
//.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
// catch all
//.anyMessage().denyAll();
}
/**
* Disables CSRF for Websockets.
*/
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
Here is the connect method which is called after authentication for connecting the websocket.
connect():void {
console.log("connect called"); //eslint-disable-line
// build absolute path, websocket doesn't fail during deploying with a context path
let url = '/websocket/blog';
url = this.location.prepareExternalUrl(url);
const authToken = this.authServerProvider.getToken();
if (authToken) {
url += '?access_token=' + authToken;
}
const socket = new SockJS(url);
this.stompClient = Stomp.over(socket);
const headers = {};
this.stompClient.connect(headers, () => {
console.log("this is inside stompclient connect connect"); //eslint-disable-line
this.stompClient.subscribe('/topic/blog', data => {
console.log("This inside subscription"); //eslint-disable-line
this.listenerObserver.next(JSON.parse(data.body));
});
});
}
}
Authorization token is passed with the correct value. Hence there is no error in authorization.
Please let me know if anything else is required.
I am stuck at this point for quite a long time now.
Error:
WebSocket connection to 'ws://localhost:8080/websocket/blog/587/mklduqvp/websocket?access_token=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJoYXJkZWVwc2h1a2xhMDhAbmljLmluIiwiYXV0aCI6IlJPTEVfRU1QTE9ZRUUiLCJleHAiOjE2MDUyNDUwNjd9.DJ2HITaVAiaphd2yg3yPAiLrLI4n8MjszjBasC3zOHrC-73mFdltDPEYHihY16VzPv0rh6EYLj84zCBv37TDNA' failed: Error during WebSocket handshake: Unexpected response code: 200
In your DefaultHandshakeHandler, can you try to annotate #Bean and set to public.
#Bean
public DefaultHandshakeHandler defaultHandshakeHandler() {

Sticky session Ribbon rule in Zuul always has null request

I am attempting to implement a sticky session load balancer rule in a Zuul proxy service. I am using the code from this example: https://github.com/alejandro-du/vaadin-microservices-demo/blob/master/proxy-server/src/main/java/com/example/StickySessionRule.java
I seem to have everything configured correctly, and the rule is triggering in my debugger, but the call to RequestContext.getCurrentContext().getResponse() always returns null, so the cookie is never found, so the rule never takes effect.
The rest of the Zuul config is working 100%. My traffic is proxied and routed and I can use the app fine, only the sticky session rule is not working.
Is there another step I am missing to get the request wired in to this rule correctly?
My route config:
zuul.routes.appname.path=/appname/**
zuul.routes.appname.sensitiveHeaders=
zuul.routes.appname.stripPrefix=false
zuul.routes.appname.retryable=true
zuul.add-host-header=true
zuul.routes.appname.service-id=APP_NAME
hystrix.command.APP_NAME.execution.isolation.strategy=THREAD
hystrix.command.APP_NAME.execution.isolation.thread.timeoutInMilliseconds=125000
APP_NAME.ribbon.ServerListRefreshInterval=10000
APP_NAME.ribbon.retryableStatusCodes=500
APP_NAME.ribbon.MaxAutoRetries=5
APP_NAME.ribbon.MaxAutoRetriesNextServer=1
APP_NAME.ribbon.OkToRetryOnAllOperations=true
APP_NAME.ribbon.ReadTimeout=5000
APP_NAME.ribbon.ConnectTimeout=5000
APP_NAME.ribbon.EnablePrimeConnections=true
APP_NAME.ribbon.NFLoadBalancerRuleClassName=my.package.name.StickySessionRule
The app:
#EnableZuulProxy
#SpringBootApplication
public class ApplicationGateway {
public static void main(String[] args) {
SpringApplication.run(ApplicationGateway.class, args);
}
#Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
EDIT: As requested, the code:
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* #author Alejandro Duarte.
*/
public class StickySessionRule extends ZoneAvoidanceRule {
public static final String COOKIE_NAME_SUFFIX = "-" + StickySessionRule.class.getSimpleName();
#Override
public Server choose(Object key) {
Optional<Cookie> cookie = getCookie(key);
if (cookie.isPresent()) {
Cookie hash = cookie.get();
List<Server> servers = getLoadBalancer().getReachableServers();
Optional<Server> server = servers.stream()
.filter(s -> s.isAlive() && s.isReadyToServe())
.filter(s -> hash.getValue().equals("" + s.hashCode()))
.findFirst();
if (server.isPresent()) {
return server.get();
}
}
return useNewServer(key);
}
private Server useNewServer(Object key) {
Server server = super.choose(key);
HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
if (response != null) {
String cookieName = getCookieName(server);
Cookie newCookie = new Cookie(cookieName, "" + server.hashCode());
newCookie.setPath("/");
response.addCookie(newCookie);
}
return server;
}
private Optional<Cookie> getCookie(Object key) {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if (request != null) {
Server server = super.choose(key);
String cookieName = getCookieName(server);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.filter(c -> c.getName().equals(cookieName))
.findFirst();
}
}
return Optional.empty();
}
private String getCookieName(Server server) {
return server.getMetaInfo().getAppName() + COOKIE_NAME_SUFFIX;
}
}
I think you are missing a PreFilter, like this:
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
public class PreFilter extends com.netflix.zuul.ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
RequestContext.getCurrentContext().set(FilterConstants.LOAD_BALANCER_KEY, ctx.getRequest());
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER;
}
#Override
public String filterType() {
return "pre";
}
}
Mark as Bean
#Bean
public PreFilter preFilter() {
return new PreFilter();
}
And use it in your rule
#Override
public Server choose(Object key) {
javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) key;
RequestContext not working cause "hystrix.command.APP_NAME.execution.isolation.strategy=THREAD"

spring websocket+rabbitmq stomp:How can i get all online user and disconnect some user’s connect?

If spring store user's websocket session,how can get it,if i use someHandler to put session in a map,that mean map‘s size maybe bigger than 10w+,I don't think it good。
SessionHandler.java
package hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
#Service
public class SessionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionHandler.class);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
public SessionHandler() {
scheduler.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
sessionMap.keySet().forEach(k -> {
try {
sessionMap.get(k).close();
sessionMap.remove(k);
} catch (IOException e) {
LOGGER.error("Error while closing websocket session: {}", e);
}
});
}
}, 10, 10, TimeUnit.SECONDS);
}
public void register(WebSocketSession session) {
sessionMap.put(session.getId(), session);
}
}
like this:
https://github.com/isaranchuk/spring-websocket-disconnect/blob/master/src/main/java/hello/SessionHandler.java
Implement the HandshakeInter as below.
public class HttpSessionIdHandshakeInterceptor implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes)
throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
attributes.put(SESSION_ATTR, session.getId());
}
}
return true;
}
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception ex) {
}
}
Then Add to the endpoint.
public class WebSocketConfig extends
AbstractWebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio")
.withSockJS()
.setInterceptors(new HttpSessionIdHandshakeInterceptor());
}
...
}
Next we can create a ChannelInterceptorAdapter that uses the session id to update the last accessed time using Spring Session. For example:
#Bean public ChannelInterceptorAdapter sessionContextChannelInterceptorAdapter() {
return new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
String sessionId = (String) sessionHeaders.get(SESSION_ATTR);
if (sessionId != null) {
Session session = sessionRepository.getSession(sessionId);
if (session != null) {
sessionRepository.save(session);
}
}
return super.preSend(message, channel);
}
};
}

How do I get the Session Object in Spring?

I am relatively new to Spring and Spring security.
I was attempting to write a program where I needed to authenticate a user at the server end using Spring security,
I came up with the following:
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException
{
System.out.println("Method invoked : additionalAuthenticationChecks isAuthenticated ? :"+usernamePasswordAuthenticationToken.isAuthenticated());
}
#Override
protected UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{
System.out.println("Method invoked : retrieveUser");
//so far so good, i can authenticate user here, and throw exception if not authenticated!!
//THIS IS WHERE I WANT TO ACCESS SESSION OBJECT
}
}
My usecase is that when a user is authenticated, I need to place an attribute like:
session.setAttribute("userObject", myUserObject);
myUserObject is an object of some class that I can access throughout my server code across multiple user requests.
Your friend here is org.springframework.web.context.request.RequestContextHolder
// example usage
public static HttpSession session() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true); // true == allow create
}
This will be populated by the standard spring mvc dispatch servlet, but if you are using a different web framework you have add org.springframework.web.filter.RequestContextFilter as a filter in your web.xml to manage the holder.
EDIT: just as a side issue what are you actually trying to do, I'm not sure you should need access to the HttpSession in the retieveUser method of a UserDetailsService. Spring security will put the UserDetails object in the session for you any how. It can be retrieved by accessing the SecurityContextHolder:
public static UserDetails currentUserDetails(){
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
return principal instanceof UserDetails ? (UserDetails) principal : null;
}
return null;
}
Since you're using Spring, stick with Spring, don't hack it yourself like the other post posits.
The Spring manual says:
You shouldn't interact directly with the HttpSession for security
purposes. There is simply no justification for doing so - always use
the SecurityContextHolder instead.
The suggested best practice for accessing the session is:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
The key here is that Spring and Spring Security do all sorts of great stuff for you like Session Fixation Prevention. These things assume that you're using the Spring framework as it was designed to be used. So, in your servlet, make it context aware and access the session like the above example.
If you just need to stash some data in the session scope, try creating some session scoped bean like this example and let autowire do its magic. :)
i made my own utils. it is handy. :)
package samples.utils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.MessageSource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.ui.context.Theme;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* SpringMVC通用工具
*
* #author 应卓(yingzhor#gmail.com)
*
*/
public final class WebContextHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(WebContextHolder.class);
private static WebContextHolder INSTANCE = new WebContextHolder();
public WebContextHolder get() {
return INSTANCE;
}
private WebContextHolder() {
super();
}
// --------------------------------------------------------------------------------------------------------------
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getRequest();
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean create) {
return getRequest().getSession(create);
}
public String getSessionId() {
return getSession().getId();
}
public ServletContext getServletContext() {
return getSession().getServletContext(); // servlet2.3
}
public Locale getLocale() {
return RequestContextUtils.getLocale(getRequest());
}
public Theme getTheme() {
return RequestContextUtils.getTheme(getRequest());
}
public ApplicationContext getApplicationContext() {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
}
public ApplicationEventPublisher getApplicationEventPublisher() {
return (ApplicationEventPublisher) getApplicationContext();
}
public LocaleResolver getLocaleResolver() {
return RequestContextUtils.getLocaleResolver(getRequest());
}
public ThemeResolver getThemeResolver() {
return RequestContextUtils.getThemeResolver(getRequest());
}
public ResourceLoader getResourceLoader() {
return (ResourceLoader) getApplicationContext();
}
public ResourcePatternResolver getResourcePatternResolver() {
return (ResourcePatternResolver) getApplicationContext();
}
public MessageSource getMessageSource() {
return (MessageSource) getApplicationContext();
}
public ConversionService getConversionService() {
return getBeanFromApplicationContext(ConversionService.class);
}
public DataSource getDataSource() {
return getBeanFromApplicationContext(DataSource.class);
}
public Collection<String> getActiveProfiles() {
return Arrays.asList(getApplicationContext().getEnvironment().getActiveProfiles());
}
public ClassLoader getBeanClassLoader() {
return ClassUtils.getDefaultClassLoader();
}
private <T> T getBeanFromApplicationContext(Class<T> requiredType) {
try {
return getApplicationContext().getBean(requiredType);
} catch (NoUniqueBeanDefinitionException e) {
LOGGER.error(e.getMessage(), e);
throw e;
} catch (NoSuchBeanDefinitionException e) {
LOGGER.warn(e.getMessage());
return null;
}
}
}
Indeed you can access the information from the session even when the session is being destroyed on an HttpSessionLisener by doing:
public void sessionDestroyed(HttpSessionEvent hse) {
SecurityContextImpl sci = (SecurityContextImpl) hse.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
// be sure to check is not null since for users who just get into the home page but never get authenticated it will be
if (sci != null) {
UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();
// do whatever you need here with the UserDetails
}
}
or you could also access the information anywhere you have the HttpSession object available like:
SecurityContextImpl sci = (SecurityContextImpl) session().getAttribute("SPRING_SECURITY_CONTEXT");
the last assuming you have something like:
HttpSession sesssion = ...; // can come from request.getSession(false);
I try with next code and work excellent
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Created by jaime on 14/01/15.
*/
#Controller
public class obteinUserSession {
#RequestMapping(value = "/loginds", method = RequestMethod.GET)
public String UserSession(ModelMap modelMap) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
modelMap.addAttribute("username", name);
return "hellos " + name;
}
In my scenario, I've injected the HttpSession into the CustomAuthenticationProvider class
like this
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{
#Autowired
private HttpSession httpSession;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException
{
System.out.println("Method invoked : additionalAuthenticationChecks isAuthenticated ? :"+usernamePasswordAuthenticationToken.isAuthenticated());
}
#Override
protected UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{
System.out.println("Method invoked : retrieveUser");
//so far so good, i can authenticate user here, and throw exception
if not authenticated!!
//THIS IS WHERE I WANT TO ACCESS SESSION OBJECT
httpSession.setAttribute("userObject", myUserObject);
}
}
If all that you need is details of User, for Spring Version 4.x you can use #AuthenticationPrincipal and #EnableWebSecurity tag provided by Spring as shown below.
Security Configuration Class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Controller method:
#RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(#AuthenticationPrincipal User user) {
...
}
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
attr.getSessionId();

Resources