java单点登录需求分析与代码实现

需求背景:随着公司的发展,公司内部使用的系统越来越多。但是对于使用系统的员工来说不是个好事情。
     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&&params.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&&params.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>&emsp; &emsp; &emsp; &emsp; <a href="http://www.wms.com:8089/main" target="_blank">wms系统</a>&emsp; &emsp; &emsp; &emsp;
    <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系统登录界面。根据不同单点登录需求,修改代码,这里针对后台所有系统统一登录管理。

发布了43 篇原创文章 · 获赞 32 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yz2015/article/details/84141889
今日推荐