Instead of Tomcat’s Embedded HttpSession
stored in memory, Spring-Session reimplements HttpSesson
with Redis to support multiple sessions with same browser.
Try it up with debugger
Let's first create a demo project, go to starter page and create a demo project with following plugin:
- Redis
- Web
- Session
Add @EnableRedisHttpSession
to BootApplication
@SpringBootApplication
+ @EnableRedisHttpSession
public class DemoApplication {
...
}
And create a Hello Controller for debugging
@RestController
@RequestMapping("/")
public class Hello {
@GetMapping("hello")
public String hello(HttpServletRequest request, @RequestParam String value){
// add debugger here
request.getSession().setAttribute("key",value);
return value;
}
}
Add debugger with find usage
for getter/setter
org.springframework.session.web.http.SessionRepositoryFilter#CURRENT_SESSION_ATTR
Cookies resolver
org.springframework.session.web.http.CookieHttpSessionIdResolver#resolveSessionIds
And these are the mapping between cookies and redis
org.springframework.session.web.http.DefaultCookieSerializer#readCookieValues
org.springframework.session.web.http.DefaultCookieSerializer#writeCookieValue
then start a local redis(localhost:6379) and run springboot in debug mode.
Let's try with a curl
curl "http://localhost:8080/hello?value=1"
Finally, login to redis-cli, you will find sessions are already in Redis.
KEYS spring:session:sessions:*
How to reuse session-id?
By default, session ids are generated by UUID, they have no relation with browser or IP address.
org.springframework.session.MapSession#generateId
To reuse the session-id, every request from browser must have a valid seesion in the header(cookies or x-auth-token), I prefer header based session resolver (less payload and easier for non-browser clients).
@Bean
HttpSessionIdResolver headerResolver(){
return new HeaderHttpSessionIdResolver("miao-token");
}
Recurl and your will get a miao-token
header in response.
Content-Type: ...
Date: ...
miao-token: f15b28ea-15b6-4ce8-aad5-9ed0ab00d643
To keep the client's session up to date with server, just add the header in HttpInterceptor(eg: okhttp/ajax).
// angular HttpInterceptor, same as okhttp
export class AddHeaderInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('miao-token', YOUR_GLOBAL_TOKEN)
});
return next.handle(clonedRequest);
}
}
In the end, here is the resuable process flow.
RESTful Request
|
HttpSessionIdResolver
|
(has token id)?
|
|-------(yes): get seesion by id from redis and reuse.
---------(no): jump to login page and create id by UUID
Appendix
What's cookie's scope?
See Scope of cookies, if you share two war with same session, you need to customize the DefaultCookieSerializer
for wider scope.
CSRF protection
CSRF(Cross-Site Request Forgery) attacks may inject malicious code into webpages, see more at Spring-Securiy for server-side protection and Angular for client-side protection.
Alternatives
If you prefer a lower level redis store implementation, just try tomcat-redis-session-manager
com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve