今日完成的事
- 线上项目 整合Cookie 登陆
- 通过 Memcached 来同步Session, 解决tomcat多开带来的Session同步问题.
Memcached 同步 Session
由于使用了多台tomcat, 在Nginx做负载均衡时会将请求随机分发到各个tomcat上, 当属于tomcat_1的登陆后会话请求被转发到tomcat_2上时, 就会出现登陆验证错误, 因为tomcat_2上这个会话并未登陆.
解决方法, 通过Memcache保存Session会话.
- 注意, Session是容器保存的,内容放置在服务器具体的文件中,并不是程序保存的, 这个概念一定要搞清楚.
- 我这里场景是 登陆后执行一个请求列出所有用户, 所以不能直接转到jsp.
- 使用
redirect
跳转页面会导致 session 值丢失. 参考连接1 参考连接2 - 解决方法
- 思路: handler中保存session后,先forward到一个handler再redirect到目标handler, session值不会丢失
- 具体实现:
- handler 中保存 session, 此时session还未保存到容器中
- 返回页面使用redirect到新页面,因为重定向特性不会带前页面的数据, 所以容器会将session值直接丢弃,导致session值丢失.(session id还在)
- 返回时使用 forward 到新handler, 容器会将session保存.
- 在新handler中再redirect到新页面, 此时session已保存,不会影响
- handler 中保存 session, 此时session还未保存到容器中
将Tomcat的session由Memcached保存 准备工作
- Nginx 负载均衡已配置
- Tomcat 已开启多个容器
- Tomcat 8
- 官方文档:
https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration
- 参考资料:
https://www.cnblogs.com/jsonhc/p/7344902.html
下载所需jar包, 每个tomcat版本对应的包不同
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager/2.1.1/memcached-session-manager-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager-tc8/2.1.1/memcached-session-manager-tc8-2.1.1.jar
wget http://repo1.maven.org/maven2/net/spy/spymemcached/2.11.1/spymemcached-2.11.1.jar
- 如果仅仅只是用java来做序列化器只需上面这三个包就ok
- 下面两个包,配置后一直报错,未测试通过
wget http://repo1.maven.org/maven2/de/javakaffee/msm/msm-javolution-serializer/1.8.3/msm-javolution-serializer-1.8.3.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/msm-javolution-serializer/2.1.1/msm-javolution-serializer-2.1.1.jar
- 下载完成后放入tomcat/lib目录中
修改两个tomcat配置文件,启动
vim
{tomcat}/conf/context.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<Context>
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:127.0.0.1:11211,n2:127.0.0.1:22122"
failoverNodes="n1"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
/>
</Context>
开一个test项目测试
- test/index.jsp, 分别写到两个 tomcat 测试, 修改{TomcatA}.liuhuan.ml代指Tomcat容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<%@ page language="java" %>
<html>
<head><title>TomcatA</title></head>
<body>
<h1><font color="blue">TomcatA.liuhuan.ml</font></h1>
<table align="centre" border="1">
<tr>
<td>SessionID</td>
<%session.setAttribute("linuxinfo.top","linuxinfo.top");%>
<td><%=session.getId() %></td>
</tr>
<tr>
<td>Createdon</td>
<td><%=session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>测试
启动Memcached
memcached -p 11211 -m 64m -vv -u root
memcached -p 22122 -m 64m -vv -u root
测试
1 | <%@ page language="java" %> |
实际项目
- session 登陆验证
Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//登陆
public String login(HttpSession session, Auth auth) throws Exception {
if(userService.findAuth(auth)){
// 在session中保存用户身份信息
session.setAttribute("username", auth.getUsername());
logger.info("session.getId():" + session.getId());
// 存入缓存 用作效验
MemcacheUtils.set(session.getId(), auth.getUsername());
}
//redirect 重定向到用户信息页面,会导致session内容丢失. 这里先forward保存session
return "forward:/s/succed.action";
}
//退出登陆
public String logout(HttpSession session){
// 删除缓存
MemcacheUtils.delete(session.getId());
//删除session
session.invalidate();
return "redirect:/login.action";
} - 拦截器效验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54//执行Handler方法之前执行
//用于身份认证、身份授权
//比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//handler 开始时间
this.timer = System.currentTimeMillis();
//获取请求的url
String url = httpServletRequest.getRequestURI();
//判断url是否是公开地址(实际使用时将公开地址配置到配置文件中)
//正则匹配url
Pattern pattern = Pattern.compile("^.*login.action.*$|^.*rest\\/api\\/.*$|^.*home\\/.*$|^.*profession\\/.*$|^.*memcache\\/.*$");
Matcher matcher = pattern.matcher(url);
//可以导入一个配置文件,匹配其中的请求
if (matcher.matches()){
//如果要进行登陆提交,放行
return true;
}
// 获取 session
HttpSession session = httpServletRequest.getSession();
// 从session中取出用户信息
String username = (String)session.getAttribute("username");
logger.debug("尝试登陆用户: " + username + "session_id:" + session.getId());
// 登陆数据效验
if(username !="" &&username!=null){
String sessionName = (String) MemcacheUtils.get(session.getId());
logger.info("登陆用户名: " + username + ", sessionName:" + sessionName + ", session.getId():" + session.getId());
if(username.equals(sessionName)){
//身份存在 放行
logger.debug("身份存在 放行");
return true;
}
}else{
// 当 session或缓存的 username 的 值为空时(可能是缓存出问题,又或者浏览器将seesion删除), 需要删除其session重新创建, 否则会一直判空
// 删除缓存
MemcacheUtils.delete(session.getId());
//删除session
session.invalidate();
}
//执行到这里标识用户身份需要认证,跳转到登陆界面
//跳转网址需要绝对路径,将当前请求重新映射到/WEB-INF/jsp/login.jsp,
// WEB-INF/jsp/login.jsp访问的是原地址+WEB-INF/jsp/login.jsp
httpServletRequest.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(httpServletRequest, httpServletResponse);
logger.info("用户身份需要认证,跳转至登陆页面,执行Handler方法之前执行");
//return false表示拦截,不向下执行 此时应计算页面结束时间
this.timer = System.currentTimeMillis() - this.timer;
logger.debug( "性能日志 页面生成时长 : " + this.timer + "ms" );
//return true表示放行
return false;
}登陆测试
- session 登陆测试
- 日志
- 重启tomcat, session任然可以登陆
- 刷新页面 正常显示
- 日志
- 日志
- Cookie登陆, 由于它存储在用户端, 后端只需要解密验证, 没有同步问题, 服务器端重启也是不影响它验证的
总结
- memcached 因为session同步的问题拖了一天, 今天就结束了, 相对cookie验证, 纯session相对限制多一些.
明日计划
- 更换Redis测试.