SpringBoot+SpringSession+Redis實現session共享及單點登入
最近在學習springboot,session這個點一直困擾了我好久,今天把這些天踩的坑分享出來吧,希望能幫助更多的人。
一、pom.xml配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
二、application.properties的redis配置
#redis spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=123456 spring.redis.pool.max-idle=8 spring.redis.pool.min-idle=0 spring.redis.pool.max-active=8 spring.redis.pool.max-wait=-1 #超時一定要大於0 spring.redis.timeout=3000 spring.session.store-type=redis
在配置redis時需要確保redis安裝正確,並且配置notify-keyspace-events Egx,spring.redis.timeout設定為大於0,我當時這裡配置為0時springboot時啟不起來。
三、編寫登入狀態攔截器RedisSessionInterceptor
//攔截登入失效的請求 public class RedisSessionInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //無論訪問的地址是不是正確的,都進行登入驗證,登入成功後的訪問再進行分發,404的訪問自然會進入到錯誤控制器中 HttpSession session = request.getSession(); if (session.getAttribute("loginUserId") != null) { try { //驗證當前請求的session是否是已登入的session String loginSessionId = redisTemplate.opsForValue().get("loginUser:" + (long) session.getAttribute("loginUserId")); if (loginSessionId != null && loginSessionId.equals(session.getId())) { return true; } } catch (Exception e) { e.printStackTrace(); } } response401(response); return false; } private void response401(HttpServletResponse response) { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); try { response.getWriter().print(JSON.toJSONString(new ReturnData(StatusCode.NEED_LOGIN, "", "使用者未登入!"))); } catch (IOException e) { e.printStackTrace(); } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
四、配置攔截器
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter
{
@Bean
public RedisSessionInterceptor getSessionInterceptor()
{
return new RedisSessionInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry)
{
//所有已api開頭的訪問都要進入RedisSessionInterceptor攔截器進行登入驗證,並排除login介面(全路徑)。必須寫成鏈式,分別設定的話會建立多個攔截器。
//必須寫成getSessionInterceptor(),否則SessionInterceptor中的@Autowired會無效
registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/user/login");
super.addInterceptors(registry);
}
}
五、登入控制器
@RestController
@RequestMapping(value = "/api/user")
public class LoginController
{
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("/login")
public ReturnData login(HttpServletRequest request, String account, String password)
{
User user = userService.findUserByAccountAndPassword(account, password);
if (user != null)
{
HttpSession session = request.getSession();
session.setAttribute("loginUserId", user.getUserId());
redisTemplate.opsForValue().set("loginUser:" + user.getUserId(), session.getId());
return new ReturnData(StatusCode.REQUEST_SUCCESS, user, "登入成功!");
}
else
{
throw new MyException(StatusCode.ACCOUNT_OR_PASSWORD_ERROR, "賬戶名或密碼錯誤!");
}
}
@RequestMapping(value = "/getUserInfo")
public ReturnData get(long userId)
{
User user = userService.findUserByUserId(userId);
if (user != null)
{
return new ReturnData(StatusCode.REQUEST_SUCCESS, user, "查詢成功!");
}
else
{
throw new MyException(StatusCode.USER_NOT_EXIST, "使用者不存在!");
}
}
}
六、效果
我在瀏覽器上登入,然後獲取使用者資訊,再在postman上登入相同的賬號,瀏覽器再獲取使用者資訊,就會提示401錯誤了,瀏覽器需要重新登入才能獲取得到使用者資訊,同樣,postman上登入的賬號就失效了。
瀏覽器:
postman:
七、核心原理詳解
分散式session需要解決兩個難點:1、正確配置redis讓springboot把session託管到redis伺服器。2、單點登入。
1、redis:
redis需要能正確啟動到出現如下效果才證明redis正常配置並啟動
同時還要保證配置正確
@EnableCaching
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 30)//session過期時間(秒)
@Configuration
public class RedisSessionConfig
{
@Bean
public static ConfigureRedisAction configureRedisAction()
{
//讓springSession不再執行config命令
return ConfigureRedisAction.NO_OP;
}
}
springboot啟動後能在redis上查到快取的session才能說明整個redis+springboot配置成功!
2、單點登入:
這裡的實現我搜過大量資料,基本是基於第三方框架實現的,我在想,既然已經使用redis做集中快取了那幹嘛不用redis來做單點登入呢?
1、使用者登入時,在redis中記錄該userId對應的sessionId,並將userId儲存到session中。
HttpSession session = request.getSession(); session.setAttribute("loginUserId", user.getUserId()); redisTemplate.opsForValue().set("loginUser:" + user.getUserId(), session.getId());
2、訪問介面時,會在RedisSessionInterceptor攔截器中的preHandle()中捕獲,然後根據該請求發起者的session中儲存的userId去redis查當前已登入的sessionId,若查到的sessionId與訪問者的sessionId相等,那麼說明請求合法,放行。否則丟擲401異常給全域性異常捕獲器去返回給客戶端401狀態。
單點登入經過我的驗證後滿足需求,暫時沒有出現問題,也希望大家能看看有沒有問題,有的話給我點好的建議!