Tomcat集群下如何实现单点登录?
分析
假设应用环境如上图,这里有三个Tomcat,里面部署的内容都是一样的,现在有这么一个问题,假设用户第一次在第一台服务器登陆后,下次通过负载均衡到了Tomcat2上面,是不是还要再登录一次,假如下次再到Tomcat3呢?如果不止这三台呢?这对用户来说每次登录显然是不现实的,而且三台tomcat的会话也是不共用的。现在要做的就是这三个Tomcat要共同拥有一份session列表,
思路一
我们首先可以这样想,让每台的tomcat进行session同步,但这样呢是不是太浪费服务器资源和性能了
思路二
nginx带有负载均衡功能,可以让nginx根据ip进行hash,负载固定用户到某一个Tomcat服务器,但这样万一某一台Tomcat挂了呢,还有可能就是万一客户端使用了静态代理且代理得用户还比较多,这样又会导致资源分配不均衡
思路三 通过Cookie+Redis
当用户在某一台服务器进行登录逻辑时,我们返回一个自定义的cookie,new Cookie("cookie",session.getId()),然后我们再把这个session.getId()
作为键,用户信息可以作为value存入Redis中,当下次访问到其他服务器需要验证用户状态时可以根据前端传过来的Cookie到Redis里面进行查询,查詢到的说明已经登录过,查询不到进行登录。
代码实现
public void login(@RequestParam("username") String username, @RequestParam("password")String password,
HttpSession session, HttpServletResponse response){
if("用户验证成功"){
writeLoginToken(response,session.getId())
}
}
public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie ck = new Cookie("cookiename", token);
ck.setDomain("域名");
ck.setPath("/");//代表设置在根目录
ck.setHttpOnly(true);
//如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。
ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久
ck.getValue());
response.addCookie(ck);
}
//读取客户端cookie
public static String readLoginToken(HttpServletRequest request) {
Cookie[] cks = request.getCookies();
if(cks!=null){
for(Cookie ck:cks){
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
return ck.getValue();
}
}
}
return null;
}
思路四 通过SpringSession实现session共享
思路三的使用方式我们时自己借助于Cookie在客户端写入值,同时我们没有使用session.setAttribute("key","value")这个方法,而通过SpringSession呢?原理上也差不多,但我们不用自己写Cookie操作这一部分,同时也要借助于Redis这个NoSql这个数据库,在原来的代码中我们还是通过session.setAttribute("key","value")
进行会话操作,这种方式最好的一面就是对代码的侵入性比较低。接下来是具体配置:
1、引入jar包
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
2、配置web.xml文件
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
3、配置applicationContext.xml文件
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800"></property>
</bean>
<bean class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="domainName" value="域名"></property>
<property name="useHttpOnlyCookie" value="true"></property>
<property name="cookiePath" value="/"></property>
<property name="cookieMaxAge" value="31536000"></property>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="20"></property>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="127.0.0.1"/>
<property name="port" value="6379"></property>
<property name="poolConfig" ref="jedisPoolConfig"></property>
</bean>