多个系统的情况下,我们希望用户在一个系统登录,然后在同一个浏览器中访问别的系统(当然也是同一个平台的),比如新浪微博和新浪博客,域名都不同,但是我们登录了新浪微博,只需要刷新新浪博客,状态就会变成已登录,这样子就实现了单点登录。
实现单点登录的方式有多种,这里列举一下我自己的实现原理。
一、同一域名下不同项目的单点登录
这个实现起来很好办,用户cookie即可,因为cookie是共享的,这里不做分析。
二、同一父域名下,不同项目的单点登录
这个其实跟第一种没两样,只不过指定cookie的时候需要设置路径为父域名,比如cookie.setPath(.forever.cn);这样子也可以实现cookie共享。
上面的两种情况都不通用,下面我将要实现一种完全通用的单点登录,介绍原理以及实现方法以及遇到的困难
三、通用的单点登录,不管域名相不相同都可以。
原理:有一个检验中心,负责所有系统的注册登录以及注销。
1、假设用A,B两个系统,校验中心为C。当用户访问A 的时候,检查session中是否有token(可以自己定义),若是有token,表明用户登录成功,若是没有token,则重定向到校验中心C的登录页面,参数将A的地址带上。
2、在校验中心的登录页面,用户输入用户名和密码,点击登录按钮,异步调用注册中心的登录方法,若是用户名和密码正确,则表明用户登录成功,此时生成一个token,值应该为一串比较难以仿造的数字,建议UUID。叫token放到内存中,建议放到redis中,设定过期时间(当然测试的时候也可以放到application中)。返回token到页面。
3、此时,token标识就已经拿到了,唯一需要的是叫该token标识放到A,B的session中,当然也就表明A,B中都需要有addSession的异步方法。在注册中心中保存着系统中所有平台的addSesssion方法。因为不能直接ajax跨域访问,并且需要A,B中都有session,所以这里可以用一个
public static String openConnection(String link){
if(link==null||"".equals(link)){
System.out.println("链接不存在");
return null;
}
HttpURLConnection conn=null;
InputStream is=null;
BufferedInputStream bis=null;
try {
URL url=new URL(link);
conn=(HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setRequestMethod("GET");
is=conn.getInputStream();
bis = new BufferedInputStream(is);
byte[] bytes = new byte[1024];
int len = -1;
StringBuilder sb = new StringBuilder();
while((len=bis.read(bytes))!=-1){
sb.append(new String(bytes,0,len));
}
String str = sb.toString();
bis.close();
is.close();
conn.disconnect();
System.out.println("链接返回的值:"+str);
return str;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
5、注册中心的校验方法,获取token后,会根据token中的值去内存或者redis中找,若是找得到,且值相同,则表明用户校验通过,返回success,否则返回fail.
6、A,B在addSession中都校验通过,则把token放到session中。所有系统都登录成功。
7、登录页面在backurl这个iframe的结果返回为success的时候,就可以跳转到backrul,用户登录成功,此时单点登录完成。
8、注销的话也一样,将校验中心的token移除,再将每个系统中的token移除即可。
如上,就是单点登录的流程啦,其实原理就是这样,只不过大家也可以不通过注册中心来发送登录信息,可以再登录到A的时候,叫token带过去,这样在A的页面获取所有其他平台的addSession方法来登录,就不用判断iframe是否成功啦。
四、遇到的一个问题:
在自己测试过程中,发现A系统的sessionId和B系统的sessionId会一直变化,也就是在我访问了A系统后,再访问B系统,A系统的sessionId就被移除了,原因是:由于两个工程的sessionid,名称、域、路径都一样,导致sessionid被覆盖,从而导致session失效;由此也得出cookie是不区分端口的。我的项目只是端口不同而已。
有如下三种解决方案:
1、每个系统的域名不同,这样的话就完全不会影响,但是现在我的系统,都用同一个域名的,一个tomcat中跑了几个项目。
2、每个项目设置不同的项目路径,这样的话就可以了,不要用根路径来。
使用不同SpringBoot版本,指定访问项目路径的项目名,使用的配置也不一样,如下。
以下是两种配置方式:
SpringBoot版本 配置
1.x server.context-path=/demo
2.x server.servlet.context-path=/demo
3、设置key不同
在Tomcat的server.xml中配置sessionCookieName,只要两个不相同就可以 ,但是我的是只有一个tomcat所以,最终选择了第二种方案。
五、代码实例,这里用的是springboot来演示,只出示关键部分的代码
一、校验中心center

二、各大系统,这里只需要举一个例子即可
六、这里提供一下springboot的基础配置pom.xml
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.myforever</groupId>
<artifactId>center</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>start</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加 jsp 依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- 添加 JSTL 支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
</dependencies>
<build>
<finalName>start</finalName>
<plugins>
<!-- 可以将项目打包成可执行jar包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<archive>
<manifest>
<mainClass>start.HelloApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
单点登录,结束,肯定还有很多不不足的地方,但是也是辛苦了一中午写博客,转载请注明来源IT随笔博客-微服务https://www.myforever.cn
