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系统…)项目结构图:
在这里插入图片描述
在这里插入图片描述
代码:
pom.xml

<?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>cn.wolfcode.sso</groupId>
    <artifactId>client-crm</artifactId>
    <version>1.0-SNAPSHOT</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.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

package cn.wolfcode.sso.util;

import org.springframework.util.StreamUtils;
import sun.net.www.protocol.http.HttpURLConnection;

import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {

    /**
     * 模拟浏览器的请求
     * @param httpURL 发送请求的地址
     * @param params  请求参数
     * @return
     */
    public static String sendHttpRequest(String httpURL, Map<String,String> params) throws Exception {
        //建立URL连接对象
        URL url = new URL(httpURL);
        //创建连接
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        //设置请求的方式 需要大写
        connection.setRequestMethod("POST");
        //设置需要输出
        connection.setDoOutput(true);
        //判断是否有参数
        if( params != null && params.size()>0){
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String,String> entry:params.entrySet()){
                sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
            }
            //sb.substring(1)去掉最前面的&
            connection.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
        }
        //发送请求到服务器
        connection.connect();
        //获取远程响应的内容
        String responseContent = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("utf-8"));
        connection.disconnect();
        return responseContent;
    }

    /**
     * 模拟浏览器的请求
     * @param httpURL 发生请求的地址
     * @param jessionId 会话 Id
     *
     */
    public static void sendHttpRequest(String httpURL, String jessionId) throws Exception {
       //建立URL连接对象
       URL url = new URL(httpURL);
       //创建连接
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        //设置请求的方式  需要大写
        connection.setRequestMethod("POST");
        //设置需要输出
        connection.setDoOutput(true);
        connection.addRequestProperty("Cookie","JSESSIONID="+jessionId);
        //发送请求到服务期
        connection.connect();
        connection.getInputStream();
        connection.disconnect();
    }
}


package cn.wolfcode.sso.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;

public class SSOClientUtil {
    private static Properties properties = 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 {
            properties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
        }catch (Exception e){
            e.printStackTrace();
        }
        SERVER_URL_PREFIX = properties.getProperty("server-url-prefix");
        CLIENT_HOST_URL = properties.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";
    }
}

package cn.wolfcode.sso.filter;


import cn.wolfcode.sso.util.HttpUtil;
import cn.wolfcode.sso.util.SSOClientUtil;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SSOClientFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String path = request.getRequestURI();
        System.out.println("crm:"+path);
        HttpSession httpSession = request.getSession();
        //1.判断是否有局部的会话
        Boolean isLogin = (Boolean) httpSession.getAttribute("isLogin");
        if(isLogin != null && isLogin){
            //有局部会话,直接放行
            filterChain.doFilter(request,response);
            return;
        }
        // 判断地址栏中是否有携带token参数
        String token = request.getParameter("token");
        if(StringUtils.isNoneBlank(token)){
            // token信息不为null, 说明地址中包含了token,拥有令牌。
            //判断token信息是否有认证中心产生的
            String httpURL = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
            Map<String,String> params = new HashMap<>();
            params.put("token",token);
            params.put("clientUrl", SSOClientUtil.getClientLogOutUrl());
            params.put("jessionid",httpSession.getId());
            try {
                String isVerify = HttpUtil.sendHttpRequest(httpURL,params);
                if("true".equals(isVerify)){
                    //如果返回的字符串是true,说明这个token是由统一认证中心产生的
                    //创建局部的会话
                    httpSession.setAttribute("isLogin",true);
                    //放行该次的请求
                    filterChain.doFilter(request,response);
                    return;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        //没有局部的会话,重定向到统一认证中心,检查是否有其他的系统已经登录过
        // http://www.sso.com:8443/checkLogin?redirectUrl=http://www.crm.com:8088
        SSOClientUtil.redirectToSSOURL(request,response);
    }

    @Override
    public void destroy() {

    }
}

package cn.wolfcode.sso.controller;

import cn.wolfcode.sso.util.SSOClientUtil;

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 java.io.IOException;

@WebServlet(name="mainServlet",urlPatterns = "/main")
public class MainServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
        this.doPost(req,res);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
        req.setAttribute("serverLogOutUrl", SSOClientUtil.getServerLogOutUrl());
        req.getRequestDispatcher("/WEB-INF/views/main.jsp").forward(req,res);
    }
}

package cn.wolfcode.sso.controller;

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 java.io.IOException;

@WebServlet(name="LogOutServlet.java", urlPatterns = "/logOut")
public class LogOutServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res)throws ServletException , IOException {
        doPost(req,res);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException , IOException {
        req.getSession().invalidate();
    }
}

package cn.wolfcode.sso.controller;

import cn.wolfcode.sso.util.SSOClientUtil;

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 java.io.IOException;

@WebServlet(name="mainServlet",urlPatterns = "/main")
public class MainServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
        this.doPost(req,res);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
        req.setAttribute("serverLogOutUrl", SSOClientUtil.getServerLogOutUrl());
        req.getRequestDispatcher("/WEB-INF/views/main.jsp").forward(req,res);
    }
}

sso.properties
crm 和wms的 properties 的 client-host-url 不一样

server-url-prefix=http://www.sso.com:8080
client-host-url=http://www.crm.com:8088

main.jsp

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/6/21
  Time: 15:59
  To change this template use File | Settings | File Templates.
--%>

<%@ 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>

服务端(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字符串.
项目结构图:
在这里插入图片描述
pom.xml

<?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>cn.wolfcode.sso</groupId>
    <artifactId>sso-server</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <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.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

package cn.wolfcode.sso.vo;

import lombok.Data;

@Data
public class ClientInfoVo {
    private String clientUrl;
    private String jsessionId;

}

package cn.wolfcode.sso.util;

import cn.wolfcode.sso.vo.ClientInfoVo;

import java.util.*;

/**
 * 模拟数据库,保存子系统的session对象信息
 */
public class MockDatabaseUtil {
    public static Set<String> T_TOKEN = new HashSet<>();

    public static Map<String, List<ClientInfoVo>> T_CLIENT_INFO = new HashMap<>();

}


package cn.wolfcode.sso.util;

import org.springframework.util.StreamUtils;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {

    public static String sendHttpRequest(String httpURL, Map<String, String> params) throws Exception {
        //建立URL连接对象
        URL url = new URL(httpURL);
        //建立连接
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        //设置请求的方式 需要是大写的
        connection.setRequestMethod("POST");
        //设置需要输出
        connection.setDoOutput(true);
        if (params != null && params.size()>0) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
            }
            //sb.substring(1)去除最前面的&
            connection.getOutputStream().write(sb.substring(1).toString().getBytes("utf-8"));
        }
        //发送请求到服务器
        connection.connect();
        //获取远程响应的内容
        String responseContent = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("utf-8"));
        connection.disconnect();
        return responseContent;
    }

    /**
     * 模拟浏览器的请求
     * @param httpURL
     * @param jsessionId
     * @return
     */
    public static void sendHttpRequest(String httpURL, String jsessionId) throws Exception {
        //建立URL 连接对象
        URL url = new URL(httpURL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.addRequestProperty("Cookie","JSESSION="+jsessionId);
        connection.connect();
        connection.getInputStream();
        connection.disconnect();
    }

    public static void sendHttpRequest(String httpUrl) throws Exception {
        URL url = new URL(httpUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.connect();
        connection.getInputStream();
        connection.disconnect();
    }
}
package cn.wolfcode.sso.listener;

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;

public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {

    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.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();
        }
    }
}
package cn.wolfcode.sso.filter;

import com.sun.deploy.net.HttpResponse;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @category :登录拦截器
 *
 */
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //如果是登录有关操作的,不拦截
        String path = request.getRequestURI();
        //1.session还有效
        HttpSession session = request.getSession();
        String token = (String) session.getAttribute("token");
        if (token != null) {
            //放行
            filterChain.doFilter(request,response);
            return;
        }
        if (path.contains("login") || path.endsWith(".css") || path.endsWith(".js")) {
            //放行
            filterChain.doFilter(request, response);
            return;
        } else {
            System.out.println("path:"+path);
            if (path.endsWith("verify")) {
                //放行
                filterChain.doFilter(request,response);
                return;
            }else {
                response.sendRedirect("login");
            }
        }

    }

    @Override
    public void destroy() {

    }
}
package cn.wolfcode.sso.controller;

import cn.wolfcode.sso.util.HttpUtil;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;
import java.util.*;

@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;
        }
    }

    /**
     * 登录功能
     * @return
     */
    @RequestMapping("/user_login")
    public String login(String username, String password, String redirectUrl, HttpSession httpSession, Model model){
        if ("admin".equals(username)&&"123456".equals(password)) {
            //账号密码匹配
            //1.创建令牌信息
            String token = UUID.randomUUID().toString();
            //2.创建全局的会话,把令牌信息放入会话中,
            httpSession.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;
        }
        //如果账号密码有误
        if(!StringUtils.isEmpty(redirectUrl)){
            model.addAttribute("redirectUrl",redirectUrl);
        }
        return "redirect:login";
    }

    @RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token, String clientUrl, String jsessionId) {
        if (MockDatabaseUtil.T_TOKEN.contains(token)) {
            //把客户端的登出地址记录
            List<ClientInfoVo> clientInfoVoList = MockDatabaseUtil.T_CLIENT_INFO.get(token);
            if (clientInfoVoList == null) {
                clientInfoVoList = new ArrayList<ClientInfoVo>();
                MockDatabaseUtil.T_CLIENT_INFO.put(token,clientInfoVoList);
            }
            ClientInfoVo vo = new ClientInfoVo();
            vo.setClientUrl(clientUrl);
            vo.setJsessionId(jsessionId);
            clientInfoVoList.add(vo);
            //说明令牌有效,返回true
            return "true";
        }
        return "false";
    }

    @RequestMapping("/logOut")
    public String logOut(HttpSession session){
        //销毁全局会话
        session.invalidate();
        return "redirect:login";
    }
}

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="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.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>

单点退出操作:
在这里插入图片描述
单点退出流程:用户在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系统登录界面。根据不同单点登录需求,修改代码,这里针对后台所有系统统一登录管理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值