What is single sign-on

Single Sign On, or SSO for short, is one of the more popular solutions for enterprise business integration. SSO is defined as one login in multiple applications that allows users to access all trusted applications.

SSO generally requires an independent authentication center (Passport), and the subsystem login must pass Passport. The subsystem itself will not participate in the login operation. When a system successfully logs in, Passport will issue a token to each subsystem, which can obtain their own protected resources by holding the token. To reduce frequent authentication, each subsystem, after being authorized by Passport, establishes a local session and does not need to initiate authentication to Passport again for a certain period of time.

For example, Taobao and Tmall are both products of Ali. When a user logs in to Taobao and then opens Tmall, the system automatically helps the user log in to Tmall. Behind this phenomenon, single sign-on is used.

Single sign-on process

1. Login

  • When a user accesses the protected resource of system 1, system 1 finds that the user does not log in, switches to the SSO authentication center, and takes its own address as a parameter
  • The SSO authentication center detects that the user has not logged in and directs the user to the login page
  • The user enters the user name and password to submit a login application
  • The SSO authentication center verifies user information, creates a global session between the user and the SSO authentication center, and creates an authorization token
  • Sso authority redirects the original request address with the token (System 1)
  • System 1 obtains the token and goes to the SSO authentication center to verify whether the token is valid
  • The SSO authentication authority verifies the token, returns valid, and registers system 1
  • System 1 uses this token to create a session with the user, called a local session, that returns the protected resource
  • Users access protected resources in system 2
  • System 2 detects that the user does not log in, switches to the SSO authentication center, and sets its own address as a parameter
  • When the SSO authentication center detects that the user has logged in, the sso authentication center switches back to the address of system 2 with the token attached
  • System 2 obtains the token and goes to the SSO authentication center to verify whether the token is valid
  • Sso authentication center validates token, return valid, register system 2
  • System 2 uses this token to create a local session with the user and return the protected resource

After successful login, the user establishes sessions with the SSO authentication center and each subsystem. The sessions established between the user and the SSO authentication center are called global sessions, and those established between the user and each subsystem are called local sessions. After the local sessions are established, the user does not access the subsystem protected resources through the SSO authentication center. Global sessions and local sessions have the following constraints

  • If a local session exists, a global session must exist
  • Global sessions exist, but local sessions do not necessarily exist
  • Global session destruction, local session must be destroyed
2. Log out

  • The user sends a logout request to system 1
  • System 1 obtains the token based on the session ID established between the user and system 1 and sends a deregistration request to the SSO authentication center
  • Sso authenticates that the token is valid, destroys the global session, and extracts all system addresses registered with the token
  • Sso authentication center sends deregistration requests to all registration systems
  • Each registration system receives the logout request from the SSO authentication center and destroys the partial session
  • The SSO authentication center directs the user to the login page

What is the CAS

CAS stands for Central Authentication Service, an independent open instruction protocol. CAS is an open source project initiated by Yale University, which aims to provide a reliable single sign-on method for Web application systems. The CAS consists of the CAS Server and CAS Client. The CAS Server must be deployed independently to authenticate users. The CAS Client processes the access requests to protected resources on the Client and redirects the access requests to the CAS Server.

The basic CAS protocol process:

What is the OAuth2

OAuth (Open Authorization) is an open standard that allows a user to give third-party applications access to the user’s private resources (such as photos, videos, and contact lists) stored on a site without having to provide a user name and password to third-party applications.

Generally speaking, OAuth is an authorization protocol. As long as the authorized party and the authorized party abide by this protocol to write codes and provide services, the two parties have realized the OAuth mode.

Specifically, OAuth sets up an authorization layer between “clients” and “service providers.” Clients “cannot log directly into the” service provider, “but only into the authorization layer to separate users from clients.” The token used by the client to log into the authorization layer is different from the password used by the user. The user can specify the permission scope and validity period of the authorization layer token when logging in. After the client logs on to the authorization layer, the service provider opens the data stored by the user to the client based on the scope and validity of the token.

OAuth2 is the next version of OAuth1.0, OAuth2 focuses on simplicity for client developers while providing a dedicated authentication process for Web applications, desktop applications and mobile, and living room devices. Whereas the old OAuth would issue a very long token(typically one year or no lifetime limit), in OAuth 2.0, the Server would issue a short access token and a refresh token with a long lifetime. This allows the client to obtain a new Access token without the user having to do it again, and also limits the lifetime of the Access token.

CAS is different from OAuth2

  • CAS single sign-on ensures the security of user resources on the client, while OAuth2 ensures the security of user resources on the server.
  • The final information the CAS client needs to obtain is whether the user has the permission to access my (CAS client’s) resources. The final information oAuth2 gets is, can I (OAuth2 service provider) user resources are accessible to you (OAuth2 client);
  • In single sign-on (SSO) of the CAS server, resources are stored on the CAS client, not on the CAS server. After the user provides the user name and password to the CAS server, the CAS client is unaware of this. Just give the client a ST, so the client is not sure whether the ST is forged by the user or really effective, so take the ST to the server and ask again, the user gave me a valid ST or invalid ST, is valid I can let the user access.
  • OAuth2 authentication, resources are in the OAuth2 service provider that side, the client is to claim the user’s resources. Therefore, in the most secure mode, after the user is authorized, the server cannot directly return the token to the client through redirection, because the token may be intercepted by the hacker. If the hacker intercepts the token, the user’s resources will be exposed to the hacker. So the smart server sends an authentication code to the client (via redirection), and the client in the background, via HTTPS, obtains the token and refreshes the token using this code and another password that the client and the server have previously agreed on. This process is very secure. If the hacker intercepts the code, he will not be able to obtain the token without the pre-agreed password. In this way, OAuth2 can ensure that the request for resources is approved by the user, and the client is approved, and can safely send resources to the client.
  • The biggest difference in the process between CAS login and OAuth2 is whether a pre-negotiated password is required for authentication through ST or code.
Conclusion:
CAS: indicates the authorization server and authorized client
  1. The authorization server (one) stores a global session, while the clients (multiple) store their own sessions.
  2. When the client logs in, it checks whether its session is logged in. If it is not logged in, it (tells the browser) redirects to the authorization server (with its own address for callback).
  3. The authorization server determines whether the global session is logged in. If not, it directs the user to the login page, prompting the user to log in. After the login succeeds, the authorization server redirects the user to the client (with ticket [a credential number] as the parameter).
  4. After receiving the ticket, the client requests the server to obtain user information.
  5. After the server agrees to authorize the client, the server saves the user information to the global session, and the client saves the user information to the local session
OAuth2: Master system, authorized system (authorized to the master system, can be the same system as the master system), third party system
  1. The third-party system needs to use the resources of the primary system, and the third-party system redirects to the authorization system.
  2. According to different authorization modes, the authorization system prompts users to authorize.
  3. After user authorization, the authorization system returns an authorization certificate (accessToken) to the third party system [accessToken is valid].
  4. The third party uses accessToken to access the main system resources [After accessToken fails, the third party needs to request the authorization system again to obtain new accessToken].

What is the JWT

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and independent way to securely transfer information between parties as JSON objects. This information can be authenticated and trusted with a digital signature. JWT can sign using secret (using the HMAC algorithm) or using RSA or ECDSA’s public/private key pair.

The JSON WEB token structure consists of three parts:

  • Header: Includes the type of token and the hash algorithm being used.
  • Payload: A statement is a statement about an entity (usually a user) and other data. There are three types of claims: standard registration statements, public claims and private claims.
  • Signature: Must take the encoded header, encoded payload, secret, algorithm specified in the header, and sign it.
  1. Load – Standard declaration:
  • Iss: JWT issuer
  • Sub: The user JWT is targeting
  • Aud: The side receiving the JWT
  • Exp: the expiration time of the JWT. The expiration time must be greater than the issue time, which is a number of seconds
  • NBF: Define before what time the JWT is unavailable
  • Iat: issue time of JWT
  1. Load – Public declaration: Any information can be added, generally adding information about the user or other necessary information required by the business, but it is not recommended to add sensitive information, because this part can be decrypted by the client.
  2. Load-private declaration: a declaration defined by both the provider and the consumer. It is generally not recommended to store sensitive information because Base64 is symmetrically decrypted, meaning that part of the information can be classified as plaintext information.

To create a signature, you need to use the encoded header, payload and a secret key. The signature algorithm specified in the header is used. For example, if you want to use the HMAC SHA256 algorithm, Then the signature should create the HMACSHA256 (base64UrlEncode (header) +”.”+base64UrlEncode (Payload), secret) signature to verify the sender of the message and that the message has not been tampered with. The complete JWT output is in. The secret key is stored in the server. The server will generate token and authentication based on this key, so it needs to be protected. For more information, please go to the official website

Single sign-on on the front end

This code uses OAuth2. As for token storage, I have referred to many online tutorials, most of which store tokens in cookies and set cookies as top-level domains to solve cross-domain problems. However, our business needs are that top-level domains of some products are also different. Therefore, the implementation idea is to store the token in localStorage, and then use the new H5 attribute postMessage to achieve cross-domain sharing. For those who do not know about cross-domain sharing, please refer to my article.

Implementation idea: When a user accesses a system of the company (such as product.html), an IFrame is loaded in the product first. The iframe can obtain the token stored in localStorage. If the token is not obtained or the token expires, Iframe will internally redirect the user to the login page. After the user logs in from this page, the user will still obtain the token from the authentication system and save it in the localStorage of the IFrame page

<! --product.html-->
<head>
    <script src="Auth_1. 0.0 js." "></script>
</head>
<body>
    <h2>Product page</h2>
    <a onClick="login()" id="login">The login</a>
    <h3 id="txt"></h3>
</body>
<script>
var opts = {
    origin: 'http://localhost:8080'.login_path: '/login.html'.path: '/cross_domain.html'
}
// Load the iframe to this page with the SRC value cross_domain.html
var auth = new ssoAuth(opts);
function getTokenCallback(data) {
    // Jump to the login page if there is no token
    if(! data.value){ auth.doWebLogin(); }// If there is a token, display it directly on the page and then do other operations
    document.getElementById('txt').innerText = 'token=' + data.value;
}
// Get the token stored in the iframe named cross_domain
auth.getToken(getTokenCallback);
</script>
Copy the code

After instantiating ssoAuth in product.html, this page introduces iframe to the previous page, the value named opts.path, or cross_domain.html. Auth.gettoken () gets the localStorage value in this iframe page.

/ / auth_1. 0.0. Js
function ssoAuth(opts) {
    this._origin = opts.origin,
    this._iframe_path = opts.path,
    this._iframe = null.this._iframe_ready = false.this._queue = [],
    this._auth = {},
    this._access_token_msg = { type: "get".key: "access_token" },
    this._callback = undefined,
    that = this;
    
    // Check whether postMessage and localStorage are supported
   var supported = (function () {
        try {
            return window.postMessage && window.JSON && 'localStorage' in window && window['localStorage']! = =null;
        } catch (e) {
            return false;
        }
    })();
    
    _iframeLoaded = function () {
        that._iframe_ready = true
        if (that._queue.length) {
            for (var i = 0, len = that._queue.length; i < len; i++) {
                _sendMessage(that._queue[i]);
            }
            that._queue = [];
        }
    }

    _sendMessage = function (data) {
        // Through the contentWindow attribute, the script can access the WINDOW object of the HTML page contained in the iframe element.
        that._iframe.contentWindow.postMessage(JSON.stringify(data), that._origin);
    }
    
    // Get the token, but since the iframe has not been loaded, first store the message in queue _queue
    this._auth.getToken = function (callback) {
        that._callback = callback
        if (that._access_token_msg && that._iframe_ready) {
            // When the iframe is loaded, send a message to the page where the iframe resides
            _sendMessage(that._access_token_msg);
        } else{ that._queue.push(that._access_token_msg); }}var _handleMessage = function (event) {
        if (event.origin === that._origin) {
            var data = JSON.parse(event.data);
            if (data.error) {
                console.error(event.data)
                that._callback({ value: null });
                return;
            }
            if (that._callback && typeof that._callback === 'function') {
                that._callback(data);
            } else {
                console.error("callback is null or not a function, please "); }}}this._auth.doWebLogin = function () {
        window.location.href = opts.origin + opts.login_path + "? redirect_url=" + window.location.href
    }
    // Initializes an iframe and appends it to the bottom of the parent page
    if (!this._iframe && supported) {
        this._iframe = document.createElement("iframe");
        this._iframe.style.cssText = "position:absolute; width:1px; height:1px; left:-9999px;";
        document.body.appendChild(this._iframe);

        if (window.addEventListener) {
            this._iframe.addEventListener("load".function () {
                _iframeLoaded();
            }, false);
            window.addEventListener("message".function (event) {
                _handleMessage(event)
            }, false);
        } else if (this._iframe.attachEvent) {
            this._iframe.attachEvent("onload".function () {
                _iframeLoaded();
            }, false);
            window.attachEvent("onmessage".function (event) {
                _handleMessage(event)
            });
        }
        this._iframe.src = this._origin + this._iframe_path;
    }
    return this._auth;
}
Copy the code
<! --cross_domain.html-->
<script type="text/javascript">
    (function () {

        / / white list
        var whitelist = ["localhost"."127.0.0.1"."^.*\.domain\.com"];

        function verifyOrigin(origin) {
            var domain = origin.replace(/^https? : \ \ / | : \ d {1, 4} $/ g."").toLowerCase(),
                i = 0,
                len = whitelist.length;

            while (i < len) {
                if (domain.match(new RegExp(whitelist[i]))) {
                    return true;
                }
                i++;
            }
            return false;
        }

        function handleRequest(event) {
            // Whitelist is valid
            if (verifyOrigin(event.origin)) {
                var request = JSON.parse(event.data);
                if (request.type == 'get') {
                    var idi = localStorage.getItem("idi");
                    if(! idi) {// source: a reference to the window object that sent the message. Event. source is only a proxy for the window object
                        event.source.postMessage(JSON.stringify({ key: request.key, value: null }), event.origin);
                        return;
                    }
                    value = JSON.parse(idi)[request.key];
                    event.source.postMessage(JSON.stringify({ key: request.key, value: value }), event.origin);
                } else {
                    event.source.postMessage(JSON.stringify({ error: "Not supported".error_description: "Not supported message type"}), event.origin); }}}// Receive messages from iframe
        if (window.addEventListener) {
            window.addEventListener("message", handleRequest, false);
        } else if (window.attachEvent) {
            window.attachEvent("onmessage", handleRequest);
        }
    })();
</script>
Copy the code
<! --login.html-->
<head>
    <script src="Auth_1. 0.0 js." "></script>
</head>
<body>
    <form>
        <input type="text" placeholder="Username" id="user">
        <input type="password" placeholder="Password" id="pwd">
    </form>
    <button onClick="login()">Log in</button>
</body>
<script>
    function login() {
        var name = document.getElementById('user')
        var pwd = document.getElementById('pwd')

        var expires_in = 7200
        // If this is the json data returned by the background developer after a successful login
        var res = { 
            access_token: "xxxxx.yyyyy.zzzzz".expires_at: expires_in * 1000 + new Date().getTime(), 
            refresh_token: "yyyyyyyyyyyyyyyyyyyyyyyyyyyy" 
        };
        localStorage.setItem("idi".JSON.stringify(res))
        // Return to the original page after successful login
        window.location.href = getQueryString("redirect_url")}function getQueryString(name) {
        var reg = new RegExp("(^ | &)" + name + "= (/ ^ & *) (& | $)");
        var r = window.location.search.substr(1).match(reg);
        if(r ! =null) return unescape(r[2]); return null;
    }
</script>
Copy the code

PS: Cancel temporarily not done. In addition, postMessage has compatibility problems, if other friends have a better method, please share, thank you ~

Reference:

  • www.cnblogs.com/ywlaker/p/6…
  • www.ruanyifeng.com/blog/2014/0…
  • www.cnblogs.com/flying607/p…
  • 375287760. iteye.com/blog/240097…