单点登录(原理与代码)

一、单点登录简介

什么是单点登录:⼀处登录,处处登录,⼀处登出,处处登出。
⽤户只需要登录⼀次就可以访问所有相互信任的应⽤系统。
SSO
Single Sign On 单点登录——企业业务整合解决⽅案。
生活实例举例:⼀票通,景点联票。

SSO 原理(单点登录的过程)

(结合示例说明)

  • 当⽤户第⼀次访问淘宝的时候,因为还没有登录,会被引导到认证中⼼进⾏登录。
  • 根据⽤户提供的登录信息,认证系统进⾏身份验证,如果通过,则登录成功,并返回给⽤户⼀个认证的凭据(token)。
  • 当⽤户访问天猫时,就会将这个 token 带上,作为⾃⼰认证的凭据。
  • 应⽤系统接收到请求后会把 token 送到认证中⼼进⾏校验,检查 token 的合法性。
  • 如果通过校验,⽤户就可以在不⽤再次登录的情况下访问天猫了。

补充:token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 token 给前端。前端可以在每次请求的时候带上 token 证明自己的合法地位。如果这个 token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。
在这里插入图片描述

下图是CAS官网上的标准流程,具体流程如下
在这里插入图片描述

  • 1、用户访问app系统,app系统是需要登录的,但用户现在没有登录。
  • 2、跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
  • 3、用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
  • 4、SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
  • 5、app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
  • 6、验证通过后,app系统将登录状态写入session并设置app域下的Cookie。

至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。

  • 1、用户访问app2系统,app2系统没有登录,跳转到SSO。
  • 2、由于SSO已经登录了,不需要重新登录认证。
  • 3、SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
  • 4、app2拿到ST,后台访问SSO,验证ST是否有效。
  • 5、验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。

这样,app2系统不需要走登录流程,就已经是登录了。SSO,app和app2在不同的域,它们之间的session不共享也是没问题的。

SSO 实现技术

  • 1.Cookie 单点登录

使⽤ Cookie 作为媒介,存放⽤户凭证。
⽤户登录淘宝之后,返回⼀个 token,存⼊客户端的 Cookie 中,当⽤户访问天猫的时候,会⾃动带上 Cookie,这样 token ⼜传给了认证中⼼,进⾏校验。

  • 2.分布式 Session
    在这里插入图片描述

1、⽤户第⼀次登录时,将会话信息,写⼊分布式 Session。
2、⽤户再次登录时,获取分布式 Session,判断是否有登录信息,如果没有则返回登录⻚⾯。
3、Session ⼀般存储到 Redis 中,因为它有持久化功能,如果分布式 Session 宕机后,重启之后可以从持久化存储中重新加载会话信息。

SSO 常见方案

  • 1.OAuth2(第三方登录授权)

第三⽅系统访问主系统资源,⽤户⽆需将主系统的账户告知第三⽅,只需要通过主系统的授权,第三⽅就可以使⽤主系统的资源。

  • 2.jwt

Json Web Token,是为了在⽹络应⽤之间传递信息⽽执⾏的⼀种,基于 JSON 的开放标准,难度较⾼,需要了解很多协议,偏向底层的东⻄,需要你基于 JWT 认证协议,⾃⼰开放 SSO 服务和权限控制。

  • 3.CAS(不是并发的 CAS)

中央认证服务,Central Authentication ServiceCAS 是耶鲁⼤学发起的⼀个开源项⽬,为 Web 应⽤系统提供单点登录的解决⽅案,实现多个系统只需要登录⼀次,⽆需重复登录。

二、手写单点登录系统架构(代码篇)

Cookie 单点登录的实现(更注重底层)

  • 开始写代码:
  • CAS Server
  • CAS Client
    Server 和 Client 分别独⽴部署,Server 主要负责认证工作,Client 负责处理对客户端资源的访问请求,需要登录时,重定向到Server 进⾏认证。
    1、授权服务器保存⼀个全局 session,多个客户端各⾃保存⾃⼰的session。
    2、客户端登录时判断⾃⼰的 session 是否已经登录,如果没有登录,则重定向到服务器进⾏授权(带上⾃⼰的地址,⽤于回调)。
    3、授权服务器判断全局的 session 是否已经登录,如果未登录则重定向到登录⻚⾯,提供⽤户登录,登录成功之后,授权服务器重定向到客户端,带上 token。
    4、客户端收到 token 后,请求服务器获取⽤户信息。
    5、服务器同意客户端授权后,服务器保存⽤户信息到全局session,客户端将⽤户信息保存⾄本地 session。
    用到的技术框架:Spring Boot + layui + Thymelaf
    在这里插入图片描述
    在父工程下创建三个子模块(maven模块):
  • taobao sso_client_taobao
  • tmall sso_client_tmall
  • server sso_server

本代码git地址(下载即可使用):https://github.com/liuwen766/SSO.git
代码结构如下:
在这里插入图片描述
操作说明(ReadMe):
登录测试:
step1:
访问淘宝页面,输入:
http://localhost:8081/taobao
会验证token,若token为空,返回登录页面
username:admin password:123123
登录成功后,
step2:
访问天猫页面,输入:
http://localhost:8082/tmall
验证token,若token正确,直接登录天猫页面(不用再输入用户名,密码)
登出测试:
step1:在两个页面已登录状态,任意退出登录一个页面;
step2:刷新另一个页面,页面自动登出。

三、单点登录详介(原理篇)

(这里代码仅仅用于示例,与上面的git仓库里的代码略有不同)
什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。

1.登录

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明:
在这里插入图片描述

下面对上图简要描述

  1. 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  2. sso认证中心发现用户未登录,将用户引导至登录页面
  3. 用户输入用户名密码提交登录申请
  4. sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
  5. sso认证中心带着令牌跳转会最初的请求地址(系统1)
  6. 系统1拿到令牌,去sso认证中心校验令牌是否有效
  7. sso认证中心校验令牌,返回有效,注册系统1
  8. 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
  9. 用户访问系统2的受保护资源
  10. 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  11. sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
  12. 系统2拿到令牌,去sso认证中心校验令牌是否有效
  13. sso认证中心校验令牌,返回有效,注册系统2
  14. 系统2使用该令牌创建与用户的局部会话,返回受保护资源

  用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系

  1. 局部会话存在,全局会话一定存在
  2. 全局会话存在,局部会话不一定存在
  3. 全局会话销毁,局部会话必须销毁

2.注销

单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明
在这里插入图片描述
sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作,下面对上图简要说明

  1. 用户向系统1发起注销请求
  2. 系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
  3. sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
  4. sso认证中心向所有注册系统发起注销请求
  5. 各注册系统接收sso认证中心的注销请求,销毁局部会话
  6. sso认证中心引导用户至登录页面

单点登陆的具体实现:

  这里只是简要介绍下基于java的实现过程,不提供完整源码,用于明白原理。sso采用客户端/服务端架构,我们先看sso-client与sso-server要实现的功能(下面:sso认证中心=sso-server)
  sso-client
1 . 拦截子系统未登录用户请求,跳转至sso认证中心
2 . 接收并存储sso认证中心发送的令牌
3 . 与sso-server通信,校验令牌的有效性
4 . 建立局部会话
5 . 拦截用户注销请求,向sso认证中心发送注销请求
6 . 接收sso认证中心发出的注销请求,销毁局部会话
  sso-server
1 . 验证用户的登录信息
2 . 创建全局会话
3 . 创建授权令牌
4 . 与sso-client通信发送令牌
5 . 校验sso-client令牌有效性
6 . 系统注册
7 . 接收sso-client注销请求,注销所有会话

接下来,按照原理来一步步实现sso!

1.sso-client拦截未登录请求

java拦截请求的方式有servlet、filter、listener三种方式,我们采用filter。在sso-client中新建LoginFilter.java类并实现Filter接口,在doFilter()方法中加入对未登录用户的拦截

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    HttpSession session = req.getSession();
     
    if (session.getAttribute("isLogin")) {
        chain.doFilter(request, response);
        return;
    }
    //跳转至sso认证中心
    res.sendRedirect("sso-server-url-with-system-url");
}

2.sso-server拦截未登录请求

拦截从sso-client跳转至sso认证中心的未登录请求,跳转至登录页面,这个过程与sso-client完全一样

3.sso-server验证用户登录信息

用户在登录页面输入用户名密码,请求登录,sso认证中心校验用户信息,校验成功,将会话状态标记为“已登录”

@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req) {
    this.checkLoginInfo(username, password);
    req.getSession().setAttribute("isLogin", true);
    return "success";
}

4.sso-server创建授权令牌

授权令牌是一串随机字符,以什么样的方式生成都没有关系,只要不重复、不易伪造即可,下面是一个例子

String token = UUID.randomUUID().toString();

5.sso-client取得令牌并校验

sso认证中心登录后,跳转回子系统并附上令牌,子系统(sso-client)取得令牌,然后去sso认证中心校验,在LoginFilter.java的doFilter()中添加几行

// 请求附带token参数
String token = req.getParameter("token");
if (token != null) {
    // 去sso认证中心校验token
    boolean verifyResult = this.verify("sso-server-verify-url", token);
    if (!verifyResult) {
        res.sendRedirect("sso-server-url");
        return;
    }
    chain.doFilter(request, response);
}

verify()方法使用httpClient实现,这里仅简略介绍,httpClient详细使用方法请参考官方文档

HttpPost httpPost = new HttpPost("sso-server-verify-url-with-token");
HttpResponse httpResponse = httpClient.execute(httpPost);

6.sso-server接收并处理校验令牌请求

用户在sso认证中心登录成功后,sso-server创建授权令牌并存储该令牌,所以,sso-server对令牌的校验就是去查找这个令牌是否存在以及是否过期,令牌校验成功后sso-server将发送校验请求的系统注册到sso认证中心(就是存储起来的意思)
  令牌与注册系统地址通常存储在key-value数据库(如redis)中,redis可以为key设置有效时间也就是令牌的有效期。redis运行在内存中,速度非常快,正好sso-server不需要持久化任何数据。
  令牌与注册系统地址可以用下图描述的结构存储在redis中,可能你会问,为什么要存储这些系统的地址?如果不存储,注销的时候就麻烦了,用户向sso认证中心提交注销请求,sso认证中心注销全局会话,但不知道哪些系统用此全局会话建立了自己的局部会话,也不知道要向哪些子系统发送注销请求注销局部会话
在这里插入图片描述

7.sso-client校验令牌成功创建局部会话

令牌校验成功后,sso-client将当前局部会话标记为“已登录”,修改LoginFilter.java,添加几行

if (verifyResult) {
    session.setAttribute("isLogin", true);
}

  sso-client还需将当前会话id与令牌绑定,表示这个会话的登录状态与令牌相关,此关系可以用java的hashmap保存,保存的数据用来处理sso认证中心发来的注销请求

8.注销过程

用户向子系统发送带有“logout”参数的请求(注销请求),sso-client拦截器拦截该请求,向sso认证中心发起注销请求

String logout = req.getParameter("logout");
if (logout != null) {
    this.ssoServer.logout(token);
}

sso认证中心也用同样的方式识别出sso-client的请求是注销请求(带有“logout”参数),sso认证中心注销全局会话

@RequestMapping("/logout")
public String logout(HttpServletRequest req) {
    HttpSession session = req.getSession();
    if (session != null) {
        session.invalidate();//触发LogoutListener
    }
    return "redirect:/";
}

sso认证中心有一个全局会话的监听器,一旦全局会话注销,将通知所有注册系统注销

public class LogoutListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent event) {}
    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        //通过httpClient向所有注册系统发送注销请求
    }
}
  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的程序猿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值