单点登录
单点登录
单点登录时不能将 token 拼接在 URL 上传递,因为 URL 参数可能被浏览器历史、插件、Referer、恶意 JS 或抓包工具获取,存在较高泄漏风险。
正确方式是:认证中心在登录成功后,仅通过重定向返回一个一次性授权码(code),该 code 短期有效(通常几十秒)且只能使用一次;业务系统通过后端调用 /token 接口,用 code 换取 token。
携带参数
业务系统跳转到单点登录页面时要携带如下参数:
https://sso.com/authorize?
response_type=code
&client_id=system-a
&redirect_uri=https%3A%2F%2Fsystem-a.com%2Fcallback
&scope=openid%20profile%20email
&state=abc123
&code_challenge=xyz
&code_challenge_method=S256
&nonce=n-0S6_WzA2Mj
response_type: 认证中心服务端通过此参数来判断此次登录是普通登录,还是需要重定向的单点登录;
client_id:用于标识发起认证请求的客户端(业务系统),认证中心根据该参数识别调用方,并加载对应的配置(如 redirect_uri 白名单、scope 权限范围等)。
redirect_uri: 登录后要跳转的业务系统地址;这是高危参数,必须在认证中心提前注册,且不允许通配符、不允许动态拼接,否则会导致 code 被发给攻击者;
scope:向认证中心声明“需要哪些权限 / 信息”,例如:openid(用户ID)、profile(用户基本信息)、email(用户邮箱)等;
state: 业务系统在跳转到登录页面前创建一个 state 保存到 sessionStorage,登录成功后必须验证此 state; 用于在登录发起阶段与回调阶段建立一一对应关系,从而防止第三方伪造登录结果(CSRF 攻击);例如:攻击者使用自己的合法 code 伪造一个的回调地址来访问业务系统,导致业务系统使用攻击者伪造的 code 去获取 token,从而登录到攻击者的账号中。
code_challenge:用于防止授权码(code)被截获后被滥用,使授权码只能被原始客户端使用。客户端在发起登录前生成一个高随机的 code_verifier,并对其进行 SHA-256 哈希计算得到 code_challenge(通过 code_challenge_method=S256 声明算法);随后在 /authorize 登录请求中只提交 code_challenge,认证中心在签发授权码(code)时会将该 code 与 code_challenge 进行绑定;登录成功后客户端在调用 /token 接口获取 token 时提交原始的 code_verifier,认证中心对其进行同样的哈希计算,并与绑定的 code_challenge 比对,只有一致时才会签发 token。由于 SHA-256 是不可逆的,攻击者即使获取了 code 和 code_challenge,也无法推导出 code_verifier,因此无法利用截获的授权码换取 token。
code_challenge_method:用于声明 code_challenge 的生成方式(哈希算法),通常为 S256(表示使用 SHA-256);该值是协议中定义的固定枚举值,而不是可自定义字符串。
nonce: 客户端在发起登录时生成一个一次性的高随机的 nonce,并存入到 sessionStorage 中,认证中心把它原样写进 id_token,客户端在收到 id_token 后校验两者一致,从而确保这个 token 就是本次登录产生的,防止旧 token 被重放。