如何实现系统的单点登录?

1. 单点登录的流程是什么样的?

总体来说,一次单点登录过程包括了 3 次重定向过程。

第一次重定向,发现 Session 里未携带用户信息,拦截该未登录请求,重定向到 CAS Server;
第二次重定向,是登录成功重定向到初始访问接口(用户被拦截的接口),这步的重定向很关键,解决了单点登录的跨域问题;
第三次重定向,Session 携带用户信息去访问初始接口;

整个流程图如下:
在这里插入图片描述
思考以下几个问题以加深理解:

  1. 用户信息是哪个角色赋值在 Session 中的?

    CAS Server 将用户信息封装在 body 里,服务方读取该 body 数据并填充到 Session 中;

  2. 服务方是怎么验证 Ticket 的?

第一次登录后,后面登录就不需要验证了,流程图如下。
在这里插入图片描述
访问其他系统也不需要认证了,流程图如下。
在这里插入图片描述

2. 从代码层面剖析单点登录流程

  1. 准备工作:注册拦截资源

    @Component
    public class AuthConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            AiforceProperties aiforceProperties = SpringContextHolder.getApplicationContext().getBean(AiforceProperties.class);
            registry.addInterceptor(new AuthHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(getNoAuthUrls());// 拦截白名单
        }
    }
    
  2. 从前端请求后端服务

    首先从前端调用后端的http://app.example.com/auth/user接口,这时会被拦截器拦截,下面是拦截器的实现。

    @Component
    @Slf4j
    public class AuthHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (isLogin(request)) {
                return true;
            }
            // 未登录则需要跳转到CAS Server进行登录
            request.getSession().setAttribute(Constants.RETURN_URL, "http://app.example.com/auth/user");
            response.sendRedirect("http://cas.example.com/cas/login?service=http://app.example.com/callback");// 【1】
            return false;
        }
    
        public boolean isLogin(HttpServletRequest request) {
            // 会话中用户信息为空则未登录
            if (request.getSession().getAttribute(Constants.USER_SESSION) == null) {
                return false;
            }
            return true;
        }
    }
    
  3. 后端转发请求到单点登录服务器

    注意:这里的请求转发response.sendRedirect跟上面的重定向可以理解为一个意思。

    在请求单点登录服务器前,需要把注册一个回调地址http://app.example.com/callback,比如步骤1中代码【1】处。

    用户提交表单后,单点登录服务器会跳转至http://cas.example.com/cas/login?service=http://app.example.com/callbackservice 配置的值即http://app.example.com/callback,并携带ticket数据,可以放在http body里面。

  4. /callback接口怎么实现?

    @RequestMapping(value = "/callback")
    public Result<String> callback(HttpServletRequest request, HttpServletResponse response) {
        // 请求单点登录服务器,校验ticket,返回用户信息
        UserInfo userInfo = iAuthService.validateTicket(request);
        // 将用户信息放到session里
        request.getSession().setAttribute(Constants.USER_SESSION, JSON.toJSONString(userInfo));
        // 转发请求至步骤1初始请求的地址
        response.sendRedirect("http://app.example.com/auth/user");
        return Result.ok();
    }
    
  5. 重新请求请求后端服务

    这时再请求http://app.example.com/auth/user接口,步骤1中的isLogin()就返回true了。这样就完成了登录。

3. 跨域问题怎么解决?

比如现在有 2 个请求。

请求1:http://app1.example.com/auth/user
请求2:http://app2.example.com/auth/user

在这里插入图片描述

图片来源:https://blog.csdn.net/hezheqiang/article/details/82145125

可以通过在 SSO 域名下保存 Cookie 来解决跨域问题。

4. 如何实现会话超时?

/callback接口里添加逻辑,将用户信息缓存到redis中。

@RequestMapping(value = "/callback")
public Result<String> callback(HttpServletRequest request, HttpServletResponse response) {
    UserInfo userInfo = iAuthService.validateTicket(request);
    request.getSession().setAttribute(Constants.USER_SESSION, JSON.toJSONString(userInfo));
    // 新增的逻辑
    stringRedisTemplate.opsForValue().set(getTokenKey(), JSON.toJSONString(userInfo), 120, TimeUnit.SECONDS);
    response.sendRedirect("http://app.example.com/auth/user");
    return Result.ok();
}

接着在isLogin()方法里添加判断:redis中的TokenKey是否过期。

public boolean isLogin(HttpServletRequest request) {
    if (request.getSession().getAttribute(Constants.USER_SESSION) == null) {
        return false;
    }
    // 判断redis缓存中key是否失效
    if (stringRedisTemplate.opsForValue().get(getTokenKey()) == null) {
        return false;
    }
    return true;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值