有两台相同的服务器,并且用nginx管理,用户的访问在两台机器之间权重相同(也就是说用户的访问是一次一台服务器,下一次访问的是另一台服务器),这样存在的问题是登录在一台服务器,下次访问应该传递回的是登录后的界面,但因为跨服务器,服务器对权限检查发现session中未包含信息,表明没有权限,而做了错误响应 SpringSession就是为了解决上面的问题
login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form th:action="@{/dologin}" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="提交"> </form> </body> </html>home.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1><span th:text="${#session.getAttribute('username')}"></span>你好,登录成功</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="退出登录"> </form> </body> </html>DemoController
@Controller public class DemoController { @GetMapping("/")//访问根地址时进入home界面,但是会被自定义的拦截器先进行拦截判断是否有权限 public String homeShow(){ return "home"; } //响应可以的dologin操作,并将username属性存放到session中,要求用户进行重定向到另一台机器 @PostMapping("/dologin") public String doLogin(@RequestParam("username")String userName, @RequestParam("password") String password, HttpServletRequest request){ request.getSession().setAttribute("username",userName); return "redirect:/"; } //响应用户的logout操作 @PostMapping("/logout") public String LoginOut( HttpServletRequest request){ //登出将session失效 request.getSession().invalidate(); return "login"; } }LoginInterceptor
@Component //注入到容器中 @Slf4j //用于日志记录 public class LoginInterceptor extends HandlerInterceptorAdapter { //定义拦截器,前置拦截,对权限进行判断 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //用日志功能查看拦截到的路径 log.info("拦截到"+request.getRequestURI()); //检查路径,对/login /dologin不拦截 if (request.getRequestURI().equals("/login")|| request.getRequestURI().equals("/dologin")){ return true; } //如果session中存在username,表明已登录则放行 if (request.getSession()!=null&& request.getSession().getAttribute("username")!=null){ // String username =(String) request.getSession().getAttribute("username"); // if ( username.trim().length()>0 ){ return true; } //验证没有登录,重定向到登录界面 response.sendRedirect(request.getContextPath()+"/login"); return false; } }WebMvcConfig
@Configuration @AllArgsConstructor //所有变量的构造方法,利用spring自动注入所有成员变量 public class WebMvcConfig implements WebMvcConfigurer { private LoginInterceptor loginInterceptor; //添加/login的请求响应界面 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login"); } //注入拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor); } }增加两个配置文件,其中定义端口号分别为8081 和9092 application.properties设置启动时默认的配置文件为M1
在idea中右键复制一个相同的服务器 右键新建的机器,进行编辑配置
修改nginx配置文件 指定好网络和端口,权重设置都为1
启动服务 点击提交后依然会重新进入login界面
解决的思路主要有两种:
将session单独存放到一个服务器,运行的两台服务器在进行服务时都从第三台服务器上读取session在用户进行登录后给用户返回特定字符串(JWT技术),每次用户进行后续访问都自动携带这串信息,在服务器上只需进行这串信息合法性判断即可(这里不做过多讨论)针对第一种方案,使用redis进行session存储,用到的框架就是SpringSession
这里需要注意的是,必须在controller中拿到session才会触发保存到redis的操作(即request.getSession()),否则不会进行存储 因为springSession进行自动存储,所以序列化格式采用了JDK默认方式,不太好修改,但是如果我们自己想要在redis中进行存储,可以设置存储格式:
redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer());