单点登录解决方案

共享 Session

@startuml

actor User order 1
participant Browser order 2
participant "SSO Server" order 3
participant App1 order 4
participant App2 order 5
database Redis order 6

== 第一次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: GET https://app1.example.com/
activate App1

return 302 Location: https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F

activate Browser
Browser -> "SSO Server": GET https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "SSO Server"

return SSO 登录表单

Browser -> User: 展示 SSO 登录表单
activate User

return: 提交 SSO 登录表单

Browser -> "SSO Server": POST https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "SSO Server"

"SSO Server" -> "SSO Server": 认证用户

"SSO Server" -> Redis: set sso:ABC1234567 {"username": "Tom"}
activate Redis

return ok

return Set-Cookie: JSESSIONID=ABC1234567; Domain=example.com\n302 Location: https://app1.example.com/
deactivate Browser

Browser -> App1: Cookie: JSESSIONID=ABC1234567\nGET https://app1.example.com/
activate App1

App1 -> Redis: get sso:ABC1234567
activate Redis

return {"username": "Tom"}

return 200 [https://app1.example.com/]

return 展示 App1

== 第二次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: Cookie: JSESSIONID=ABC1234567\nGET https://app1.example.com/resource/
activate App1

App1 -> Redis: get sso:ABC1234567
activate Redis

return {"username": "Tom"}

return 200 [https://app1.example.com/resource/]

return 展示 App1

== 第一次访问 App2 ==

User -> Browser: 访问 App2
activate Browser

Browser -> App2: Cookie: JSESSIONID=ABC1234567\nGET https://app2.example.com/
activate App2

App2 -> Redis: get sso:ABC1234567
activate Redis

return {"username": "Tom"}

return 200 [https://app2.example.com/]

return 展示 App2

@enduml

优点:

  1. 实现单点登出比较简单

缺点:

  1. 不支持跨顶级域名登录

  2. 严重依赖外部系统(Redis)

JSON Web Token (JWT)

@startuml

actor User order 1
participant Browser order 2
participant "SSO Server" order 3
participant App1 order 4
participant App2 order 5

== 第一次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: GET https://app1.example.com/
activate App1

return 302 Location: https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F

activate Browser
Browser -> "SSO Server": GET https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "SSO Server"

return SSO 登录表单

Browser -> User: 展示 SSO 登录表单
activate User

return: 提交 SSO 登录表单

Browser -> "SSO Server": POST https://sso.example.com/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "SSO Server"

"SSO Server" -> "SSO Server": 认证用户

"SSO Server" -> "SSO Server": 生成 JWT\nheader + payload + secret -> abc.def.ghi

return Set-Cookie: abc.def.ghi; Domain=example.com\n302 Location: https://app1.example.com/
deactivate Browser

Browser -> App1: Authorization: Bearer abc.def.ghi\nGET https://app1.example.com/
activate App1

App1 -> App1: 解析 JWT\nabc.def.ghi -> header + payload + secret

return 200 [https://app1.example.com/]

return 展示 App1

== 第二次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: Authorization: Bearer abc.def.ghi\nGET https://app1.example.com/resource/
activate App1

App1 -> App1: 解析 JWT\nabc.def.ghi -> header + payload + secret

return 200 [https://app1.example.com/resource/]

return 展示 App1

== 第一次访问 App2 ==

User -> Browser: 访问 App2
activate Browser

Browser -> App2: Authorization: Bearer abc.def.ghi\nGET https://app2.example.com/
activate App2

App2 -> App2: 解析 JWT\nabc.def.ghi -> header + payload + secret

return 200 [https://app2.example.com/]

return 展示 App2

@enduml

优点:

  1. 服务端无状态

缺点:

  1. 不支持跨顶级域名登录

  2. 实现单点登出(失效 Token)比较复杂

  3. 续签 Token 比较复杂

  4. 需要额外的计算成本

Central Authentication Service (CAS)

@startuml

actor User order 1
participant Browser order 2
participant "CAS Server" order 3
participant App1 order 4
participant App2 order 5

== 第一次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: GET https://app1.example.com/
activate App1

return 302 Location: https://cas.example.com/cas/login?\nservice=https%3A%2F%2Fapp1.example.com%2F

activate Browser
Browser -> "CAS Server": GET https://cas.example.com/cas/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "CAS Server"

return CAS 登录表单

Browser -> User: 展示 CAS 登录表单
activate User

return: 提交 CAS 登录表单

Browser -> "CAS Server": POST https://cas.example.com/cas/login?\nservice=https%3A%2F%2Fapp1.example.com%2F
activate "CAS Server"

"CAS Server" -> "CAS Server": 认证用户

return Set-Cookie: CASTGC=TGT-2345678\n302 Location: https://app1.example.com/?\nticket=ST-12345678
deactivate Browser

Browser -> App1: GET https://app1.example.com/?\nticket=ST-12345678
activate App1

App1 -> "CAS Server": GET https://cas.example.com/serviceValidate?\nservice=https%3A%2F%2Fapp1.example.com%2F%3F\nticket%3DST-12345678
activate "CAS Server"

return 200 [XML]

return Set-Cookie: JSESSIONID=ABC1234567\n 302 Location: https://app1.example.com/

Browser -> App1: Cookie: JSESSIONID=ABC1234567\nGET https://app1.example.com/
activate App1

App1 -> App1: 校验 session 和 cookie

return 200 [https://app1.example.com/]

return 展示 App1

== 第二次访问 App1 ==

User -> Browser: 访问 App1
activate Browser

Browser -> App1: Cookie: JSESSIONID=ABC1234567\nGET https://app1.example.com/resource/
activate App1

App1 -> App1: 校验 session 和 cookie

return 200 [https://app1.example.com/resource/]

return 展示 App1

== 第一次访问 App2 ==

User -> Browser: 访问 App2
activate Browser

Browser -> App2: GET https://app2.example.com/
activate App2

return 302 Location: https://cas.example.com/cas/login?\nservice=https%3A%2F%2Fapp2.example.com%2F

activate Browser
Browser -> "CAS Server": Cookie: CASTGC=TGT-2345678\nGet https://cas.example.com/cas/login?\nservice=https%3A%2F%2Fapp2.example.com%2F
activate "CAS Server"

"CAS Server" -> "CAS Server": 校验 TGT

return 302 Location: https://app2.example.com/?\nticket=ST-345678
deactivate Browser

Browser -> App2: GET https://app2.example.com/?ticket=ST-345678
activate App2

App2 -> "CAS Server": GET https://cas.example.com/serviceValidate?\nservice=https%3A%2F%2Fapp2.example.com%2F%3F\nticket%3DST-345678
activate "CAS Server"

return 200 [XML]

return Set-Cookie: JSESSIONID=DEF1234567\n 302 Location: https://app2.example.com/

Browser -> App2: Cookie JSESSIONID=DEF1234567\n https://app2.example.com/
activate App2

App2 -> App2: 校验 session 和 cookie

return 200 [https://app2.example.com/]

return 展示 App2

@enduml

优点:

  1. 支持跨顶级域名登录

缺点

  1. 实现单点登出比较复杂

最后更新于