SSO单点登录设计

1. 无状态服务与有状态服务

无状态:每次请求毫无关联。
有状态:比如cookie、session,可作为类似http等无状态服务的传输介质。

2. 单点登录时序图

在这里插入图片描述
分三个部分:
用户、系统、认证中心。
以上时序图解释:

  1. 用户访问系统1,未登录,转发请求到认证中心验证;
  2. 认证中心验证该用户未登录,引导用户访问未登录页面;
  3. 用户输入账号密码,携带地址1地址参数发给认证中心;
  4. 认证中心验证通过后创建与用户的全局会话,并创建授权令牌发送给系统1;
  5. 系统1返回授权令牌给认证中心作二次验证;
  6. 认证中心验证通过,注册系统1,返回有效;
  7. 系统1创建与用户的局部会话;
  8. 用户访问系统2,未登录,转发请求到认证中心;
  9. 认证中心通过全局会话发现用户已登录,返回授权令牌给系统2;
  10. 系统2携带自己的地址参数并返回授权令牌;
  11. 认证中心校验授权令牌通过,注册系统2,返回有效;
  12. 系统2创建与用户的局部会话。

3. 单点注销

在这里插入图片描述

  1. 用户向系统1发起注销请求,系统1通过会话ID取出令牌,发给认证中心;
  2. 认证中心校验令牌通过,销毁与拥护的全局会话,并向使用该授权令牌注册的系统发送注销请求;
  3. 系统收到注销请求,销毁与用户的局部会话。
    会话间的区别
  4. 全局会话存在,局部会话不一定存在;
  5. 局部会话存在,全局会话一定存在;
  6. 局部不存在,全局会话不一定存在;
  7. 全局会话不存在,局部会话一定不存在。

4. 系统设计

系统1、系统2、认证中心。
系统:

  1. 拦截登录请求,重定向到认证中心验证;
  2. 创建与用户的局部会话。
    认证中心:
  3. 负责登录操作以及授权码的校验
  4. map存储系统地址,模仿系统的注册,方便统一发送注销请求;
  5. 创建与用户的全局会话;
  6. 统一注销。

核心请求拦截器的实现:

package com.example.sso.client.interceptor;

import com.example.sso.client.utils.CookieUtil;
import com.example.sso.client.utils.SSOClientHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 创建拦截器-拦截需要安全访问的请求
 * 方法说明
 * 1.preHandle():前置处理回调方法,返回true继续执行,返回false中断流程,不会继续调用其它拦截器
 * 2.postHandle():后置处理回调方法,但在渲染视图之前
 * 3.afterCompletion():全部后置处理之后,整个请求处理完毕后回调。
 *
 * @author yhw
 */
@Slf4j
public class WebInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        log.info("[ WebInterceptor ] >> preHandle  requestUrl:{} ", request.getRequestURI());
        //判断是否有局部会话
        HttpSession session = request.getSession();
        Object isLogin = session.getAttribute("isLogin");
        if (isLogin != null && (Boolean) isLogin) {
            log.debug("[ WebInterceptor ] >> 已登录,有局部会话 requestUrl:{}", request.getRequestURI());
            return true;
        }
        //获取令牌ssoToken
        String token = SSOClientHelper.getSsoToken(request);

        //无令牌
        if (StringUtils.isEmpty(token)) {
            //认证中心验证是否已经登录(是否存在全局会话)
            SSOClientHelper.checkLogin(request, response);
            return true;
        }

        //有令牌-则请求认证中心校验令牌是否有效
        Boolean checkToken = SSOClientHelper.checkToken(token, session.getId());

        //令牌无效
        if (!checkToken) {
            log.debug("[ WebInterceptor ] >> 令牌无效,将跳转认证中心进行认证 requestUrl:{}, token:{}", request.getRequestURI(), token);
            //认证中心验证是否已经登录(是否存在全局会话)
            SSOClientHelper.checkLogin(request, response);
            return true;
        }

        //token有效,创建局部会话设置登录状态,并放行
        session.setAttribute("isLogin", true);
        //设置session失效时间-单位秒
        session.setMaxInactiveInterval(1800);
        //设置本域cookie
        CookieUtil.setCookie(response, SSOClientHelper.SSOProperty.TOKEN_NAME, token, 1800);
        log.debug("[ WebInterceptor ] >> 令牌有效,创建局部会话成功 requestUrl:{}, token:{}", request.getRequestURI(), token);
        return true;
    }

}

校验令牌接口

 /**
     * 校验令牌是否合法
     *
     * @param ssoToken    令牌
     * @param loginOutUrl 退出登录访问地址
     * @param jsessionid
     * @return 令牌是否有效
     */
    @ResponseBody
    @RequestMapping("/checkToken")
    public String verify(String ssoToken, String loginOutUrl, String jsessionid) {
        // 判断token是否存在map容器中,如果存在则代表合法
        boolean isVerify = SSOConstantPool.TOKEN_POOL.contains(ssoToken);
        if (!isVerify) {
            log.info("[ SSO-令牌校验 ] checkToken 令牌已失效 ssoToken:{}", ssoToken);
            return "false";
        }

        //把客户端的登出地址记录起来,后面注销的时候需要根据使用(生产环境建议存库或者redis)
        List<ClientRegisterModel> clientInfoList =
                SSOConstantPool.CLIENT_REGISTER_POOL.computeIfAbsent(ssoToken, k -> new ArrayList<>());
        ClientRegisterModel vo = new ClientRegisterModel();
        vo.setLoginOutUrl(loginOutUrl);
        vo.setJsessionid(jsessionid);
        clientInfoList.add(vo);
        log.info("[ SSO-令牌校验 ] checkToken success ssoToken:{} , clientInfoList:{}", ssoToken, clientInfoList);
        return "true";
    }

5. 效果截图

单点登录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
单点注销:
在这里插入图片描述

6.单点登录实现的三种方式

session广播(session复制)

适合在服务模块较少的情况。如果在服务过多的情况下,显得过于冗余。

cookie+redis实现

  1. 在项目任何一个模块进行登录,登录之后,把数据放到两个地方
    (1)redis:在key:生成随机唯一值(ip、用户id等),在value:存用户数据
    (2)cookie:把redis里面生成的key值放到cookie。

  2. 访问其他模块时,请求带着cookie进行发送
    各个模块从cookie中获取key,到redis中查找值,查到证明已登录

jwt验证机制(使用token实现)

在某个模块登录后,会把用户信息以某种加密形式放到字符串中,并返回。
(1)放到cookie中
(2)拼接到地址栏后面

 
每次发送请求都带上这个字符串,其他模块从这个字符串中提取到用户数据,注意不能存储敏感信息,比如用户密码。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
一、需求背景 随着企业内部系统的增多,用户需要频繁登录各个系统,造成了用户体验不佳和安全性隐患。为了提高用户体验和系统安全性,需要引入单点登录SSO)解决方案,实现用户一次登录即可访问多个系统。 二、需求概述 单点登录SSO)是一种身份验证和授权机制,允许用户使用一组凭据(例如用户名和密码)进行一次登录,然后在不同的应用程序和系统中无需重复登录即可访问资源。主要功能包括: 1. 用户认证:提供统一的身份认证机制,用户只需进行一次登录认证即可访问多个系统。 2. 会话管理:管理用户的登录会话状态,确保用户在一段时间内可以无需重新认证访问多个系统。 3. 用户授权:实现对用户的角色和权限进行统一管理和控制,确保用户在不同系统中的访问权限一致。 4. 单点注销:提供单点注销功能,用户退出一个系统后可以同时退出其他已登录的系统。 5. 安全性保护:采用安全的身份验证机制,如加密、令牌、证书等,保障用户身份和数据的安全性。 三、详细需求描述 1. 用户认证 1.1. 支持多种身份认证方式,如用户名密码、LDAP、OAuth等。 1.2. 支持多因素认证,如短信验证码、指纹识别、硬件令牌等。 1.3. 支持单点登录与外部身份提供商(IdP)集成,如ADFS、Okta、Ping Identity等。 1.4. 支持自定义认证流程,满足不同系统的特定需求。 2. 会话管理 2.1. 管理用户的登录会话状态,确保用户在一段时间内可以无需重新认证访问多个系统。 2.2. 实现会话过期和续签机制,保障会话的安全性和可用性。 2.3. 支持会话监控和管理,包括会话超时、并发登录限制等。 3. 用户授权 3.1. 统一管理和控制用户的角色和权限信息。 3.2. 支持基于角色和权限的访问控制,确保用户在不同系统中的访问权限一致。 3.3. 提供角色和权限的动态分配和调整机制,方便系统管理员进行管理。 4. 单点注销 4.1. 提供单点注销功能,用户退出一个系统后可以同时退出其他已登录的系统。 4.2. 确保注销操作的安全性,防止未经授权的用户访问已注销的系统。 5. 安全性保护 5.1. 采用安全的身份验证机制,如加密、令牌、证书等,保障用户身份和数据的安全性。 5.2. 对敏感信息进行加密传输,防止信息泄露。 5.3. 提供安全审计和日志记录功能,追踪用户的登录和操作行为。 四、技术实现方案 1. 选择适合的单点登录协议,如SAML、OAuth、OpenID Connect等。 2. 采用身份提供商(IdP)和服务提供商(SP)的架构,将认证和授权分离。 3. 使用安全框架和加密算法,确保用户凭证和数据的安全性。 4. 使用会话管理机制,如Cookie、Token、Session等,实现会话的跟踪和管理。 五、需求交付物 1. 需求说明书; 2. 系统设计文档; 3. 系统源代码; 4. 用户手册; 5. 系统部署说明。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超级无敌暴龙战士塔塔开

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

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

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

打赏作者

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

抵扣说明:

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

余额充值