SSO 单点登录

版权声明:https://xianzhan.github.io/ https://blog.csdn.net/Ro_bot/article/details/53842062

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。实现 SSO 的主要工具是 Cookie

实现步骤

SSO_Step

简的来说:申请票据、存储票据、查验票据。

同域 SSO 登录

同域SSO

此处使用 struts2 作为 MVC 框架,可根据实际需要替换

登录界面

接收用户登录信息

<%@ page contentType="text/html;charset=UTF-8" language="java"
         pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, inital-scale=1">
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <center>
        <h1>请登录</h1>
        <form action="/sso/doLogin.action" method="post">
            <span>用户名:</span><input type="text" name="username">
            <span>密码&nbsp;&nbsp;:</span><input type="password"
                                               name="password"><br>
            <%-- gotoUrl 包含从其它页面过来的地址,登录完毕后跳转回 gotoUrl --%>
            <input type="hidden" name="gotoUrl" value="${gotoUrl}">
            <input type="submit">
        </form>
    </center>
</body>
</html>

处理用户登录信息

import com.opensymphony.xwork2.ActionSupport;
import lee.sso.util.SSOCheck;
import org.apache.struts2.ServletActionContext;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

public class SSOAction extends ActionSupport {

    private String username;
    private String password;
    private String gotoUrl;

    public String doLogin() {
        /*
        此处不进行数据用户数据验证,
        只关注 sso
         */
        boolean ok = SSOCheck.checkLogin(username, password);
        if (ok) {
            /*
            此处 cookie 值应为加密后的值,此处亦省去
             */
            Cookie cookie = new Cookie("ssocookie", "sso");
            cookie.setPath("/");
            HttpServletResponse response = ServletActionContext.getResponse();
            response.addCookie(cookie);
            return SUCCESS;
        }
        // 失败此处不做处理,或者返回登录页面
        return null;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGotoUrl() {
        return gotoUrl;
    }

    public void setGotoUrl(String gotoUrl) {
        this.gotoUrl = gotoUrl;
    }
}

处理登录信息工具类

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

public class SSOCheck {
    public static final String USERNAME = "user";
    public static final String PASSWORD = "123";

    public static boolean checkLogin(String username, String password) {
        return username.equals(USERNAME) && password.equals(PASSWORD) ? true
                : false;
    }

    /**
     * 实际开发时应放在拦截器,此处为了方便
     *
     * @param request
     * @return
     */
    public static boolean checkCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("ssocookie") && cookie.getValue()
                        .equals("sso"))
                    return true;
            }
        }
        return false;
    }
}

登录页面的 struts2 配置

<package name="sso" namespace="/sso" extends="struts-default">
        <action name="doLogin" class="lee.sso.action.SSOAction" method="doLogin">
            <result name="success" type="redirect">${gotoUrl}</result>
        </action>
    </package>

编写 domain1

主页,当为用户访问时未登录将先跳转登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java"
         pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, inital-scale=1">
    <meta charset="UTF-8">
    <title>domain1 的标题</title>
</head>
<body>
    <h1>欢迎访问 domain1,这是 domain1 的主页</h1>
</body>
</html>

domain1 Action 类

import com.opensymphony.xwork2.ActionSupport;
import lee.sso.util.SSOCheck;
import org.apache.struts2.ServletActionContext;

import javax.servlet.http.HttpServletRequest;

public class Domain1Action extends ActionSupport {

    private String gotoUrl;

    public String main() {
        HttpServletRequest request = ServletActionContext.getRequest();
        if (SSOCheck.checkCookie(request)) {
            return SUCCESS;
        }
        // 此处声明登录完成后跳转的页面
        gotoUrl = "/domain1/main.action";
        return LOGIN;
    }

    public String getGotoUrl() {
        return gotoUrl;
    }

    public void setGotoUrl(String gotoUrl) {
        this.gotoUrl = gotoUrl;
    }
}

domain1 的 struts2 配置

<package name="domain1" namespace="/domain1" 
             extends="struts-default">
        <action name="main" class="domain1.Domain1Action"
                method="main">
            <result name="success">/domain1.jsp</result>
            <result name="login">/login.jsp</result>
        </action>
    </package>

domain2 与 domain1 区别不大,只需将 1 复制一份,将 1 改为 2 即可。

效果

填写 domain1 (别点提交)

登录 domain1

进入 domain2 看看

domain2

domain1 点解提交

domain1主页

此时刷新 domain2 登录界面

domain2 主页

神奇的一幕发生了,不需要登录信息直接进入 domain2 的主页

这是因为在登录 domain1 后服务器向我们发送了一个 cookie,浏览器保存了。当刷新 domain2 时,浏览器带着未过期的 cookie 给服务器发请求,服务器判断此 cookie 的会话是否还有效,若有,则直接跳过登录页面进入 domain2 主页。

cookie


同父域 SSO 登录

若在同一机器部署测试,可更改文件C:\Windows\System32\drivers\etc\hosts文件

同父域 SSO 登录

由图可看出,同父域的 SSO 登录与同域 SSO 登录并无多大区别,需要解决的问题有两个,一个是 cookie 的跨域问题以及服务器之间的通信。可根据同域 SSO 登录修改代码。

此处主要使用了子域可访问父域cookie的机制。设置 cookie 时,需要我们统一一个 token ,即 cookie 的变量名。

我们访问http://demo1.x.com/demo1/main.action时,查看 request.getCookies()是否包含我们所需的信息,若无,则带着验证后跳转地址<input type="hidden" name="gotoUrl">跳转到统一验证接口http://check.x.com/sso/doLogin.action验证用户名和密码。

统一验证接口返回 cookie 之前,必须设置

// 由 . 开头,则只有子域名可用。若无,则主域名和子域名可用
cookie.setDomain(".x.com");  // 同一父域名
cookie.setPath("/");

浏览器保存cookie后,带着cookie跳转回
http://demo1.x.com/demo1/main.action
要设置setDomain(),否则请求无法携带cookie

服务器之间的通信

http://demo1.x.com/demo1/main.action的服务器接收到cookie时,将cookie名cookie值发给统一验证接口http://check.x.com/sso/checkCookie.action处理,约定好返回值是什么,成功返回后判断是否让其访问资源。

可通过 HttpURLConnection 类或者使用 HttpClient 客户端进行通信。


完全跨域 SSO 登录

即域名完全不同,如www.a.comwww.b.com

完全跨域SSO

登录验证

freecodecampLogin

进入www.a.com资源页面,查看cookie,若无,则跳转登录页面。

www.a.com服务器接收用户信息数据,但不验证,将用户信息数据传输http://www.x.com/sso/doLogin.action验证。

<form action="/doLogin.action"></form>

进入 action 类,根据代码逻辑与验证服务器通信

result = SSOUtil.post(checkSSOUrl, username, password);

验证成功后返回信息给www.a.com,判断可访问资源后并向浏览器发送 cookie 存放确认用户身份的 token(即 cookieValue)

Cookie cookie = new Cookie(cookieName, cookieValue);
HttpServletResponse response = ServletActionContext.getResponse();
response.addCookie(cookie);

问题此cookie只有www.a.com才能访问,所以,需要在确定用户身份后,访问资源页面时向www.b.com发送请求,并让www.b.com向浏览器发送cookie的值

www.a.comwww.b.comaction类里添加方法,传送兄弟 URL 和传参,则无论是访问 a 或 b ,登录成功后在cookie有效期内都能访问 b 或 a 的资源。

List<String> urlList; // 存放兄弟 URL
String cookieName;
String cookieValue;

...
// Struts2 的 OGNL
setter/getter
...

public String main() {
    ...
    urlList.add("www.b.com/demo2/pushCookieToBrowser.action"
        + "?cookieName=" + cookieName
        + "&cookieValue=" + cookieValue);
    ...
}

public void pushCookieToBrowser() {
    HttpServletRequest request = ServletActionContext.getRequest();
    this.cookieName = request.getParameter("cookieName");
    this.cookieValue = request.getParameter("cookieValue");
    Cookie cookie = new Cookie(cookieName, cookieValue);
    HttpServletResponse response = ServletActionContext.getResponse();
    response.addCookie(cookie);
}

在登录成功的资源页面使用 <iframe> 将返回数据信息循环请求发给兄弟服务器域名,让兄弟域名向浏览器发送 cookie,完成跨域 SSO

www.a.com loginSuccess.jsp

<c:forEach var="url" items="${urlList}">
    <%-- 隐藏 iframe --%>
    <iframe src="${url}" width="0px" hight="0px" style="dispaly: none"></iframe>
</c:forEach>

再访问 b 的资源时将不需要再次输入密码。


注意

  1. 核心是Cookie,需要注意设置的域、位置和安全性。
  2. 应用群的安全问题:木桶效应。

资料

慕课视频

单点登录原理与简单实现

展开阅读全文

没有更多推荐了,返回首页