需求背景:随着公司的发展,公司内部使用的系统越来越多。但是对于使用系统的员工来说不是个好事情。
1.每个系统都需要记住对应的账号和密码,很多员工都是每个系统的账户和密码都一样的。
2.如果同时要使用CRM系统、WMS系统、OA系统,用户需要登录三次,如果10个系统需要登录分别登录十次,非常繁琐。
3.如果不使用了,还需要分别在三个系统中依次的注销。
需求:
1.后台用户通过SSO系统实现统一登录,并在SSO系统中点击其他系统并跳转到该系统,无需再次登录。
2.后台用户未在SSO系统登录时,其他系统无权访问,需统一跳转至SSO系统登录界面。
3.同步退出机制,即不管在SSO系统中退出,还是在其他系统中退出,实现统一退出,即单点注销。
技术分析:
原理:
相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。
sso认证中心与sso客户端通信方式有多种,这里以简单好用的HttpURLConnection为例,webService、rpc、restful api都可以
会话机制:
1.第一次访问前,浏览器本地没有cookie,服务器也没有对应的session。
2.第一次访问的时候,服务器会创建session对象,每个session对象都会有id,也就是JSESSIONID。
3.服务器会把JSESSIONID通过cookie的方式写到浏览器中。
4.第二次以后的请求,都会在请求服务器的时候,把JSESSIONID带上,通过JSESSIONID就可以找到服务器对应的session对象。
5.不同域名下的系统session对象是不一样的(跨域)。
SSO单点登录系统 JSESSIONID D7591D0622CB525F165E08C498543457
CRM客户管理系统 JSESSIONID 84C481030AA27C4F2AE2C955CB33EBE5
WMS进销存系统 JSESSIONID 9C757F7AEAD769146317E006DDCA66AB
实现步骤:
修改C:\Windows\System32\drivers\etc\host文件,添加如下配置:
127.0.0.1 www.sso.com
127.0.0.1 www.crm.com
127.0.0.1 www.wms.com
客户端操作(CRM系统、WMS系统、CMS系统...)流程图:
客户端(CRM系统、WMS系统、CMS系统...)项目结构图:
代码:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "LogOutServlet.java", urlPatterns = "/logOut")
public class LogOutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getSession().invalidate();
}
}
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.wolfcode.sso.util.SSOClientUtil;
@WebServlet(name = "mainServlet", urlPatterns = "/main")
public class MainServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setAttribute("serverLogOutUrl", SSOClientUtil.getServerLogOutUrl());
req.getRequestDispatcher("/WEB-INF/views/main.jsp").forward(req, resp);
}
}
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import cn.wolfcode.sso.util.HttpUtil;
import cn.wolfcode.sso.util.SSOClientUtil;
public class SSOClientFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String path = req.getRequestURI();
System.out.println("crm: "+path);
HttpSession session = req.getSession();
//1.判断是否有局部的会话
Boolean isLogin = (Boolean) session.getAttribute("isLogin");
if(isLogin!=null && isLogin){
//有局部会话,直接放行.
chain.doFilter(request, response);
return;
}
//判断地址栏中是否有携带token参数.
String token = req.getParameter("token");
if(StringUtils.isNoneBlank(token)){
//token信息不为null,说明地址中包含了token,拥有令牌.
//判断token信息是否由认证中心产生的.
String httpURL = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
Map<String,String> params = new HashMap<String,String>();
params.put("token", token);
params.put("clientUrl", SSOClientUtil.getClientLogOutUrl());
params.put("jsessionid", session.getId());
try {
String isVerify = HttpUtil.sendHttpRequest(httpURL, params);
if("true".equals(isVerify)){
//如果返回的字符串是true,说明这个token是由统一认证中心产生的.
//创建局部的会话.
session.setAttribute("isLogin", true);
//放行该次的请求
chain.doFilter(request, response);
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//没有局部会话,重定向到统一认证中心,检查是否有其他的系统已经登录过.
// http://www.sso.com:8443/checkLogin?redirectUrl=http://www.crm.com:8088
SSOClientUtil.redirectToSSOURL(req, resp);
}
@Override
public void destroy() {
}
}
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.util.StreamUtils;
public class HttpUtil {
/**
* 模拟浏览器的请求
* @param httpURL 发送请求的地址
* @param params 请求参数
* @return
* @throws Exception
*/
public static String sendHttpRequest(String httpURL,Map<String,String> params) throws Exception{
//建立URL连接对象
URL url = new URL(httpURL);
//创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求的方式(需要是大写的)
conn.setRequestMethod("POST");
//设置需要输出
conn.setDoOutput(true);
//判断是否有参数.
if(params!=null&¶ms.size()>0){
StringBuilder sb = new StringBuilder();
for(Entry<String,String> entry:params.entrySet()){
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
//sb.substring(1)去除最前面的&
conn.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
}
//发送请求到服务器
conn.connect();
//获取远程响应的内容.
String responseContent = StreamUtils.copyToString(conn.getInputStream(),Charset.forName("utf-8"));
conn.disconnect();
return responseContent;
}
/**
* 模拟浏览器的请求
* @param httpURL 发送请求的地址
* @param jesssionId 会话Id
* @return
* @throws Exception
*/
public static void sendHttpRequest(String httpURL,String jesssionId) throws Exception{
//建立URL连接对象
URL url = new URL(httpURL);
//创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求的方式(需要是大写的)
conn.setRequestMethod("POST");
//设置需要输出
conn.setDoOutput(true);
conn.addRequestProperty("Cookie","JSESSIONID="+jesssionId);
//发送请求到服务器
conn.connect();
conn.getInputStream();
conn.disconnect();
}
}
import java.io.IOException;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SSOClientUtil {
private static Properties ssoProperties = new Properties();
public static String SERVER_URL_PREFIX;//统一认证中心地址:http://www.sso.com:8443,在sso.properties配置
public static String CLIENT_HOST_URL;//当前客户端地址:http://www.crm.com:8088,在sso.properties配置
static{
try {
ssoProperties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
} catch (IOException e) {
e.printStackTrace();
}
SERVER_URL_PREFIX = ssoProperties.getProperty("server-url-prefix");
CLIENT_HOST_URL = ssoProperties.getProperty("client-host-url");
}
/**
* 当客户端请求被拦截,跳往统一认证中心,需要带redirectUrl的参数,统一认证中心登录后回调的地址
* 通过Request获取这次请求的地址 http://www.crm.com:8088/main
*
* @param request
* @return
*/
public static String getRedirectUrl(HttpServletRequest request){
//获取请求URL
return CLIENT_HOST_URL+request.getServletPath();
}
/**
* 根据request获取跳转到统一认证中心的地址 http://www.sso.com:8443//checkLogin?redirectUrl=http://www.crm.com:8088/main
* 通过Response跳转到指定的地址
* @param request
* @param response
* @throws IOException
*/
public static void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) throws IOException {
String redirectUrl = getRedirectUrl(request);
StringBuilder url = new StringBuilder(50)
.append(SERVER_URL_PREFIX)
.append("/checkLogin?redirectUrl=")
.append(redirectUrl);
response.sendRedirect(url.toString());
}
/**
* 获取客户端的完整登出地址 http://www.crm.com:8088/logOut
* @return
*/
public static String getClientLogOutUrl(){
return CLIENT_HOST_URL+"/logOut";
}
/**
* 获取认证中心的登出地址 http://www.sso.com:8443/logOut
* @return
*/
public static String getServerLogOutUrl(){
return SERVER_URL_PREFIX+"/logOut";
}
}
sso.properties
server-url-prefix=http://www.sso.com:8443
client-host-url=http://www.crm.com:8088
main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>客户关系管理系统</title>
</head>
<body>
<h1>这是客户管理系统的首页</h1>
<div style="text-align: right;width: 25%;">
<a href="${serverLogOutUrl}">退出系统</a>
</div>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<filter>
<filter-name>SSOClientFilter</filter-name>
<filter-class>cn.wolfcode.sso.filter.SSOClientFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SSOClientFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.wolfcode.sso</groupId>
<artifactId>client-crm</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<finalName>client-crm</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
服务端(SSO系统)操作流程图:
/checkLogin
1.检测客户端在服务端是否已经登录了
1.1获取session中的token.
1.2如果token不为空,说明服务端已经登录过了,此时重定向到客户端的地址,并把token带上
1.3如果token为空,跳转到统一认证中心的的登录页面,并把redirectUrl放入到request域中.
/user_login
2.统一认证中心的登录方法
2.1判断用户提交的账号密码是否正确.
2.2如果正确
2.2.1创建token(可以使用UUID,保证唯一就可以)
2.2.2把token放入到session中.
2.2.3这个token要知道有哪些客户端登陆了,创建Map<String,List<String[]> clientMap;(为单点注销做准备)
SSOUtil.clientMap.put(token,new ArrayList());(把这些数据放入到数据库中也是可以的,我们就做比较简单的,模拟一下.)
2.2.4转发到redirectUrl地址,把token带上.
2.3如果错误
转发到login.jsp,还需要把redirectUrl参数放入到request域中.
/verify
3.统一认证中心认证token方法
3.1如果SSOUtil.clientMap.get(token)有数据clientList,说明token是有效的.
3.1.1clientList把客户端传入的客户端登出地址(clientLogOutUrl)和会话ID(jsessionid)保存到集合中.
3.1.2返回true字符串.
3.1如果SSOUtil.clientMap.get(token)为null,说明token是无效的,返回false字符串.
项目结构图:
代码:
import cn.wolfcode.sso.util.MockDatabaseUtil;
import cn.wolfcode.sso.vo.ClientInfoVo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Created by yz
*/
@Controller
public class SSOServerController {
@RequestMapping("/")
public String index(){
return "index";
}
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/test")
public String test(){
return "test";
}
@RequestMapping("/checkLogin")
public String checkLogin(String redirectUrl, HttpSession session,Model model){
//1.判断是否有全局的会话
String token = (String) session.getAttribute("token");
if(StringUtils.isEmpty(token)){
//表示没有全局会话
//跳转到统一认证中心的登陆页面.
model.addAttribute("redirectUrl",redirectUrl);
return "login";
}else{
//有全局会话
//取出令牌信息,重定向到redirectUrl,把令牌带上 http://www.wms.com:8089/main?token
model.addAttribute("token",token);
return "redirect:"+redirectUrl;
}
}
/**
* 登陆功能
*/
@RequestMapping("/user_login")
public String login(String username,String password,String redirectUrl,HttpSession session,Model model){
if("admin".equals(username)&&"123456".equals(password)){
//账号密码匹配
//1.创建令牌信息
String token = UUID.randomUUID().toString();
//2.创建全局的会话,把令牌信息放入会话中.
session.setAttribute("token",token);
//3.需要把令牌信息放到数据库中.
MockDatabaseUtil.T_TOKEN.add(token);
if(StringUtils.isEmpty(redirectUrl)){
return "redirect:/";
}
//4.重定向到redirectUrl,把令牌信息带上. http://www.crm.com:8088/main?token=
model.addAttribute("token",token);
return "redirect:"+redirectUrl;
}
//如果账号密码有误,重新回到登录页面,还需要把redirectUrl放入request域中.
if(!StringUtils.isEmpty(redirectUrl)){
model.addAttribute("redirectUrl",redirectUrl);
}
return "redirect:login";
}
/**
* 校验token是否由统一认证中心产生的
*
*/
@RequestMapping("/verify")
@ResponseBody
public String verifyToken(String token,String clientUrl,String jsessionid){
if(MockDatabaseUtil.T_TOKEN.contains(token)){
//把客户端的登出地址记录
List<ClientInfoVo> clientInfoList = MockDatabaseUtil.T_CLIENT_INFO.get(token);
if(clientInfoList==null){
clientInfoList = new ArrayList<ClientInfoVo>();
MockDatabaseUtil.T_CLIENT_INFO.put(token,clientInfoList);
}
ClientInfoVo vo = new ClientInfoVo();
vo.setClientUrl(clientUrl);
vo.setJsessionid(jsessionid);
clientInfoList.add(vo);
//说明令牌有效,返回true
return "true";
}
return "false";
}
@RequestMapping("/logOut")
public String logOut(HttpSession session){
//销毁全局会话
session.invalidate();
return "redirect:login";
}
}
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @description: 登录拦截器
* @author: yz
* @create: 2018/11/16 11:38
*/
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 如果是登录有关操作的,不拦截
String path = req.getRequestURI();
// 1. session还有效
HttpSession session = req.getSession();
String token = (String) session.getAttribute("token");
if(token != null){
// 放行
chain.doFilter(request,response);
return;
}
if (path.contains("login") || path.endsWith(".css") || path.endsWith(".js")){
// 放行
chain.doFilter(request,response);
return;
}else{
System.out.println("path:"+path);
if(path.endsWith("verify")){
// 放行
chain.doFilter(request,response);
return;
}else {
resp.sendRedirect("login");
}
}
}
@Override
public void destroy() {
}
}
import cn.wolfcode.sso.util.HttpUtil;
import cn.wolfcode.sso.util.MockDatabaseUtil;
import cn.wolfcode.sso.vo.ClientInfoVo;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.List;
/**
* 监听session,用于退出所有客户端
*/
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
String token = (String) session.getAttribute("token");
//删除t_token表中的数据
MockDatabaseUtil.T_TOKEN.remove(token);
List<ClientInfoVo> clientInfoVoList = MockDatabaseUtil.T_CLIENT_INFO.remove(token);
try{
if(clientInfoVoList !=null){
for(ClientInfoVo vo:clientInfoVoList){
//获取出注册的子系统,依次调用子系统的登出的方法
HttpUtil.sendHttpRequest(vo.getClientUrl(),vo.getJsessionid());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.util.StreamUtils;
public class HttpUtil {
/**
* 模拟浏览器的请求
* @param httpURL 发送请求的地址
* @param params 请求参数
* @return
* @throws Exception
*/
public static String sendHttpRequest(String httpURL,Map<String,String> params) throws Exception{
//建立URL连接对象
URL url = new URL(httpURL);
//创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求的方式(需要是大写的)
conn.setRequestMethod("POST");
//设置需要输出
conn.setDoOutput(true);
//判断是否有参数.
if(params!=null&¶ms.size()>0){
StringBuilder sb = new StringBuilder();
for(Entry<String,String> entry:params.entrySet()){
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
//sb.substring(1)去除最前面的&
conn.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
}
//发送请求到服务器
conn.connect();
//获取远程响应的内容.
String responseContent = StreamUtils.copyToString(conn.getInputStream(),Charset.forName("utf-8"));
conn.disconnect();
return responseContent;
}
/**
* 模拟浏览器的请求
* @param httpURL 发送请求的地址
* @param jesssionId 会话Id
* @return
* @throws Exception
*/
public static void sendHttpRequest(String httpURL,String jesssionId) throws Exception{
//建立URL连接对象
URL url = new URL(httpURL);
//创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求的方式(需要是大写的)
conn.setRequestMethod("POST");
//设置需要输出
conn.setDoOutput(true);
conn.addRequestProperty("Cookie","JSESSIONID="+jesssionId);
//发送请求到服务器
conn.connect();
conn.getInputStream();
conn.disconnect();
}
}
import cn.wolfcode.sso.vo.ClientInfoVo;
import java.util.*;
/**
* 模拟数据库,保存子系统的session对象信息
*/
public class MockDatabaseUtil {
public static Set<String> T_TOKEN = new HashSet<String>();
public static Map<String,List<ClientInfoVo>> T_CLIENT_INFO =new HashMap<String,List<ClientInfoVo>>();
}
/**
* 客户端信息
*/
public class ClientInfoVo {
private String clientUrl;
private String jsessionid;
public String getClientUrl() {
return clientUrl;
}
public void setClientUrl(String clientUrl) {
this.clientUrl = clientUrl;
}
public String getJsessionid() {
return jsessionid;
}
public void setJsessionid(String jsessionid) {
this.jsessionid = jsessionid;
}
@Override
public String toString() {
return "ClientInfoVo{" +
"clientUrl='" + clientUrl + '\'' +
", jsessionid='" + jsessionid + '\'' +
'}';
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="cn.wolfcode.sso"/>
<!--mvc注解驱动支持-->
<mvc:annotation-driven/>
<!--静态资源处理-->
<mvc:default-servlet-handler/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>sso单点登录系统</title>
</head>
<body>
<h1>这是sso单点登录的首页</h1>
<div style="text-align:center;">
<a href="http://www.crm.com:8088/main" target="_blank">crm系统</a>        <a href="http://www.wms.com:8089/main" target="_blank">wms系统</a>       
<a href="/logOut">退出系统</a>
</div>
</body>
</html>
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>统一认证中心</title>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<script type="text/javascript" src="/static/js/jquery-latest.min.js"></script>
<script type="text/javascript" src="/static/js/placeholder.js"></script>
</head>
<body>
${errorMsg}
<form id="slick-login" method="post" action="/user_login">
<input type="hidden" name="redirectUrl" value="${redirectUrl}">
<label>username</label><input type="text" name="username" class="placeholder" placeholder="账号">
<label>password</label><input type="password" name="password" class="placeholder" placeholder="密码">
<input type="submit" value="登录">
</form>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>cn.wolfcode.sso.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>cn.wolfcode.sso.listener.MySessionListener</listener-class>
</listener>
</web-app>
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.wolfcode.sso</groupId>
<artifactId>server</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<name>server Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
</dependencies>
<build>
<finalName>server</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
单点退出操作:
单点退出流程:用户在SSO系统登录后创建token,保存token到数据库t_token表中,当子系统登录时,将该token的子系统信息:退出url和jsessionid信息保存到t_client_info表,一对多关系。当用户点击退出时,调用SSO系统/logOut方法,销毁sesseion,并在web.xml中注册session监听器,当有销毁操作时,调出t_client_info表对象信息,并依次调用子系统的/logOut方法,根据jsessionid销毁子系统中session对象信息,从而实现全部退出。
整体效果图:
输入账号密码登录后:
在SSO系统中分别进入CRM系统、WMS系统
三个系统中不管哪个系统点击退出系统,全部退出,并再次跳转到SSO系统登录界面。根据不同单点登录需求,修改代码,这里针对后台所有系统统一登录管理。