1.两个服务器通过同步session实现session共享

    Session共享方法以及单点登录方案 - 图1

    优点:配置简单
    缺点:如果机器多了,就会出现大量的网络传输,甚至容易引起网络风暴,导致系统崩溃,只能适合少数的机器。

    2.将session存储在某个介质上、比如数据库上或者缓存服务器上,进行统一管理。

    Session共享方法以及单点登录方案 - 图2

    下面是一个springboot+springSession+redis共享的列子

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <groupId>com.sumei</groupId>
    6. <artifactId>login</artifactId>
    7. <version>0.0.1-SNAPSHOT</version>
    8. <packaging>jar</packaging>
    9. <name>login</name>
    10. <description>Demo project for Spring Boot</description>
    11. <parent>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-starter-parent</artifactId>
    14. <version>1.5.10.RELEASE</version>
    15. <relativePath/> <!-- lookup parent from repository -->
    16. </parent>
    17. <properties>
    18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    20. <java.version>1.8</java.version>
    21. </properties>
    22. <dependencies>
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-starter-data-redis</artifactId>
    26. </dependency>
    27. <dependency>
    28. <groupId>org.springframework.session</groupId>
    29. <artifactId>spring-session-data-redis</artifactId>
    30. </dependency>
    31. <dependency>
    32. <groupId>org.springframework.boot</groupId>
    33. <artifactId>spring-boot-starter-web</artifactId>
    34. </dependency>
    35. <dependency>
    36. <groupId>org.springframework.session</groupId>
    37. <artifactId>spring-session</artifactId>
    38. </dependency>
    39. <dependency>
    40. <groupId>org.springframework.boot</groupId>
    41. <artifactId>spring-boot-starter-test</artifactId>
    42. <scope>test</scope>
    43. </dependency>
    44. </dependencies>
    45. <build>
    46. <plugins>
    47. <plugin>
    48. <groupId>org.springframework.boot</groupId>
    49. <artifactId>spring-boot-maven-plugin</artifactId>
    50. </plugin>
    51. </plugins>
    52. </build>
    53. <repositories>
    54. <repository>
    55. <id>spring-milestone</id>
    56. <url>https://repo.spring.io/libs-release</url>
    57. </repository>
    58. </repositories>
    59. </project>
    1. package com.sumei.login;
    2. import org.springframework.session.web.http.DefaultCookieSerializer;
    3. import javax.servlet.http.HttpServletRequest;
    4. /**
    5. * 自定义CookiePath
    6. */
    7. public class CustomerCookiesSerializer extends DefaultCookieSerializer {
    8. private String getCookiePath(HttpServletRequest request) {
    9. return "/";
    10. }
    11. }
    1. package com.sumei.login;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    5. import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
    6. import org.springframework.session.web.http.CookieHttpSessionStrategy;
    7. @Configuration
    8. @EnableRedisHttpSession
    9. public class Config {
    10. /**
    11. *jedis简单配置
    12. * @return
    13. */
    14. @Bean
    15. public JedisConnectionFactory connectionFactory() {
    16. JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
    17. connectionFactory.setPort(6379);
    18. connectionFactory.setHostName("192.168.31.100");
    19. return connectionFactory;
    20. }
    21. /**
    22. *CookieHttpSessionStrategy 配置
    23. * @return
    24. */
    25. @Bean
    26. public CookieHttpSessionStrategy cookieHttpSessionStrategy(){
    27. CookieHttpSessionStrategy cookieHttpSessionStrategy=new CookieHttpSessionStrategy();
    28. CustomerCookiesSerializer cookiesSerializer= new CustomerCookiesSerializer();
    29. cookiesSerializer.setDomainName("sumei.com");
    30. cookieHttpSessionStrategy.setCookieSerializer(cookiesSerializer);
    31. return cookieHttpSessionStrategy;
    32. }
    33. }
    1. package com.sumei.login;
    2. import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
    3. public class Initializer extends AbstractHttpSessionApplicationInitializer {
    4. public Initializer() {
    5. super(Config.class);
    6. }
    7. }
    1. package com.sumei.login;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. import org.springframework.context.annotation.ComponentScan;
    5. @SpringBootApplication
    6. @ComponentScan
    7. public class LoginApplication {
    8. public static void main(String[] args) {
    9. SpringApplication.run(LoginApplication.class, args);
    10. }
    11. }
    1. package com.sumei.login;
    2. import org.springframework.web.bind.annotation.RequestMapping;
    3. import org.springframework.web.bind.annotation.RestController;
    4. import javax.servlet.http.HttpServletRequest;
    5. import javax.servlet.http.HttpSession;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. /**
    9. *测试类
    10. */
    11. @RestController
    12. public class LoginController {
    13. @RequestMapping("test1")
    14. public Map getSessionId(HttpServletRequest request){
    15. HttpSession session = request.getSession(false);
    16. String session_id=null;
    17. if(session==null){
    18. session=request.getSession(true);
    19. }
    20. session_id = session.getId();
    21. System.out.println(session_id);
    22. Map<String,Object> res=new HashMap<>();
    23. res.put("sessionid",session_id);
    24. if(session.getAttribute("boot")==null){
    25. System.out.println("***********************");
    26. session.setAttribute("boot","nbbo");
    27. }else {
    28. System.out.println(session.getAttribute("boot"));
    29. }
    30. return res;
    31. }
    32. }

    将其打成jar放到服务器上进行测试

    配置我本地的host 配置文件

    192.168.31.100 top.sumei.com
    192.168.31.101 bottom.sumei.com

    用 java -jar login-0.0.1-SNAPSHOT.jar启动服务 浏览器地址

    Session共享方法以及单点登录方案 - 图3

    Session共享方法以及单点登录方案 - 图4

    发现 top.sumei.com 与 bottom.sumei.com 的sesionid 是一样的,说明session共享成功。

    3.基于JWT(JSON WEB TOKEN)代替的方案

    Session共享方法以及单点登录方案 - 图5

    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

    1. package com.sumei.login.jwt;
    2. public class JSONTokenInfo {
    3. /**
    4. * 过期时间
    5. */
    6. private String uid;
    7. /**
    8. * 用户id
    9. */
    10. private int exprie;
    11. public JSONTokenInfo() {
    12. }
    13. public JSONTokenInfo(String uid) {
    14. this.uid = uid;
    15. }
    16. public String getUid() {
    17. return uid;
    18. }
    19. public void setUid(String uid) {
    20. this.uid = uid;
    21. }
    22. public int getExprie() {
    23. return exprie;
    24. }
    25. public void setExprie(int exprie) {
    26. this.exprie = exprie;
    27. }
    28. }
    1. package com.sumei.login.jwt;
    2. import io.jsonwebtoken.Claims;
    3. import io.jsonwebtoken.Jws;
    4. import io.jsonwebtoken.Jwts;
    5. import io.jsonwebtoken.SignatureAlgorithm;
    6. import org.joda.time.DateTime;
    7. import javax.crypto.spec.SecretKeySpec;
    8. import javax.xml.bind.DatatypeConverter;
    9. import java.security.Key;
    10. public class JWSTokenUtil {
    11. private static String jwt_key_user_id="uid";
    12. /**
    13. * 生成一个加密key
    14. * @return
    15. */
    16. public static Key getKey(){
    17. SignatureAlgorithm es256 = SignatureAlgorithm.HS256;
    18. byte[] bytes= DatatypeConverter.parseBase64Binary("secret key");
    19. SecretKeySpec secretKeySpec = new SecretKeySpec(bytes,es256.getJcaName());
    20. return secretKeySpec;
    21. }
    22. /**
    23. * 生成token
    24. * @param jsonTokenInfo
    25. * @param exprie
    26. * @return
    27. */
    28. public static String getToken(JSONTokenInfo jsonTokenInfo,int exprie){
    29. return Jwts.builder().claim(jwt_key_user_id,jsonTokenInfo)
    30. .setExpiration(DateTime.now().plusSeconds(exprie).toDate())
    31. .signWith(SignatureAlgorithm.HS256,getKey()).compact();
    32. }
    33. /**
    34. *
    35. * @param token
    36. * @return
    37. */
    38. public static JSONTokenInfo getInstance(String token) {
    39. Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKey()).parseClaimsJws(token);
    40. Claims body = claimsJws.getBody();
    41. JSONTokenInfo jsonTokenInfo=new JSONTokenInfo();
    42. jsonTokenInfo.setUid(body.get(jwt_key_user_id).toString());
    43. return jsonTokenInfo;
    44. }
    45. }
    1. package com.sumei.login;
    2. import com.sumei.login.jwt.JSONTokenInfo;
    3. import com.sumei.login.jwt.JWSTokenUtil;
    4. import org.springframework.web.bind.annotation.RequestMapping;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import javax.servlet.http.HttpServletRequest;
    7. import javax.servlet.http.HttpServletResponse;
    8. import javax.servlet.http.HttpSession;
    9. import java.util.HashMap;
    10. import java.util.Map;
    11. @RestController
    12. public class JWSTokenController {
    13. /**
    14. *
    15. * @param uid
    16. * @param response
    17. * @return
    18. */
    19. @RequestMapping("testJws")
    20. public String testJws(String uid,HttpServletResponse response){
    21. JSONTokenInfo jsonTokenInfo=new JSONTokenInfo(uid);
    22. String token = JWSTokenUtil.getToken(jsonTokenInfo, 3000);
    23. response.addHeader("Set-Cookie","access_token="+token+"PATH=/;HttpOnly");
    24. System.out.println(token);
    25. return token;
    26. }
    27. /**
    28. * 根据token检查uuid
    29. * @param token
    30. * @return
    31. */
    32. @RequestMapping("testToken")
    33. public JSONTokenInfo testToken(String token){
    34. JSONTokenInfo jsonTokenInfo = JWSTokenUtil.getInstance(token);
    35. return jsonTokenInfo;
    36. }
    37. }

    打成jar 进行本地测试

    启动java -jar *.jar 启动两个服务 一个端口为8888 一个端口为8881

    1.浏览器输入 http://localhost:8888/testJws?uid=001

    Session共享方法以及单点登录方案 - 图6

    2.复制token 将这个token 传递给8881的端口。

    在浏览器输入 http://localhost:8881/testToken?token=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOnsidWlkIjoiMDAxIiwiZXhwcmllIjowfSwiZXhwIjoxNTIxMzY2OTkxfQ.FSfsjldW7jr8TNHlfREu6l_nDPQQ33LdvMydem3ZSas

    Session共享方法以及单点登录方案 - 图7

    在8881中我并没有用浏览器传入uid=1 说明通过这种方式服务之间可以进行共享数据,可以解决单点登录。

    文章来源:https://my.oschina.net/u/2474435/blog/1645027
    相关文章推荐:http://www.roncoo.com/article/index?title=session
    spring boot 视频教程:http://www.roncoo.com/course/list.html?courseName=spring+boot