1.两个服务器通过同步session实现session共享
优点:配置简单
缺点:如果机器多了,就会出现大量的网络传输,甚至容易引起网络风暴,导致系统崩溃,只能适合少数的机器。
2.将session存储在某个介质上、比如数据库上或者缓存服务器上,进行统一管理。
下面是一个springboot+springSession+redis共享的列子
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sumei</groupId> <artifactId>login</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>login</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestone</id> <url>https://repo.spring.io/libs-release</url> </repository> </repositories> </project>
package com.sumei.login; import org.springframework.session.web.http.DefaultCookieSerializer; import javax.servlet.http.HttpServletRequest; /** * 自定义CookiePath */ public class CustomerCookiesSerializer extends DefaultCookieSerializer { private String getCookiePath(HttpServletRequest request) { return "/"; } }
package com.sumei.login; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.web.http.CookieHttpSessionStrategy; @Configuration @EnableRedisHttpSession public class Config { /** *jedis简单配置 * @return */ @Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); connectionFactory.setPort(6379); connectionFactory.setHostName("192.168.31.100"); return connectionFactory; } /** *CookieHttpSessionStrategy 配置 * @return */ @Bean public CookieHttpSessionStrategy cookieHttpSessionStrategy(){ CookieHttpSessionStrategy cookieHttpSessionStrategy=new CookieHttpSessionStrategy(); CustomerCookiesSerializer cookiesSerializer= new CustomerCookiesSerializer(); cookiesSerializer.setDomainName("sumei.com"); cookieHttpSessionStrategy.setCookieSerializer(cookiesSerializer); return cookieHttpSessionStrategy; } }
package com.sumei.login; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; public class Initializer extends AbstractHttpSessionApplicationInitializer { public Initializer() { super(Config.class); } }
package com.sumei.login; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan public class LoginApplication { public static void main(String[] args) { SpringApplication.run(LoginApplication.class, args); } }
package com.sumei.login; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.HashMap; import java.util.Map; /** *测试类 */ @RestController public class LoginController { @RequestMapping("test1") public Map getSessionId(HttpServletRequest request){ HttpSession session = request.getSession(false); String session_id=null; if(session==null){ session=request.getSession(true); } session_id = session.getId(); System.out.println(session_id); Map<String,Object> res=new HashMap<>(); res.put("sessionid",session_id); if(session.getAttribute("boot")==null){ System.out.println("***********************"); session.setAttribute("boot","nbbo"); }else { System.out.println(session.getAttribute("boot")); } return res; } }
将其打成jar放到服务器上进行测试
配置我本地的host 配置文件
192.168.31.100 top.sumei.com
192.168.31.101 bottom.sumei.com
用 java -jar login-0.0.1-SNAPSHOT.jar启动服务 浏览器地址
发现 top.sumei.com 与 bottom.sumei.com 的sesionid 是一样的,说明session共享成功。
3.基于JWT(JSON WEB TOKEN)代替的方案
jws 相比Cookie的优势
支持跨域跨站点访问:
Cookie是不允许垮域访问的,可以通过设置顶级域名的方式实现部分跨域,但是跨站点的访问仍然不支持,
如果使用Token机制,就可以通过HTTP头传输用户认证信息,从而更好的实现跨域跨站点。
无状态:
Token机制在服务端不需要存储session信息,Token自身包含了登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息;
更适用于移动应用:
当客户端是原生应用时,Cookie是不被支持的,虽然目前Webview的方式可以解决Cookie问题,
但是显然采用Token认证机制会简单得多;
安全性更强:
因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范;
标准化易扩展:
可以采用标准化的 JSON Web Token (JWT),对以后系统接入Node等纯前端开发更便捷;
相比Session一致性提高性能:
相比服务端保存Session一致性信息,并查询用户登录状态,一般来说Token的验证过程(包含加密和解密),性能开销会更小。
JWS 由三部分组成(header,payload,Signature)
header {
"alg": "HS256", //加密算法
"typ": "JWT" //令牌类型
}
payload{
{"iss",value},//签发者
{"iat", value},//非必须。issued at。 token创建时间,unix时间戳格式
{"exp", value},//过期时间
{"aud", value},//非必须。接收该JWT的一方。
{"sub", value},//拥有者
{"jti", value},//非必须。JWT ID。针对当前token的唯一标识
//自定义claims
{"user",value}
{"passwd",value}
..........
};
signature 就是用点号将header和payload联系起来,然后用header里面指定的加密方法进行加密后的字符串。
一个简单的测试demo
package com.sumei.login.jwt; public class JSONTokenInfo { /** * 过期时间 */ private String uid; /** * 用户id */ private int exprie; public JSONTokenInfo() { } public JSONTokenInfo(String uid) { this.uid = uid; } public String getUid() { return uid; } public void setUid(String uid) { this.uid = uid; } public int getExprie() { return exprie; } public void setExprie(int exprie) { this.exprie = exprie; } }
package com.sumei.login.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.joda.time.DateTime; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; public class JWSTokenUtil { private static String jwt_key_user_id="uid"; /** * 生成一个加密key * @return */ public static Key getKey(){ SignatureAlgorithm es256 = SignatureAlgorithm.HS256; byte[] bytes= DatatypeConverter.parseBase64Binary("secret key"); SecretKeySpec secretKeySpec = new SecretKeySpec(bytes,es256.getJcaName()); return secretKeySpec; } /** * 生成token * @param jsonTokenInfo * @param exprie * @return */ public static String getToken(JSONTokenInfo jsonTokenInfo,int exprie){ return Jwts.builder().claim(jwt_key_user_id,jsonTokenInfo) .setExpiration(DateTime.now().plusSeconds(exprie).toDate()) .signWith(SignatureAlgorithm.HS256,getKey()).compact(); } /** * * @param token * @return */ public static JSONTokenInfo getInstance(String token) { Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKey()).parseClaimsJws(token); Claims body = claimsJws.getBody(); JSONTokenInfo jsonTokenInfo=new JSONTokenInfo(); jsonTokenInfo.setUid(body.get(jwt_key_user_id).toString()); return jsonTokenInfo; } }
package com.sumei.login; import com.sumei.login.jwt.JSONTokenInfo; import com.sumei.login.jwt.JWSTokenUtil; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.HashMap; import java.util.Map; @RestController public class JWSTokenController { /** * * @param uid * @param response * @return */ @RequestMapping("testJws") public String testJws(String uid,HttpServletResponse response){ JSONTokenInfo jsonTokenInfo=new JSONTokenInfo(uid); String token = JWSTokenUtil.getToken(jsonTokenInfo, 3000); response.addHeader("Set-Cookie","access_token="+token+"PATH=/;HttpOnly"); System.out.println(token); return token; } /** * 根据token检查uuid * @param token * @return */ @RequestMapping("testToken") public JSONTokenInfo testToken(String token){ JSONTokenInfo jsonTokenInfo = JWSTokenUtil.getInstance(token); return jsonTokenInfo; } }
打成jar 进行本地测试
启动java -jar ***.jar 启动两个服务 一个端口为8888 一个端口为8881
1.浏览器输入 http://localhost:8888/testJws?uid=001
2.复制token 将这个token 传递给8881的端口。
在浏览器输入 http://localhost:8881/testToken?token=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOnsidWlkIjoiMDAxIiwiZXhwcmllIjowfSwiZXhwIjoxNTIxMzY2OTkxfQ.FSfsjldW7jr8TNHlfREu6l_nDPQQ33LdvMydem3ZSas
在8881中我并没有用浏览器传入uid=1 说明通过这种方式服务之间可以进行共享数据,可以解决单点登录。