RedisSession (自定義)
瘋狂創客圈 Java 高並發【 億級流量聊天室實戰】實戰系列 【博客園總入口 】
架構師成長+面試必備之 高並發基礎書籍 【Netty Zookeeper Redis 高並發實戰 】
前言
Crazy-SpringCloud 微服務腳手架 &視頻介紹:
Crazy-SpringCloud 微服務腳手架,是為 Java 微服務開發 入門者 准備的 學習和開發腳手架。並配有一系列的使用教程和視頻,大致如下:
高並發 環境搭建 圖文教程和演示視頻,陸續上線:
中間件 | 鏈接地址 |
---|---|
Linux Redis 安裝(帶視頻) | Linux Redis 安裝(帶視頻) |
Linux Zookeeper 安裝(帶視頻) | Linux Zookeeper 安裝, 帶視頻 |
Windows Redis 安裝(帶視頻) | Windows Redis 安裝(帶視頻) |
RabbitMQ 離線安裝(帶視頻) | RabbitMQ 離線安裝(帶視頻) |
ElasticSearch 安裝, 帶視頻 | ElasticSearch 安裝, 帶視頻 |
Nacos 安裝(帶視頻) | Nacos 安裝(帶視頻) |
Crazy-SpringCloud 微服務腳手架 圖文教程和演示視頻,陸續上線:
組件 | 鏈接地址 |
---|---|
Eureka | Eureka 入門,帶視頻 |
SpringCloud Config | springcloud Config 入門,帶視頻 |
spring security | spring security 原理+實戰 |
Spring Session | SpringSession 獨立使用 |
分布式 session 基礎 | RedisSession (自定義) |
重點: springcloud 開發腳手架 | springcloud 開發腳手架 |
SpingSecurity + SpringSession 死磕 (寫作中) | SpingSecurity + SpringSession 死磕 |
小視頻以及所需工具的百度網盤鏈接,請參見 瘋狂創客圈 高並發社群 博客
RedisSession 場景和問題
一般,大家獲取 Session 的方式: session = request.getSession(), 是通過HttpServletRequest 獲取的,因為每次用戶請求過來,我們服務端都會獲取到請求攜帶的唯一 SessionId。
如果自定的 HttpSession的,所以我們還要自定義一個 HttpServletRequest 的包裝類,使得每次請求獲取的都是我們自己的HttpSession。
還有一點 ,如何 使用HttpServletRequest 包裝類呢?
還需要自定義一個 Filter,這個Filter不干其它的事情,就負責把HttpServletRquest 換成我們自定義的包裝類。
第一步 ,定義一個 RedisHttpSession
RedisHttpSession 實現 HttpSession 接口 ,選擇Redis存儲屬性,達到分布式的目標。
session在 Redis中 選擇的 Hash 結構存儲,以 sessionId 作為Key,有些方法不需要實現。
//首先我說過,HttpSession是不能注入屬性的,所以就需要依賴 上面定義的那個 工具類,獲取bean
//如果不加此注解,你的屬性就會為空,獲取不到
@DependsOn("applicationContextUtil")
@SpringBootConfiguration
public class CustomRedisHttpSession implements HttpSession {
private HttpServletRequest httpServletRequest;
private HttpServletResponse httpServletResponse;
private Cookie[] cookies;
//sessionId
private String sessionId;
public CustomRedisHttpSession(){}
public CustomRedisHttpSession(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,String sid){
this.httpServletRequest = httpServletRequest;
this.httpServletResponse = httpServletResponse;
this.cookies = cookies;
this.sessionId =sid;
}
/**
* 獲取指定屬性值
* @param key 屬性key
* @return key對應的value
*/
@Override
public Object getAttribute(String key) {
if(sessionId != null){
return sessionRedisTemplate.opsForHash().get(sessionId,key);
}
return null;
}
/**
* 之前說過了,此類屬性不能注入,只能通過手動獲取
*/
@SuppressWarnings("unchecked")
private final RedisTemplate<String,Object> sessionRedisTemplate =
=ApplicationContextUtil.getBean("sessionRedisTemplate",RedisTemplate.class);
//sessionId 的前綴
private static final String SESSIONID_PRIFIX="yangxiaoguang";
/**
* 設置屬性值
* @param key key
* @param value value
*/
@Override
public void setAttribute(String key, Object value) {
if (sessionId != null) {
sessionRedisTemplate.opsForHash().put(sessionId, key, value);
}else{
//如果是第一次登錄,那么生成 sessionId,將屬性值存入redis,設置過期時間,並設置瀏覽器cookie
this.sessionId = SESSIONID_PRIFIX + UUID.randomUUID();
setCookieSessionId(sessionId);
sessionRedisTemplate.opsForHash().put(sessionId, key, value);
sessionRedisTemplate.expire(sessionId, sessionTimeout, TimeUnit.SECONDS);
}
}
//session的過期時間,8小時
private final int sessionTimeout=28800;
//將sessionId存入瀏覽器
private void setCookieSessionId(String sessionId){
Cookie cookie = new Cookie(SESSIONID,sessionId);
cookie.setPath("/");
cookie.setMaxAge(sessionTimeout);
this.httpServletResponse.addCookie(cookie);
}
/**
* 移除指定的屬性
* @param key 屬性 key
*/
@Override
public void removeAttribute(String key) {
if(sessionId != null){
sessionRedisTemplate.opsForHash().delete(sessionId,key);
}
}
}
第2步 ,定義一個 ServletRequestWrapper
如果自定的 HttpSession的,所以我們還要自定義一個 HttpServletRequest 的包裝類,使得每次請求獲取的都是我們自己的HttpSession。
public class CustomSessionHttpServletRequestWrapper extends HttpServletRequestWrapper{
private HttpServletRequest httpServletRequest;
private HttpServletResponse httpServletResponse;
//自定義Session
private CustomRedisHttpSession customRedisHttpSession;
public CustomSessionHttpServletRequestWrapper(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
super(httpServletRequest);
this.httpServletRequest = httpServletRequest;
this.httpServletResponse = httpServletResponse;
Cookie[] cookies = httpServletRequest.getCookies();
String sid= getCookieSessionId(cookies);
this.customRedisHttpSession = new
CustomRedisHttpSession(httpServletRequest,httpServletResponse,sid);
}
//這個方法就是最重要的,通過它獲取自定義的 HttpSession
@Override
public HttpSession getSession() {
return this.customRedisHttpSession;
}
//瀏覽器的cookie key
private static final String SESSIONID="xyzlycimanage";
//從瀏覽器獲取SessionId
private String getCookieSessionId(Cookie[] cookies){
if(cookies != null){
for(Cookie cookie : cookies){
if(SESSIONID.equals(cookie.getName())){
return cookie.getValue();
}
}
}
return null;
}
}
如果從header 中獲取 sessiondi,也是類似的:
public class CustomSessionHttpServletRequestWrapper extends HttpServletRequestWrapper{
private HttpServletRequest httpServletRequest;
private HttpServletResponse httpServletResponse;
//自定義Session
private CustomRedisHttpSession customRedisHttpSession;
public CustomSessionHttpServletRequestWrapper(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
super(httpServletRequest);
this.httpServletRequest = httpServletRequest;
this.httpServletResponse = httpServletResponse;
String sid= request.getHeader("SESSION_ID");
this.customRedisHttpSession = new
CustomRedisHttpSession(httpServletRequest,httpServletResponse,sid);
}
//這個方法就是最重要的,通過它獲取自定義的 HttpSession
@Override
public HttpSession getSession() {
return this.customRedisHttpSession;
}
//瀏覽器的cookie key
private static final String SESSIONID="xyzlycimanage";
//從瀏覽器獲取SessionId
private String getCookieSessionId(Cookie[] cookies){
if(cookies != null){
for(Cookie cookie : cookies){
if(SESSIONID.equals(cookie.getName())){
return cookie.getValue();
}
}
}
return null;
}
}
第三步: 定義一個 Filter 類
還有一點 ,如何 使用HttpServletRequest 包裝類呢?
還需要自定義一個 Filter,這個Filter不干其它的事情,就負責把HttpServletRquest 換成我們自定義的包裝類。
/**
* 此過濾器攔截所有請求,也放行所有請求,但是只要與Session操作的有關的請求都換被
* 替換成:CustomSessionHttpServletRequestWrapper包裝請求,
* 這個請求會獲取自定義的HttpSession
*/
public class CustomSessionRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
//將請求替換成自定義的 CustomSessionHttpServletRequestWrapper 包裝請求
HttpServletRequest customSessionHttpServletRequestWrapper =
new CustomSessionHttpServletRequestWrapper
((HttpServletRequest)servletRequest,(HttpServletResponse)servletResponse);
filterChain.doFilter(customSessionHttpServletRequestWrapper,
httpServletResponse);
//替換了 請求哦
filterChain.doFilter(customSessionHttpServletRequestWrapper,servletResponse);
}
/**
* init 和 destroy 是管理 Filter的生命周期的,與邏輯無關,所以無需實現
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
四步 裝載 Filter 類
可以獨立加載,也可以放在springsecurity 的配置類中。
如果放在springsecurity 的配置類,具體如下:
package com.gsafety.pushserver.message.config;
//...
@EnableWebSecurity()
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(
//...
"/actuator/hystrix",
"/actuator/hystrix.stream",
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html")
.permitAll()
// .antMatchers("/image/**").permitAll()
// .antMatchers("/admin/**").hasAnyRole("ADMIN")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().disable()
.sessionManagement().disable()
.and()
.logout().disable()
.addFilterBefore(new CustomSessionRequestFilter(), SessionManagementFilter.class)
.sessionManagement().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
//....
"/actuator/hystrix.stream",
"/actuator/hystrix",
"/api/mock/**",
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/api/gemp/duty/info/user/login",
"/swagger-ui.html",
"/css/**",
"/js/**",
"/images/**",
"/webjars/**",
"**/favicon.ico"
);
}
}
具體,請關注 Java 高並發研習社群 【博客園 總入口 】
最后,介紹一下瘋狂創客圈:瘋狂創客圈,一個Java 高並發研習社群 【博客園 總入口 】
瘋狂創客圈,傾力推出:面試必備 + 面試必備 + 面試必備 的基礎原理+實戰 書籍 《Netty Zookeeper Redis 高並發實戰》
瘋狂創客圈 Java 死磕系列
- Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰
- Netty 源碼、原理、JAVA NIO 原理
- Java 面試題 一網打盡
- 瘋狂創客圈 【 博客園 總入口 】