前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >认证授权的设计与实现

认证授权的设计与实现

原创
作者头像
ruochen
修改2021-11-25 12:56:05
1.1K0
修改2021-11-25 12:56:05
举报
文章被收录于专栏:若尘的技术专栏

一、前言

每个网站,小到一个H5页面,必有一个登录认证授权模块,常见的认证授权方式有哪些呢?又该如何实现呢?下面我们将来讲解SSO、OAuth等相关知识,并在实践中的应用姿势。

二、认证 (authentication) 和授权 (authorization)

这两个术语通常在安全性方面相互结合使用,尤其是在获得对系统的访问权限时。两者都是非常重要的主题,通常与网络相关联,作为其服务基础架构的关键部分。然而,这两个术语在完全不同的概念上是非常不同的。虽然它们通常使用相同的工具在相同的上下文中使用,但它们彼此完全不同。

身份验证意味着确认您自己的身份,而授权意味着授予对系统的访问权限。简单来说,身份验证是验证您的身份的过程,而授权是验证您有权访问的过程。

authentication 证明你是你,authorization 证明你有这个权限。身份验证是授权的第一步,因此始终是第一步。授权在成功验证后完成。

例子:你要登陆论坛,输入用户名张三,密码1234,密码正确,证明你张三确实是张三,这就是

authentication;再一check用户张三是个版主,所以有权限加精删别人帖,这就是 authorization。

三、单点登录(SSO)

单点登录(Single Sign On),简称为

SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

举例来说,QQ音乐和腾讯新闻是腾讯公司旗下的两个不同的应用系统,如果用户在腾讯新闻登录过之后,当他访问QQ音乐时无需再次登录,那么就说明QQ音乐和腾讯新闻之间实现了单点登录。

3.1 父域Cookie

最简单是实现方式是,将 Cookie 的 domain 属性设置为当前域的父域,那么就认为它是父域 Cookie。Cookie 有一个特点,即父域中的

Cookie 被子域所共享,换言之,子域会自动继承父域中的 Cookie。

  • 系统1:a.zxy.com
  • 系统2:b.zxy.com
  • 登录系统:login.zxy.com
代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
系统1->>系统1:已登录状态,登录cookie在zxy.com域
代码语言:txt
复制
系统2->>系统2:需要登录
代码语言:txt
复制
系统2->>登录系统:登录(携带登录cookie信息)
代码语言:txt
复制
登录系统->>登录系统:登录验证
代码语言:txt
复制
登录系统-->>系统2:登录成功
代码语言:txt
复制
系统2->>系统2:访问资源

CAS流程

3.2 CAS

还有一种方式,那就是CAS(Central Authentication Service)(中心认证服务)

。可参考OAuth2.0,应用系统检查当前请求有没有

Ticket,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心。由于这个操作会将认证中心的 Cookie

自动带过去,因此,认证中心能够根据 Cookie

知道用户是否已经登录过了。如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录,如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标

URL ,并在跳转前生成一个 Ticket,拼接在目标 URL 的后面,回传给目标应用系统。

CAS 流程图

四、OAuth

4.1 四种方式

OAuth 2.0定义了四种授权方式。

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

4.1.1 授权码模式

授权码模式(authorization

code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与“服务提供商”的认证服务器进行互动。

代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
Resource Owner->>Client: 1. 用户访问客户端
代码语言:txt
复制
Client->>User Agent: 2. 客户端将用户导向认证服务器
代码语言:txt
复制
User Agent->>Authorization Server: 3. response_type=code&client_id={客户端的ID}&redirect_uri={重定向URI}&scope={权限范围}&state={state}
代码语言:txt
复制
User Agent->>Resource Owner: 4. 用户选择是否给予客户端授权
代码语言:txt
复制
User Agent->>Authorization Server: 5. 用户给予授权
代码语言:txt
复制
Authorization Server-->>User Agent: 6. 重定向URL?code={code}&state={state}
代码语言:txt
复制
User Agent-->>Client: 7. 重定向URL?code={code}&state={state}
代码语言:txt
复制
Client->>Authorization Server: 8. grant_type=authorization_code&client_id={client_id}&code={code}&state={state}&redirect_uri={redirect_uri}
代码语言:txt
复制
Authorization Server-->>Client: 9. expires_in access_token refresh_token scope

OAuth2.0-授权码模式

  1. response_type=code&client_id={客户端的ID}&redirect_uri={重定向URI}&scope={权限范围}&state={state}
  2. grant_type=authorization_code&client_id={client_id}&code={code}&state={state}&redirect_uri={redirect_uri}

4.1.2 简化模式

简化模式(implicit grant

type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
Resource Owner->>Client: 1. 用户访问客户端
代码语言:txt
复制
Client->>User Agent: 2. 客户端将用户导向认证服务器
代码语言:txt
复制
User Agent->>Authorization Server: 3. authorize?response_type=token&client_id={客户端的ID}&redirect_uri={重定向URI}&scope={权限范围}&state={state}
代码语言:txt
复制
User Agent->>Resource Owner: 4. 用户选择是否给予客户端授权
代码语言:txt
复制
User Agent->>Authorization Server: 5. 用户给予授权
代码语言:txt
复制
Authorization Server-->>User Agent: 6. expires_in access_token refresh_token scope state,并在URI的Hash部分包含了访问令牌
代码语言:txt
复制
User Agent->>WebHosted Client Resource: 7. 浏览器向资源服务器发出请求
代码语言:txt
复制
WebHosted Client Resource-->>User Agent: 8. 返回可以从Hash值中获取令牌的代码脚本
代码语言:txt
复制
User Agent->>User Agent: 9. 根据脚本提取令牌
代码语言:txt
复制
User Agent->>Client: 10. access_token

OAuth2.0-简化模式

  1. authorize?response_type=token&client_id={客户端的ID}&redirect_uri={重定向URI}&scope={权限范围}&state={state}
  2. expires_in access_token refresh_token scope state,并在URI的Hash部分包含了访问令牌

4.1.3 密码模式

密码模式(Resource Owner Password Credentials

Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。

代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
Resource Owner->>Client: 1. 用户名和密码
代码语言:txt
复制
Client->>Authorization Server: 2. grant_type=password&username={username}&password={password}&scope={权限范围}
代码语言:txt
复制
Authorization Server-->>Client: 3. expires_in access_token refresh_token

OAuth2.0-密码模式

  1. grant_type=password&username={username}&password={password}&scope={权限范围}

4.1.4 客户端模式

客户端模式(Client Credentials

Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
Client->>Authorization Server: 1. grant_type=client_credentials&scope={权限范围}
代码语言:txt
复制
Authorization Server-->>Client: 2. expires_in access_token refresh_token

OAuth2.0-客户端模式

4.2 更新令牌

如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。

代码语言:txt
复制
sequenceDiagram
代码语言:txt
复制
Client->>Authorization Server: 1. grant_type=refresh_token&refresh_token={refresh_token}
代码语言:txt
复制
Authorization Server-->>Client: 2. expires_in access_token refresh_token

OAuth2.0-更新令牌

4.3 微信小程序登录的例子

小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。

使用的是OAuth2.0中的授权码模式。调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。调用

auth.code2Session 接口,换取 用户唯一标识 OpenID 、

用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥

session_key。之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

微信小程序登录

五、JWT

JSON Web Token (JWT)是一个开放标准(RFC

7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT的最常见场景,一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

JWT由三部分组成,它们之间用圆点“.”连接。这三部分分别是:Header、Payload、Signature。因此,一个典型的JWT看起来是这个样子的:“xxx.yyy.zzz”

JWT的第一部分Header典型的由两部分组成:类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

代码语言:txt
复制
{
代码语言:txt
复制
  "alg": "HS256",
代码语言:txt
复制
  "typ": "JWT"
代码语言:txt
复制
}

JWT的第二部分Payload,也就是我们数据的存放地方,特别注意不要在里面存放敏感信息。它包含声明,声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:

registered, public 和 private。

代码语言:txt
复制
{
代码语言:txt
复制
  "sub": "1234567890",
代码语言:txt
复制
  "name": "John Doe",
代码语言:txt
复制
  "admin": true
代码语言:txt
复制
}

JWT的第三部分Signature,为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

代码语言:txt
复制
HMACSHA256(
代码语言:txt
复制
  base64UrlEncode(header) + "." +
代码语言:txt
复制
  base64UrlEncode(payload),
代码语言:txt
复制
  secret)

Java实现

代码语言:txt
复制
<dependency>
代码语言:txt
复制
    <groupId>io.jsonwebtoken</groupId>
代码语言:txt
复制
    <artifactId>jjwt</artifactId>
代码语言:txt
复制
    <version>0.9.1</version>
代码语言:txt
复制
</dependency>
代码语言:txt
复制
import com.fasterxml.jackson.core.JsonProcessingException;
代码语言:txt
复制
import com.fasterxml.jackson.databind.ObjectMapper;
代码语言:txt
复制
import io.jsonwebtoken.Claims;
代码语言:txt
复制
import io.jsonwebtoken.JwtBuilder;
代码语言:txt
复制
import io.jsonwebtoken.Jwts;
代码语言:txt
复制
import io.jsonwebtoken.SignatureAlgorithm;
代码语言:txt
复制
import org.slf4j.Logger;
代码语言:txt
复制
import org.slf4j.LoggerFactory;
代码语言:txt
复制
import javax.crypto.spec.SecretKeySpec;
代码语言:txt
复制
import javax.xml.bind.DatatypeConverter;
代码语言:txt
复制
import java.security.Key;
代码语言:txt
复制
import java.util.Date;
代码语言:txt
复制
public class Test {
代码语言:txt
复制
    private static final Logger logger = LoggerFactory.getLogger(Test.class);
代码语言:txt
复制
    private static String secret = "zhongxy@123456";
代码语言:txt
复制
    private static ObjectMapper objectMapper = new ObjectMapper();
代码语言:txt
复制
    public static void main(String[] args) throws Exception {
代码语言:txt
复制
        UserInfo userInfo = new UserInfo(); // 自定义的登录对象
代码语言:txt
复制
        userInfo.setId(6);
代码语言:txt
复制
        userInfo.setName("测试");
代码语言:txt
复制
        logger.info("UserInfo:" + objectMapper.writeValueAsString(userInfo));
代码语言:txt
复制
        String token = generateToken(userInfo, 60 * 1000);
代码语言:txt
复制
        logger.info("token:" + token);
代码语言:txt
复制
        Object result = check(token);
代码语言:txt
复制
        logger.info("check:" + objectMapper.writeValueAsString(result));
代码语言:txt
复制
    }
代码语言:txt
复制
    // 生成token
代码语言:txt
复制
    public static String generateToken(UserInfo userInfo, long ttlSecs) {
代码语言:txt
复制
        //The JWT signature algorithm we will be using to sign the token
代码语言:txt
复制
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
代码语言:txt
复制
        long nowMillis = System.currentTimeMillis();
代码语言:txt
复制
        Date now = new Date(nowMillis);
代码语言:txt
复制
        //We will sign our JWT with our ApiKey secret
代码语言:txt
复制
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secret);
代码语言:txt
复制
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
代码语言:txt
复制
        //Let's set the JWT Claims
代码语言:txt
复制
        JwtBuilder builder = null;
代码语言:txt
复制
        try {
代码语言:txt
复制
            builder = Jwts.builder()
代码语言:txt
复制
                    .setIssuedAt(now)
代码语言:txt
复制
                    .setIssuer(objectMapper.writeValueAsString(userInfo))
代码语言:txt
复制
                    .signWith(signatureAlgorithm, signingKey);
代码语言:txt
复制
        } catch (JsonProcessingException e) {
代码语言:txt
复制
            e.printStackTrace();
代码语言:txt
复制
            return null;
代码语言:txt
复制
        }
代码语言:txt
复制
        //if it has been specified, let's add the expiration
代码语言:txt
复制
        if (ttlSecs >= 0) {
代码语言:txt
复制
            long expMillis = nowMillis + ttlSecs * 1000;
代码语言:txt
复制
            Date exp = new Date(expMillis);
代码语言:txt
复制
            builder.setExpiration(exp);
代码语言:txt
复制
        }
代码语言:txt
复制
        //Builds the JWT and serializes it to a compact, URL-safe string
代码语言:txt
复制
        return builder.compact();
代码语言:txt
复制
    }
代码语言:txt
复制
    // 从token中反向解析出UserInfo
代码语言:txt
复制
    public static UserInfo check(String token) {
代码语言:txt
复制
        try {
代码语言:txt
复制
            //This line will throw an exception if it is not a signed JWS (as expected)
代码语言:txt
复制
            Claims claims = Jwts.parser()
代码语言:txt
复制
                    .setSigningKey(DatatypeConverter.parseBase64Binary(secret))
代码语言:txt
复制
                    .parseClaimsJws(token).getBody();
代码语言:txt
复制
            String userInfoStr = claims.getIssuer();
代码语言:txt
复制
            return objectMapper.readValue(userInfoStr, UserInfo.class);
代码语言:txt
复制
        } catch (Exception e) {
代码语言:txt
复制
            e.printStackTrace();
代码语言:txt
复制
        }
代码语言:txt
复制
        return null;
代码语言:txt
复制
    }
代码语言:txt
复制
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、认证 (authentication) 和授权 (authorization)
  • 三、单点登录(SSO)
    • 3.1 父域Cookie
      • 3.2 CAS
      • 四、OAuth
        • 4.1 四种方式
          • 4.1.1 授权码模式
          • 4.1.2 简化模式
          • 4.1.3 密码模式
          • 4.1.4 客户端模式
        • 4.2 更新令牌
          • 4.3 微信小程序登录的例子
          • 五、JWT
          相关产品与服务
          访问管理
          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档