单点登陆SSO(一)原理、多客户端登陆


感谢coding老师的课程,让我能很好的学习,掌握单点登陆的相关知识,所有的代码都来自或者修改coding老师的视频,简单总结一下

单点登陆项目GitHub

github:https://github.com/Handoking/Single-Sign-on

单点登陆的原理

SSO示意图

单点登陆最主要保证的就是一处登陆,处处登陆,也就是我们登陆taobao后,访问账号相通的另一个应用时,只需要刷新页面就登陆了。单点登陆的设计合理性就是认证中心的存在,不可能让taobao的服务器通知其他产品矩阵中的应用。
本博文的单点登陆是一个比较传统的实现方式-CAS原理,使用全局会话,局部会话完成。多客户端实现单点登陆的步骤:

  1. 用户第一次访问taobao,认证中心发现用户未登录,先登录
  2. 登陆成功后,生成唯一token,并存入认证中心(现在很多自动完成认证,不需要存入),生成全局会话
  3. 访问tianmao时,先从全局会话中获得token,携带token访问
  4. 认证中心验证token是否存在
  5. 验证成功,重定向到tianmao首页。

原理

  1. 认证中心(授权服务器)保存一份全局的session(一般用cache中间件比如redis等实现),多个客户端保存本地局部session。
  2. 用户访问时,客户端先查看本地session是否登陆,如果没有登陆,跳转认证中心全局session是否登陆。如果已经登陆,授权访问客户端(重定向到来时的地址)。
  3. 如果全局session中也没有登陆,用户先登陆认证中心。登陆成功后,全局会话中存入登陆的票据或者令牌
    返回用户访问的应用,客户端将登陆信息放入本地session.

单点登陆的关键

  1. 应用1跳转到认证中心登陆或者验证后,需要重定向应用1.因此关键是所有访问要携带来时的地址
  2. 产生令牌,传递保存令牌,验证令牌。
客户端一拦截器

在认证中心授权前,用户不能访问客户端的资源,因此使用拦截器或者过滤器来实现。我采用拦截器。
xml拦截器配置

<!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--/**拦截所有请求-->
            <mvc:mapping path="/**"/>
            <!--bean配置的就是拦截器-->
            <bean class="com.handoking.interceptor.ClientInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

拦截器将所有访问客户端的请求都进行判断,直接放行或者拦截跳转到认证中心进行登陆或者验证。
客户端拦截器实现代码:

package com.handoking.interceptor;


import com.handoking.utils.HttpUtil;
import com.handoking.utils.SSOClientUtil;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName TmClientInterceptor
 * @Description TODO
 * @Author Handoking
 * @Date 2019/12/28 15:48
 **/
public class TmClientInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        //1.判断本地是否存在会话
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if (isLogin!=null&&isLogin){
            System.out.println(System.currentTimeMillis()+":再次登陆天猫,直接验证成功");
            return true;
        }
        //2.判断本地是否有token
        String token = request.getParameter("token");
        System.out.println("令牌:"+token);
        if (!StringUtils.isEmpty(token)){
            String httpUrl = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
            Map<String, String> params = new HashMap<>(10);
            params.put("token",token);
            params.put("clientUrl",SSOClientUtil.getClientLogOutUrl());
            params.put("jessionId",session.getId());
            String verigfyBool = HttpUtil.sendHttpRequest(httpUrl, params);
            if ("true".equals(verigfyBool)) {
                //服务器验证成功
                System.out.println(System.currentTimeMillis()+":访问天猫-服务器验证成功");
                session.setAttribute("isLogin",true);
                return true;
            }
        }
        //3.不存在会话、token,那么携带来时的url即redirectUrl,跳转到服务器判断是否有其他账号互通的客户端登陆
        System.out.println(System.currentTimeMillis()+" 本地不存在会话,也不存在token");
        SSOClientUtil.redirectToSSOURL(request,response);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

SSOClientUtil是一个工具包来完成跳转认证中心。

package com.handoking.utils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.Properties;

public class SSOClientUtil {

    public static Properties ssoProperties = new Properties();
    /**统一认证中心地址**/
    public static String SERVER_URL_PREFIX;
    /**当前客户端地址**/
    public static String CLIENT_HOST_URL;

    static {
        try {
            ssoProperties.load(Objects.requireNonNull(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties")));
        } catch (IOException e) {
            e.printStackTrace();
        }
        SERVER_URL_PREFIX = ssoProperties.getProperty("server-url-prefix");
        CLIENT_HOST_URL = ssoProperties.getProperty("client-host-url");
    }

    /**
     * 当客户端请求被拦截,跳往统一认证中心,需要带redirectUrl的参数,统一认证中心登录后回调的地址
     */
    public static String getRedirectUrl(HttpServletRequest request){
        //获取请求URL
        //getServletPath()获取的是servlet的路径,是完全匹配web.xml中配置的url-pattern
        return CLIENT_HOST_URL+request.getServletPath();
    }
    /**
     * 根据request获取跳转到统一认证中心的地址,通过Response跳转到指定的地址
     */
    public static void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) throws IOException {
        String redirectUrl = getRedirectUrl(request);
        // 拼接跳转的url http://www.sso.com:8003/checkLogin?redirectUrl=http://www.tbao.com:8002
        StringBuilder url = new StringBuilder(50)
                .append(SERVER_URL_PREFIX)
                .append("/checkLogin?redirectUrl=")
                .append(redirectUrl);
        response.sendRedirect(url.toString());
    }


    /**
     * 获取客户端的完整登出地址
     */
    public static String getClientLogOutUrl(){
        return CLIENT_HOST_URL+"/logOut";
    }
    /**
     * 获取认证中心的登出地址
     */
    public static String getServerLogOutUrl(){
        return SERVER_URL_PREFIX+"/logOut";
    }
}

sso.properties配置文件

server-url-prefix=http://www.sso.com:8003
client-host-url=http://www.tbao.com:8002
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值