Skip to content

Latest commit

 

History

History
178 lines (129 loc) · 7.29 KB

sso.md

File metadata and controls

178 lines (129 loc) · 7.29 KB

sso 简介

sso 是 lain 的单点登录系统,为 lain 的应用管理用户, 提供用户注册、身份认证等系列功能.

sso 的用户分为两个层次,需要利用 sso 登录的用户(end user),和需要 sso 管理自己的应用的用户. 后者需要了解更多关于 oauth2 和 openid connect 的知识.

sso 启动依赖及在 Lain 上的部署

sso 启动需要一个 MYSQL 数据库和一个 SMTP 服务器,由于前者涉及到密码。所以 sso 的配置推荐使用 LAIN 上的 secret files 机制。

要部署 sso,首先需要了解 sso 启动依赖的参数, 见 adminmanual.

写入 secret files 的详细方法见 LAIN 的秘密文件配置系统.

用户注册

提供用户名、邮箱、自定义的密码即可. 注册后的用户,在 sso 看来是公开的,只要知道某人的用户名, 可以调用相关 api 查询该用户相关信息。 所以可以方便地查询某个人在 sso 的哪个组中。 如果用户的用户名或邮箱和已有用户冲突,则会提示用户已存在, 此时正确的流程是利用邮箱重置密码。

应用注册

注册后的用户,可以注册自己的应用。一般来说, lain 上的应用是由 console 去自动帮用户注册的,所以下面的流程 是提供注册 oauth2 client 的方法.

  • 用浏览器打开 https://{LAIN_DOMAIN}
  • 点击我的应用管理,添加客户端.

正确添加客户端后,可以在 我的应用列表 中看到刚添加的应用. sso 自动生成了 Client ID 和 Client Secret, 今后可以用 oauth2 标准协议, 让用户利用 sso 登录自己的应用.

顺便说一下,sso 自身也是也是 sso 的应用,默认必须用 https 登录.

scope

用户登录第三方应用时,会跳到 sso,这时候会有一个授权的列表,其实是该页面 URL 中 scope 参数的值,表示授权被保护的资源. 当前已有的 scope 的列表如下:

  • openid
  • profile
  • email
  • phone
  • write:app
  • read:app
  • write:user
  • read:user
  • write:group
  • read:group
  • openid 的含义见 openid connect 协议,如果 scope 包含 openid, 则用授权码得到的结果除了 access_token 外还有 id_token.
  • 若 scope 中包含 profile, 则在 id_token 的 claims 中包含 "name" 项.
  • 若 scope 中包含 email, 则在 id_token 中包含 "email", 暂不支持 "email_verified"
  • 若 scope 中包含 phone, 则在 id_token 中包含 "phone_number", 暂不支持 "phone_number_verified"

更多信息请参考 /GET /.well-known/openid-configuration

openid-configuration

###GET /.well-known/openid-configuration

通过这个请求可以方便地得到 OpenID Connect Server 的相关信息。 然后通过标准的 openid connect 访问 sso 即可, 当前支持 Authorization Code Flow 和 Implicit Flow.

更多关于 OAUTH2 和 OpenID Connect 的知识可见 oauth2, openid connect

Demo

现在 sso 主要支持 oauth2 及 openid connect 的 code flow 和 implicit flow. 下面分别给出两个例子。

Authentication code flow

OAuth2

  • 首先注册应用
    • 可以在 sso 得网站上注册,也可以调用 api
    • POST /apps
  • 客户端
    • 首先使用申请到的 "client id", "redirect uri" 以及其他参数跳转到 SSO 登陆页面, url 构造的 python 代码如下所示:
      url = 'https://sso.xyz/oauth2/auth' + '?' + urlencode({'client_id': client_id, 'response_type': 'code', 'scope': 'profile', 'redirect_uri': redirect_uri, 'state': '*****',})
      
    • 用户在SSO登陆界面登陆后会跳转到 redirect uri 并在链接末尾附带一个 code 参数
    • 客户端需截取 code 参数后附带 "client id", "client secret", "redirect uri" 等其他参数向 sso 请求 access_token,相关 url 构造的 python 代码如下所示:
      url = 'https://sso.xyz/oauth2/token' + '?' + urlencode({'client_id': client_id, 'grant_type': 'authorization_code', 'client_secret': client_secret, 'code': code, 'redirect_uri': redirect_uri})
      
    • SSO 验证成功后会返回一个 http Response,status 为 201 则表明登录成功,其中 JSON 串中会包含 access_token

注意事项

如果应用包含前端与服务器端,则 client_secret 最好包含于服务器端, 同时,服务器端在接到附带 access-token 的 http 请求时, 应该先向 SSO 验证 access_token 的正确性(通过调用 https://sso.syz/api/me/, 在header中加上 "{'Authorization' : 'Bearer ' + access_token"}),再执行相应权限操作。

OpenID Connect

利用 OpenID Connect 协议得到的 access_token 与 id_token 相关联, 由于 id_token 经过 sso 的签名,而 id_token 中的 at_hash 将 access_token 与 id_token 相关联, 所以更容易验证其真实性; 又由于 id_token 的 claims 包含 aud, 可以很容易知道 id_token 是为哪一个应用产生的, 不易与其它第三方 client 申请的 access_token 混淆.

认证/授权请求的构造也很容易,与 oauth2 流程相比,仅仅是 scope 中包含 openid. 下面重点说一下,对于 access_token 和 id_token 的使用(验证)。

Implicit flow

这里仅给出一种基于浏览器的前端实现的 demo.

类似常见的 login with google,lain 上的 sso 也可以作为 openid connect 的身份认证服务器。

利用 implicit flow 认证

对于浏览器利用 sso 做认证的用例,最方便的方法是利用 openid connect 的 implicit flow, 使用步骤如下。

前提条件:需要在 sso 注册用户,并且有一个应用需要 sso 做认证。

  • 打开 https://sso.`{LAIN_DOMAIN`}
  • 在卡片“自助服务”下,点击“我的应用管理”,注册该应用及其回调 URL
  • 用 implicit flow 进行认证

下面,假设 LAIN_DOMAINlain.cc, 结合一个例子详细说明第三步的使用方法。只需要简单的 4 步即可完成。

  • 首先,需要引用 openid connect 的客户端 js 库
<script src="https://sso.lain.cc/assets/oidc/oidc.js"></script>
  • 其次,配置 client,在 loginwithsso 按钮的响应函数中添加类似如下的代码,设置 client info,主要包括 client_id, redirect_uri; 另外,设置 sso 的配置,并保存在浏览器的 session 中:
    var clientInfo = {
        client_id: '3',  // sso 生成的 client_id  
        redirect_uri: 'http://' // 用户自己的回调 URL
    };
    OIDC.setClientInfo(clientInfo);

    var providerInfo = OIDC.discover("https://sso.lain.cc")
    OIDC.setProviderInfo(providerInfo);
    
    // 将配置存到浏览器 session 中 
    OIDC.storeInfo(providerInfo, clientInfo);
  • 接下来,进行 login
    OIDC.login({
        scope: 'openid', // 具体需要的 scope 与应用相关
        response_type: 'token id_token',
    });
  • 最后,在用户回调的 URL 中,验证 id_token 的签名及有效性,例如按照如下的 js 进行函数调用;
    OIDC.restoreInfo();

    // Get ID Token: Returns ID Token if valid, else error. 
    var id_token = OIDC.getValidIdToken();
    alert("idtoken:" + id_token);

	// 得到解析后的 id_token
    var id_token_parts = OIDC.getIdTokenParts(id_token)
    alert(id_token_parts)

    // Get Access Token
    var token = OIDC.getAccessToken();
    alert("access_token:" + token)