xxl-sso 单点登录

单点登录原理及简单实现:https://www.cnblogs.com/ywlaker/p/6113927.html

xxl-sso是一款基于redis轻量级分布式高可用的SSO实现组件,支持web端(Cookie实现)和app端(Token实现)两种方式,两种方式的验证都是用Filter实现的

项目地址 gitee :https://gitee.com/xuxueli0323/xxl-sso

xxl-sso/ xxl-sso-samples 里面是基于 cookie 和 token 的调用示例,单点登陆Client端接入示例项目

1 项目启动

修改Host文件

修改Host文件:域名方式访问认证中心,模拟跨域与线上真实环境

在host文件中添加以下内容

127.0.0.1 xxlssoserver.com
127.0.0.1 xxlssoclient1.com
127.0.0.1 xxlssoclient2.com

运行路径

分别运行 “xxl-sso-server” 与 “xxl-sso-web-sample-springboot”

1、SSO认证中心地址:
http://xxlssoserver.com:8080/xxl-sso-server
2、Client01应用地址:
http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
3、Client02应用地址:
http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/

SSO登录/注销流程验证

正常情况下,登录流程如下:

  1. 访问 “Client01应用地址” ,将会自动 redirect 到 “SSO认证中心地址” 登录界面
  2. 成功登录后,将会自动 redirect 返回到 “Client01应用地址”,并切换为已登录状态
  3. 此时,访问 “Client02应用地址”,不需登陆将会自动切换为已登录状态

正常情况下,注销流程如下:

  1. 访问 “Client01应用地址” 配置的 “注销登陆path”,将会自动 redirect 到 “SSO认证中心地址” 并自动注销登陆状态
  2. 此时,访问 “Client02应用地址”,也将会自动注销登陆状态

2 分析

登录流程

① 首次访问 client1 的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/ ,进入过滤器

① 在client项目中配置了core的过滤器XxlSsoWebFilter(判断cookie或者请求携带参数中的id是否与redis中一致),首次访问client1时,过滤器判断cookie、请求携带参数、redis均为null,请求重定向到认证中心server携带参数(login?redirect_url=)

在client项目中配置了core的过滤器XxlSsoWebFilter
使用FilterRegistrationBean 详解
DisposableBean 与InitializingBean 详解

@Configuration
public class XxlSsoConfig implements DisposableBean {
   @Value("${xxl.sso.server}")
   private String xxlSsoServer;
   @Value("${xxl.sso.logout.path}")
   private String xxlSsoLogoutPath;
   @Value("${xxl-sso.excluded.paths}")
   private String xxlSsoExcludedPaths;
   @Value("${xxl.sso.redis.address}")
   private String xxlSsoRedisAddress;

   @Bean
   public FilterRegistrationBean xxlSsoFilterRegistration() {
       JedisUtil.init(xxlSsoRedisAddress);
       FilterRegistrationBean registration = new FilterRegistrationBean();

       registration.setName("XxlSsoWebFilter");
       registration.setOrder(1);
       registration.addUrlPatterns("/*");
       registration.setFilter(new XxlSsoWebFilter());   //配置了 core 里的过滤器
       registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);
       registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);
       registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);

       return registration;
   }
   @Override
   public void destroy() throws Exception {
       JedisUtil.close();    // xxl-sso, redis close
   }
}

core 里的过滤器如下(判断cookie或者请求携带参数中的id是否与redis中一致)

发现 redis 无数据,请求重定向到 server 的登录页面 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/

在这里插入图片描述
此处是配置文件中配置的参数

### xxl-sso
## SSO Server认证中心地址(推荐以域名方式配置认证中心,本机可参考章节"2.5"修改host文件配置域名指向)
xxl.sso.server=http://xxlssoserver.com:8080/xxl-sso-server
## 注销登陆path,值为Client端应用的相对路径
xxl.sso.logout.path=/logout
## 路径排除Path,允许设置多个,且支持Ant表达式。用于排除SSO客户端不需要过滤的路径
xxl-sso.excluded.paths=
xxl.sso.redis.address=redis://127.0.0.1:6379
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 跳过
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
        // remove cookie
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);
        // redirect logout
        //注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    // valid login user, cookie + redirect 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    
    // valid login fail 首次登录
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            // json msg
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
        
            // total link 首次登录
            String link = req.getRequestURL().toString();
            // redirect logout
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            //首次登录 loginPageUrl = http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    // ser sso user
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}

此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过

public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // cookie user 此处是通过cookieId获取 redis 中的user
    //首次访问client、server,cookieSessionId为null  xxlUser为null
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    //版本不一致,移除session,设置新的
    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    // 此处是通过请求参数的 Id 获取 redis 中的user
    //首次访问client、server,paramSessionId cookieSessionId xxlUser 都为 null
    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    
    if (xxlUser != null) {
   		// set new cookie 
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}
② 请求重定向至 server的 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/

② 进入server的WebController的login方法,发现cookie、请求携带参数、redis均为null 。跳转到登录页,输入账号密码,提交server的dologin登录方法,将user存放cookie 存放redis,请求重定向回到client1携带参数(?xxl_sso_sessionid=)

查询之前未登录,就进入登陆页面,输入登陆账号密码

校验账号密码 —— 根据user对象生成 xxl_sso_sessionid 存放 redis、cookie
—— 根据请求路径携带的参数 ?redirect_url= ,将请求重定向回去 client1,并携带 xxl_sso_sessionid 作为请求参数

@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {

    // 查验 cookie、请求参数 是否携带 id (id用于查询redis是否有user)
    //xxlUser = null ———— 跳过
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);

    if (xxlUser != null) {
        String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
        if (redirectUrl!=null && redirectUrl.trim().length()>0) {
            String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
            String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;
            return "redirect:" + redirectUrlFinal;
        } else {
            return "redirect:/";
        }
    }

    model.addAttribute("errorMsg", request.getParameter("errorMsg"));
    model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
    return "login";
}

跳转到登录也买你,数据账号密码后进入 doLogin 方法

@RequestMapping("/doLogin")  //首次提交登录 /xxl-sso-server/doLogin
public String doLogin(HttpServletRequest request,
                    HttpServletResponse response,
                    RedirectAttributes redirectAttributes,
                    String username,
                    String password,
                    String ifRemember) {
    //首次提交登录ifRemember=null  ifRem =false
    boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;

    // 校验账号密码是否正确
    ReturnT<UserInfo> result = userService.findUser(username, password);
    if (result.getCode() != ReturnT.SUCCESS_CODE) {
        redirectAttributes.addAttribute("errorMsg", result.getMsg());

        redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
        return "redirect:/login";
    }

    // 1、创建 XxlSsoUser 对象
    XxlSsoUser xxlUser = new XxlSsoUser();
    xxlUser.setUserid(String.valueOf(result.getData().getUserid()));
    xxlUser.setUsername(result.getData().getUsername());
    xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", ""));
    xxlUser.setExpireMinute(SsoLoginStore.getRedisExpireMinute());
    xxlUser.setExpireFreshTime(System.currentTimeMillis());


    // 2、make session id
    String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);

    // 3、存放session,存放redis
    SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);

    // 4、首次提交登录,转回client1. redirectUrlFinal = http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
    String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
    if (redirectUrl!=null && redirectUrl.trim().length()>0) {
        String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;
        return "redirect:" + redirectUrlFinal;
    } else {
        return "redirect:/";
    }

}
③ 请求重定向至 client1的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19

进入过滤器

③ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 跳过
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
        // remove cookie
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);
        // redirect logout
        //注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    // valid login user, cookie + redirect 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断
    // 此时cookieId为null,根据 请求参数id 查询redis得到user,再将 user 存入cookie
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    
    // 此处user有数据 ———— 跳过
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            // json msg
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
            // total link 首次登录
            String link = req.getRequestURL().toString();
            // redirect logout
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    // ser sso user
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // cookie user 此处是通过cookieId获取 redis 中的user
    //二次认证中心登录后跳回访问client1,所以cookieSessionId为null  xxlUser为null
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    // remove old cookie 版本不一致,移除session,设置新的
    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    // 此处是通过请求参数的 Id 获取 redis 中的user
    //二次认证中心登录后跳回访问client1,携带参数 ?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    
    //从redis获取user与sessionid进行匹配。匹配成功则设置 cookie
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    if (xxlUser != null) {
    	// set new cookie
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}

登录成功,进入 client1 首页

@RequestMapping("/")
public String index(Model model, HttpServletRequest request) {
    XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);
    model.addAttribute("xxlUser", xxlUser);
    return "index";   //登录成功页面
}
① 再访问 client2 的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/

④ 此时再访问client2,来到过滤器,过滤器判断cookie、请求携带参数、redis均为null,请求重定向到认证中心server携带参数(login?redirect_url=)

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 跳过
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
        // remove cookie
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);
        // redirect logout
        //注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    // 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    
    // xxlUser = null 首次登录
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            // json msg
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
        
            // total link 首次登录
            String link = req.getRequestURL().toString();
            // redirect logout
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            //首次登录 loginPageUrl = http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    // ser sso user
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}

此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断,此时两种key都是null ———— 跳过

public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // cookie user 此处是通过cookieId获取 redis 中的user
    //首次访问client、server,cookieSessionId为null  xxlUser为null
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    //版本不一致,移除session,设置新的
    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    // 此处是通过请求参数的 Id 获取 redis 中的user
    //首次访问client、server,paramSessionId cookieSessionId xxlUser 都为 null
    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    
    if (xxlUser != null) {
   		// set new cookie 
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}
② 请求重定向至 server的 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/

⑤ 进入server的WebController的login方法,发现cookie中有user且与redis一致。请求重定向到client2携带参数(?xxl_sso_sessionid=)

@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {

    // 因为server已有cookieId,在redis中查询得到 user
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);

    if (xxlUser != null) {

        String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
        if (redirectUrl!=null && redirectUrl.trim().length()>0) {

			//携带参数重定向回到client2. http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_bfada28b41f5492ea442ce730282043f
            String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
            String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;

            return "redirect:" + redirectUrlFinal;
        } else {
            return "redirect:/";
        }
    }
    model.addAttribute("errorMsg", request.getParameter("errorMsg"));
    model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
    return "login";
}

此时server已有cookieId,在redis中查询得到 user

public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // 此处是通过cookieId获取 redis 中的user
    //此时是client2重定向进 server,此时的server有cookie
    //根据 cookieId 查询 redis 得到 user 信息,直接返回
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    if (xxlUser != null) {
    	// set new cookie
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}
③ 请求重定向至 client2的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_bfada28b41f5492ea442ce730282043f

⑥ 再次来到过滤器,此时cookie为null,判断请求携带参数与redis一致,将user存入cookie,放行到IndexController登录成功页面

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 跳过
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    // 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断
    // 此时cookieId为null,根据 请求参数id 查询redis得到user,再将 user 存入cookie
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    
    // 此处user有数据 ———— 跳过
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
            String link = req.getRequestURL().toString();
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    // ser sso user
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // cookie user 此处是通过cookieId获取 redis 中的user
    //二次认证中心登录后跳回访问client1,所以cookieSessionId为null  xxlUser为null
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    // remove old cookie 版本不一致,移除session,设置新的
    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    // 此处是通过请求参数的 Id 获取 redis 中的user
    //二次认证中心登录后跳回访问client1,携带参数 ?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    
    //从redis获取user与sessionid进行匹配。匹配成功则设置 cookie
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    if (xxlUser != null) {
    	// set new cookie
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}

登录成功,进入 client2 首页

@RequestMapping("/")
public String index(Model model, HttpServletRequest request) {
    XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);
    model.addAttribute("xxlUser", xxlUser);
    return "index";   //登录成功页面
}

注销流程

① 访问 client1 的 http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/logout,进入过滤器
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/logout" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 进入
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
            
        // remove cookie 移除 client1 的cookie
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);

        //注销时,请求重定向 http://xxlssoserver.com:8080/xxl-sso-server/logout
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
            String link = req.getRequestURL().toString();
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}
② 请求重定向至 server的 http://xxlssoserver.com:8080/xxl-sso-server/logout
@RequestMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {

    // logout
    SsoWebLoginHelper.logout(request, response);

    redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
    return "redirect:/login";
}

服务端注销逻辑

public static void logout(HttpServletRequest request,
                          HttpServletResponse response) {
	//查询是否有 cookie
    String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID);
    if (cookieSessionId==null) {
        return;
    }

	// 移除 redis
    String storeKey = SsoSessionIdHelper.parseStoreKey(cookieSessionId);
    if (storeKey != null) {
        SsoLoginStore.remove(storeKey);
    }

	//移除 cookie
    CookieUtil.remove(request, response, Conf.SSO_SESSIONID);
}

跳转到 /login

@RequestMapping(Conf.SSO_LOGIN)
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {

    // 检查 cookie 和 请求参数 是否携带 id(id用于查询redis中是否有user)
    //结果 xxlUser = null ———— 跳过
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
    if (xxlUser != null) {
        String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
        if (redirectUrl!=null && redirectUrl.trim().length()>0) {
            String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
            String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;
            return "redirect:" + redirectUrlFinal;
        } else {
            return "redirect:/";
        }
    }

    model.addAttribute("errorMsg", request.getParameter("errorMsg"));
    model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
    return "login";
}

跳转到登陆页面

在这里插入图片描述

此时页面地址是 http://xxlssoserver.com:8080/xxl-sso-server/login

@RequestMapping("/doLogin")  //首次提交登录 /xxl-sso-server/doLogin
public String doLogin(HttpServletRequest request,
                    HttpServletResponse response,
                    RedirectAttributes redirectAttributes,
                    String username,
                    String password,
                    String ifRemember) {

    boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;

    // valid login
    ReturnT<UserInfo> result = userService.findUser(username, password);
    if (result.getCode() != ReturnT.SUCCESS_CODE) {
        redirectAttributes.addAttribute("errorMsg", result.getMsg());

        redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
        return "redirect:/login";
    }

    // 1、make xxl-sso user
    XxlSsoUser xxlUser = new XxlSsoUser();
    xxlUser.setUserid(String.valueOf(result.getData().getUserid()));
    xxlUser.setUsername(result.getData().getUsername());
    xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", ""));
    xxlUser.setExpireMinute(SsoLoginStore.getRedisExpireMinute());
    xxlUser.setExpireFreshTime(System.currentTimeMillis());


    // 2、make session id
    String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);

    // 3、login, store storeKey + cookie sessionId
    //    存放session,存放redis
    SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);

    // 4、return, redirect sessionId
    //    首次提交登录,转回client1. redirectUrlFinal = http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_48c9730ac0164d6b881c568a2b275b19
    String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
    if (redirectUrl!=null && redirectUrl.trim().length()>0) {
        String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;
        return "redirect:" + redirectUrlFinal;
    } else {
        return "redirect:/";
    }

}
@RequestMapping("/")
public String index(Model model, HttpServletRequest request, HttpServletResponse response) {

    // login check
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);

    if (xxlUser == null) {
        return "redirect:/login";
    } else {
        model.addAttribute("xxlUser", xxlUser);
        return "index";
    }
}

进入首页
在这里插入图片描述

① 刷新请求 client2的 http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/?xxl_sso_sessionid=1000_62816da65ce24d069776a7d25c510c4b

进入过滤器

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 当前访问的路径为 servletPath="/" 
    String servletPath = req.getServletPath();

    // excluded path check ———— excludedPaths="" 跳过
    if (excludedPaths!=null && excludedPaths.trim().length()>0) {
        for (String excludedPath:excludedPaths.split(",")) {
            String uriPattern = excludedPath.trim();
            // 支持ANT表达式
            if (antPathMatcher.match(uriPattern, servletPath)) {
                // excluded path, allow
                chain.doFilter(request, response);
                return;
            }
        }
    }

    // logout path check ———— logoutPath="/logout" 跳过
    if (logoutPath!=null
            && logoutPath.trim().length()>0
            && logoutPath.equals(servletPath)) {
        SsoWebLoginHelper.removeSessionIdByCookie(req, res);
        String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
        res.sendRedirect(logoutPageUrl);
        return;
    }

    // 此处是判断redis中是否有user,分别以cookieId和请求参数id作为key判断
    // 由于 redis 已被删除,所以 xxlUser = null
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
    
    if (xxlUser == null) {
        String header = req.getHeader("content-type");
        boolean isJson=  header!=null && header.contains("json");
        if (isJson) {
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
            return;
        } else {
            String link = req.getRequestURL().toString();
            String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                    + "?" + Conf.REDIRECT_URL + "=" + link;
            
            //请求重定向至 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
            res.sendRedirect(loginPageUrl);
            return;
        }
    }
    // ser sso user
    request.setAttribute(Conf.SSO_USER, xxlUser);
    chain.doFilter(request, response);
    return;
}
public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
    String cookieSessionId = CookieUtil.getValue(request, "xxl_sso_sessionid");

    // cookie user 此处是通过cookieId获取 redis 中的user
    //此处有 cookie ,但是 redis 已被删除,所以 xxlUser = null
    XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
    if (xxlUser != null) {
        return xxlUser;
    }

    SsoWebLoginHelper.removeSessionIdByCookie(request, response);

    // 此处是通过请求参数的 Id 获取 redis 中的user
    //此处有 请求参数 ,但是 redis 已被删除,所以 xxlUser = null ———— 跳过
    String paramSessionId = request.getParameter("xxl_sso_sessionid");
    xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
    if (xxlUser != null) {
    	// set new cookie
        CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
        return xxlUser;
    }
    return null;
}
② 请求重定向至 server的 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/

跳转到 /login

@RequestMapping("/login")
public String login(Model model, HttpServletRequest request, HttpServletResponse response) {

    // 检查 cookie 和 请求参数 是否携带 id(id用于查询redis中是否有user)
    //结果 xxlUser = null ———— 跳过
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);
    if (xxlUser != null) {
        String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
        if (redirectUrl!=null && redirectUrl.trim().length()>0) {
            String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
            String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;
            return "redirect:" + redirectUrlFinal;
        } else {
            return "redirect:/";
        }
    }

    model.addAttribute("errorMsg", request.getParameter("errorMsg"));
    model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
    return "login";
}

跳转到登陆页面

在这里插入图片描述

此时页面地址是 http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值