从登录验证到单点登录:一文读懂Cookie、Session、Token和SSO

2,189 阅读14分钟

身份验证和授权是Web应用程序中必不可少的功能之一。当用户登录到Web应用程序时,Web应用程序需要验证用户的身份,并根据用户的权限来授权其访问相应的资源。为了实现身份验证和授权,Web应用程序通常需要使用一些技术手段,例如Cookie、Session和Token等。

本文主要介绍了常见的Web应用程序中实现用户身份验证和授权的技术手段,包括Cookie、Session、Token和SSO等。

cookie

Untitled (7).png 我们这里讲的Cookie,是指存储在Web浏览器本地的小型数据文件,cookie里的数据通常由Web服务器发送给浏览器,并由浏览器存储在本地,里面一般存储的是用户的身份验证信息,例如用户名,密码,用户ID,过期时间等等.

Cookie的历史可以追溯到1994年,当时Netscape公司的工程师Lou Montulli创建了Cookie的最初版本。当时,Cookie是为了解决Web服务器无法跟踪用户会话状态的问题而设计的,因为当用户在浏览器中请求Web页面时,Web服务器无法区分不同用户的请求。

最初的Cookie设计仅支持存储少量的文本数据,并且没有任何安全保护机制,由此产生了许多安全问题。1997年,Netscape公司发布了新的Cookie规范,支持更多的Cookie属性和安全机制,例如过期时间、域名限制、安全标识和HTTP-only标识等。这些改进使得Cookie更加安全和有用,成为Web应用程序中实现用户身份验证和授权的重要技术手段之一。

使用cookie的基本流程

  • 首先,客户端会发送一个http请求到服务器端。
  • 服务器端接受客户端请求后,建立一个session,并发送一个http响应到客户端,这个响应头,其中就包含Set-Cookie头部。
  • 客户端收到响应后,浏览器会将cookie存储下来
  • 客户端发起第二次请求,浏览器会自动在请求头中添加cookie
  • 服务器接收请求,分解cookie,验证信息,核对成功后返回response给客户端

以下是在SpringBoot项目中设置和解析cookie的例子

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CookieController {

    @GetMapping("/get-cookie")
    public String getCookie(HttpServletRequest request) {
        String cookieValue = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("my_cookie")) {
                    cookieValue = cookie.getValue();
                    break;
                }
            }
        }
        return "Cookie value: " + cookieValue;
    }

    @GetMapping("/set-cookie")
    public void setCookie(HttpServletResponse response) {
        ResponseCookie cookie = ResponseCookie.from("my_cookie", "cookie_value")
                .maxAge(3600)
                .build();
        response.addHeader("Set-Cookie", cookie.toString());
    }
}

session

Session是一种在Web服务器上存储用户会话数据的机制,当用户登录到Web应用程序时,Web应用程序将用户的身份验证信息和其他会话数据存储在Web服务器上的会话中,同时创建一个唯一的会话ID,并将其存储在Cookie中发送给浏览器。

最初的Session机制是通过URL重写实现的,即将Session ID作为URL的一部分传递给Web服务器。但是,这种方式存在许多问题,例如URL泄漏、搜索引擎索引和缓存等。

随着时间的推移,Session机制得到了改进和加强,Session机制逐渐过渡到使用Cookie来存储Session ID。这种方式更加安全和可靠,因为Cookie可以被浏览器自动管理,并且可以设置Cookie的过期时间和域名限制等属性。

cookie与session

从上面的介绍我们可以看出,所谓的用 cookie 做登陆态管理,指的是将大部分会话信息存储在 cookie 中;用 session 做登陆态管理,指的是将大部分会话信息存储在服务器上。

只用session不用cookie,或是只用cookie,不用session在理论上都可以保持会话状态。但是在实际中因为多种原因,一般不会单独使用,都是同时使用Cookie和Session来实现用户身份验证和授权。

Cookie和Session都有其优缺点,具体取决于应用程序的需求和实际情况。

Cookie的优点

  1. 可以轻松地在Web浏览器和Web服务器之间共享状态,无需在Web服务器上存储任何数据。
  2. 可以在Web应用程序之间共享状态,因为Cookie是存储在Web浏览器中的。
  3. 可以设置Cookie的过期时间和域名限制等属性,从而提高Cookie的安全性。

Cookie的缺点

  1. Cookie的大小有限制,通常不超过4KB,因此无法存储大量的数据。
  2. Cookie的数据存储在Web浏览器中,可能会被恶意攻击者读取或修改,从而导致安全风险。
  3. Cookie可以被用户禁用或删除,从而使Web应用程序无法正常工作。

Session的优点

  1. 可以存储大量的数据,在Web服务器上存储数据,因此没有Cookie大小的限制。
  2. Session数据存储在Web服务器上,相对于Cookie来说更加安全可靠,不易被恶意攻击者读取或修改。
  3. 可以设置Session的过期时间和其他安全属性,从而提高Session的安全性。

Session的缺点

  1. Session数据存储在Web服务器上,因此在分布式环境下,可能需要使用共享存储或其他技术来实现Session的共享和同步。
  2. 如果Web应用程序使用多个Web服务器,可能需要使用负载均衡器或其他技术来确保用户的请求被路由到使用相同Session存储的Web服务器中。
  3. Session数据存储在Web服务器上,可能会对Web服务器的性能产生影响,尤其是在存储大量Session数据时。

token

Token又叫令牌,是服务端生成用来验证客户端身份的凭证,一般是一串加密字符串,客户端每次请求都携带Token。

token的使用流程

  • 客户端向服务端发送认证信息(例如账号密码)
  • 服务端根据客户端提供的认证信息执行验证逻辑,如果验证成功则生成Token并返回。
  • 客户端存储收到的Token,一般存放于localStorage,cookie,或sessionStorage中。
  • 再次请求时携带Token(可以通过HTTP请求头Authorization字段)。
  • 服务端校验Token(如查询数据库),并根据业务逻辑返回相应的数据。

Token认证优点

  • 客户端可以用Cookie、LocalStorage等存储,服务端不需要存储。
  • 安全性高(有签名校验)。
  • 支持移动APP端。
  • 支持跨域。

Token认证缺点

  • 占用额外传输宽带,因为Token比较大,可能会消耗一定的流量。
  • 每次签名校验会消耗服务端性能。

JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用间传递声明式的身份验证信息。它是一种轻量级的标准,,以JSON格式进行编码,由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

头部包含了JWT的类型和使用的算法信息。载荷包含了一些声明,例如用户的身份信息和权限等。签名用于验证JWT的完整性和真实性。

在实际应用中,JWT通常用于在客户端和服务器之间进行身份验证和授权,例如在Web应用程序和API中。客户端在登录后会收到一个包含JWT的响应,然后将JWT存储在本地,并在后续的请求中将其作为Authorization头部中的Bearer Token进行传递。服务器会验证JWT的签名,从而确认请求的发起者的身份和权限。

下面是一个简单的示例,演示如何在Spring Boot项目中设置和解析JWT,使用了Spring框架提供的io.jsonwebtoken类库:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

@Component
public class JwtUtils {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private int expiration;

    @PostConstruct
    public void init() {
        secret = Base64.getEncoder().encodeToString(secret.getBytes());
    }

    public String generateToken(String username) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration * 1000);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

在这个示例中,我们创建了一个名为JwtUtils的类,用于设置和解析JWT。这个类包含了以下方法:

  1. generateToken - 用于生成JWT。它接受一个用户名作为参数,并返回一个包含JWT的字符串。
  2. getUsernameFromToken - 用于从JWT中获取用户名。它接受一个包含JWT的字符串作为参数,并返回一个字符串表示用户名。
  3. validateToken - 用于验证JWT的有效性。它接受一个包含JWT的字符串作为参数,并返回一个布尔值,表示JWT是否有效。

需要注意的是,在使用JWT时,我们需要设置一个密钥来签名和验证JWT。在这个示例中,我们使用了@Value注解来从配置文件中获取密钥和过期时间,并使用Base64编码将其转换为字符串。我们还使用了@PostConstruct注解来在初始化JwtUtils对象时对密钥进行初始化。

单点登录(sso)

单点登录(Single Sign-On,简称SSO)是一种身份验证机制,它允许用户使用一组凭据(例如用户名和密码)登录到一个系统,然后在不需要重新输入凭据的情况下访问其他相关系统。

CAS

CAS(Central Authentication Service,中央认证服务)是一种单点登录协议,它提供了一种安全的身份验证机制,可以让用户使用一组凭据登录到多个应用程序中,而无需在每个应用程序中单独登录。CAS是一种开放源代码软件,由耶鲁大学开发,目前已经成为一种广泛使用的单点登录解决方案。

CAS的工作流程如下:

  • 用户向CAS客户端提供凭据进行身份验证。
  • CAS客户端将凭据发送到CAS服务器进行验证。
  • CAS服务器验证凭据,并向CAS客户端返回一个票据(ticket)。
  • CAS客户端将票据发送到目标应用程序进行验证。
  • 目标应用程序向CAS服务器发送票据进行验证。
  • CAS服务器验证票据,并向目标应用程序返回用户信息。
  • 目标应用程序使用用户信息进行授权和访问控制。

我们举一个具体的例子,假如我们有一个专门用来登录的域名如(ouath.com)来提供所有系统的token。当业务系统被打开时,借助中心授权系统进行登录。一个简要的流程如下:

  • 当b.com打开时,发现自己未登陆,于是跳转到ouath.com去登陆。
  • ouath.com登陆页面被打开,用户输入帐户/密码登陆成功。
  • ouath.com登陆成功,种cookie到ouath.com域名下。
  • 把sessionid放入后台redis,存放<ticket,sesssionid>数据结构,然后页面重定向到A系统。
  • 当b.com重新被打开,发现仍然是未登陆,但是有了一个ticket值。
  • 当b.com用ticket 值,到redis里查到sessionid,并做session同步,然后种cookie给自己,页面原地重定向。
  • 当b.com打开自己页面,此时有了cookie,后台校验登陆状态,成功。

OAuth

OAuth是一种授权机制,主要用于授权第三方应用程序访问受保护资源。OAuth的主要目的是让用户授权第三方应用程序访问其数据,同时保护用户的账户信息不被泄露。

使用OAuth来实现SSO需要采用OAuth2.0协议,并结合其他技术实现。以下是一种基于OAuth2.0的SSO实现方案:

配置授权服务器

授权服务器负责验证用户身份和颁发访问令牌,因此需要配置一个授权服务器。在授权服务器上,需要配置OAuth2.0的授权端点(Authorization Endpoint)和令牌端点(Token Endpoint),并设置授权服务器的客户端ID和客户端密钥。

配置客户端

客户端需要向授权服务器请求访问令牌,并将令牌发送给资源服务器进行身份验证。在客户端上,需要配置OAuth2.0的客户端ID和客户端密钥,并使用OAuth2.0的授权码授权流程(Authorization Code Grant Flow)向授权服务器请求访问令牌。

配置资源服务器

资源服务器需要验证客户端发送的访问令牌,并根据令牌授权用户访问资源。在资源服务器上,需要配置OAuth2.0的资源端点(Resource Endpoint),并使用OAuth2.0的访问令牌来验证用户身份。

实现单点登录

实现单点登录需要在授权服务器和客户端之间共享会话信息,以便用户在访问不同的客户端时无需重新登录。可以使用基于Cookie或Token的方式来共享会话信息。具体实现方式可以参考OAuth2.0的单点登录规范(OAuth 2.0 Single Sign-On)。

拓展

cookie常见属性

Cookie是一种用于在Web浏览器和Web服务器之间传递状态信息的机制,它提供了一些属性,用于控制Cookie的使用范围和行为。以下是常用的Cookie属性:

  1. Name和Value属性

Name和Value属性是Cookie的基本属性,用于指定Cookie的名称和值。例如,一个名为username,值为john的Cookie可以表示为:username=john

  1. Domain属性

Domain属性用于指定Cookie的域名。如果不指定该属性,则默认为当前网页的域名。例如,如果当前网页的域名为example.com,则设置了Domain属性为.example.com的Cookie可以在example.com的的所有子域名(例如www.example.comblog.example.com等)下使用。

  • Name和Value属性

Name和Value属性是Cookie的基本属性,用于指定Cookie的名称和值。例如,一个名为username,值为john的Cookie可以表示为:username=john

  • Domain属性

Domain属性用于指定Cookie的域名。如果不指定该属性,则默认为当前网页的域名。例如,如果当前网页的域名为example.com,则设置了Domain属性为.example.com的Cookie可以在example.com的的所有子域名(例如www.example.comblog.example.com等)下使用。

  • Path属性

Path属性用于指定Cookie的路径。如果不指定该属性,则默认为当前网页的路径。例如,如果当前网页的路径为/path,则设置了Path属性为/path/subpath的Cookie只能在以/path/subpath开头的路径下使用。

  • Expires属性

Expires属性用于指定Cookie的过期时间。它的值是一个GMT时间字符串,表示Cookie的有效期限。过期时间到了之后,浏览器将不再发送该Cookie。

  • Max-Age属性

Max-Age属性用于指定Cookie的最大生存时间,以秒为单位。它的值是一个整数,表示Cookie从创建到过期的时间间隔。过期时间到了之后,浏览器将不再发送该Cookie。

  • Secure属性

Secure属性用于指定Cookie只能在HTTPS协议下使用,如果尝试在HTTP协议下使用,则该Cookie将被忽略。Secure属性可以增强Cookie的安全性,从而保护用户的隐私和数据安全。

  • HttpOnly属性

HttpOnly属性用于指定Cookie只能在HTTP协议下使用,不能被JavaScript等客户端脚本访问。这可以防止跨站脚本攻击(XSS攻击),从而保护用户的隐私和安全。

cookie域名与路径限制

是的,Cookie有一些域名限制。Cookie是一种用于在Web浏览器和Web服务器之间传递状态信息的机制,它可以在客户端(即Web浏览器)中存储一些信息,并在每次请求中将这些信息发送给服务器。而在使用Cookie时,有以下两种域名限制:

  • Cookie的域名必须与当前Web页面的域名一致或是其父域名。

例如,如果当前Web页面的域名为example.com,那么在向服务器发送请求时,只能发送属于example.com或其父域名(例如.example.com)的Cookie。如果发送了不属于该域名或其父域名的Cookie,服务器将不会接受该Cookie。

  • Cookie的路径必须与当前Web页面的路径一致或是其父路径。

每个Cookie都有一个路径属性,用于指定该Cookie在哪些路径下有效。例如,如果某个Cookie的路径属性为/path1,那么该Cookie只在以/path1开头的路径下有效。如果当前Web页面的路径为/path1/path2,那么只有以/path1开头的Cookie才会发送给服务器,其他Cookie将被忽略。

需要注意的是,虽然Cookie有域名和路径限制,但这并不意味着Cookie只能在单个域名或路径下使用。实际上,通过设置Cookie的域名和路径属性,可以在多个域名和路径下使用同一个Cookie,从而实现跨域名和跨路径的共享。