`
gogo1217
  • 浏览: 150226 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

CAS 系列 之 AJAX 请求

阅读更多

版权所有,转载请注明来源http://gogo1217.iteye.com,违者必究!

 

最早接触 CAS 大概是 2011 年,现在发展到 5.x 了。相比较那时候的版本。比较有意思的特性有:
服务端:
1. 使用 spring-boot 开发
2. 推荐使用 overlay 的方式对服务端进行修改,这个大大的赞。虽然我接触 CAS 的第二年就是这么干的 ...

客户端 3.5.0 版本:
1、默认强制获取 session。
2、增加了 authenticationRedirectStrategyClass ,使得能自定义没有登录时选择重定向还是返回地址交给浏览器去决定。

CAS 单点登录的原理不再赘述,这里重点阐述下 AJAX 请求 CAS Client 资源发生重定向的解决方案。


问题描述
假定,我们存在以下服务:
1、CAS 服务端 Server ;
2、CAS 客户端 Client-A、Client-B,集成了 CAS 客户端;

假定 Client-A 有个页面 Page-A,使用 AJAX 调用了 Client-B 的接口 API-B。当用户访问  Page-A 时,分为以下 3 种情况:

一):没有登录过单点服务
1、API-B 发现没有登录,通知浏览器重定向到 Server
3、由于 AJAX 不能重定向,因此请求无法继续

二):Server端已登陆,但 Client-B 还未登录
1、API-B 发现没有登录,通知浏览器重定向到 Server
3、由于 AJAX 不能重定向,因此请求无法继续

三):Client-B 登录过单点服务
 1、API-B 发现已经登录,返回正确的内容


解决思路
想要解决这个问题,必须拦截所有的 AJAX 请求,根据返回判断,手动触发 CAS 的重定向。


1、首先对 Client-B (CAS 的客户端)进行改造。


如果是 AJAX 请求,则返回一个重定向的目标地址给客户端,由客户端完成URL的重定向,这里直接使用 client 3.5.0 版本,已经具备了这种接口。

 

<init-param>
    <param-name>authenticationRedirectStrategyClass</param-name>
    <param-value>org.jasig.cas.client.authentication.FacesCompatibleAuthenticationRedirectStrategy</param-value>
</init-param>

 

 

 这是它默认的实现,使用的是特殊参数,我们一般改为通过HTTP请求头判定比较合适。

 

 

public final class FacesCompatibleAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
    private static final String FACES_PARTIAL_AJAX_PARAMETER = "javax.faces.partial.ajax";

    public FacesCompatibleAuthenticationRedirectStrategy() {
    }

    public void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException {

        String xRequestedWith = request.getHeader("X-Requested-With");

        if ((CommonUtils.isNotBlank(xRequestedWith) && "XMLHttpRequest".equals(xRequestedWith.trim()))
                || CommonUtils.isNotBlank(request.getParameter(FACES_PARTIAL_AJAX_PARAMETER))) {
            response.setContentType("application/json");
            response.setStatus(200);
            PrintWriter writer = response.getWriter();
            writer.write("{\"casError\":\"403\",\"redirect\":\"" + potentialRedirectUrl + "\"}");
        } else {
            response.sendRedirect(potentialRedirectUrl);
        }
    }
}

 

 

 

2、对 Server (CAS 的服务端)进行改造。


a. 支持 AJAX 跨域调用:


修改 application.yml ,增加以下配置,使得 CAS 服务端支持跨域调用。


注意,如果要对CAS默认的属性进行配置,建议修改 application.yml 。

 

cas:
  httpWebRequest:
    cors:  # cors 跨域
      enabled: true
      allowCredentials: true
      allowOrigins: ["*"]
      allowMethods: ["*"]
      allowHeaders: ["*"]
      maxAge: 3600

 

 

b. 当 CAS 服务端已登录,而客户端没有登录,返回 ST 内容,而不是重定向。


参考:https://apereo.github.io/cas/5.0.x/protocol/CAS-Protocol-Specification.html

 

 

Use POST responses instead of redirects:

https://cas.example.org/cas/login?method=POST&service=http%3A%2F%2Fwww.example.org%2Fservice


在请求中增加参数 method=POST ,CAS 服务端将以 Form 表单的方式返回,而不是直接服务器重定向。

 

 

实现步骤

 

有了上面的准备,我们重新梳理下 AJAX请求,需要做什么处理。

 

一):没有登录过单点服务
1、API-B 发现没有登录,返回一个重定向到 Server 的地址( AJAX 特殊处理,客户端改造)。
2、AJAX 获得这个地址后在 URL 后追加参数 method=POST,重新发起 AJAX 请求,请求 Server(服务端跨请求处理)。
3、由于 Server 没有登录,服务端返回登录页面的内容。
4、AJAX 获得返回内容,发现是登录页面,提醒用户登录。
5、用户登录后,重新发起AJAX请求。

二):Server 端已登陆,但 Client-B 还未登录
1、API-B 发现没有登录,返回一个重定向到 Server 的地址( AJAX 特殊处理,客户端改造)。
2、AJAX 获得这个地址后在 URL 后追加参数 method=POST,重新发起 AJAX 请求,请求 Server(服务端跨域请求处理)。
3、由于 Server 已经登录,服务端以表单的方式返回 ST 凭证(参数 method=POST,服务端处理)。
4、AJAX 获得ST后,在最初的请求上附加 ST 凭证,重新请求 API-B。
5、Client-B 根据 ST 去 Server 获取用户信息,完成登录,并返回正确内容。

 

在上述环节中,如果 Server 端已经登录过,我们是可以做到完全静默请求,用户无感知的。

 

通过对 AJAX 的统一拦截处理,使得开发人员在开发过程中,无感知 cas。以下是 axios 的默认实现。

(function () {
    if (!axios) {
        return;
    }
    axios.interceptors.request.use((config) => {
        if (!config.headers['X-Requested-With']) {
            config.headers['X-Requested-With'] = 'XMLHttpRequest';
        }
        return config;
    });

    var oldRequest = axios.Axios.prototype.request;

    axios.Axios.prototype.request = function request(config) {
        var self = this;
        return new Promise(function (resolve, reject) {
            oldRequest.call(self, config).then(
                function (response) {
                    if (response.data && response.data.casError == '403' && response.data.redirect) {
                        console.log('原始请求- 发现 CAS 客户端未登录');
                        let url = response.data.redirect + "&method=POST";
                        axios.get(url, {
                            withCredentials: true,
                            responseType: 'document',
                        }).then(function (ssoResponse) {
                            var form = ssoResponse.data.getElementsByTagName('form');
                            if (form) {
                                form = form[0];
                                if (form.getAttribute('id') === 'fm1') {
                                    console.log('获取ticket 失败 ,CAS 服务端未登录');
                                    ssoResponse.message = '获取ticket 失败 ,CAS 服务端未登录';
                                    reject(ssoResponse);
                                    alert('请在弹出窗口完成登录后,再进行操作');
                                    window.open(response.data.redirect.substring(0, response.data.redirect.indexOf('?')));
                                }
                                else if (form.getAttribute('name') === 'acsForm' && form.getElementsByTagName('input')) {
                                    var join = config.url.indexOf('?') > 0 ? '&' : '?';
                                    config.url = config.url + join + 'ticket=' + form.getElementsByTagName('input')[0].value;
                                    axios.request(config).then(function (withLoginResponse) {
                                        console.log('获取ticket 成功,再次请求数据');
                                        resolve(withLoginResponse);
                                    }).catch(function (err) {
                                        reject(err);
                                    });
                                }
                            }
                        })
                    } else {
                        resolve(response);
                    }
                },
                function (error) {
                    reject(error);
                }
            );
        });
    };
})();

 

 完整代码:

 https://github.com/gogo1217/sso-parent

 

0
0
分享到:
评论
1 楼 masuweng 2018-06-20  
好好好    

相关推荐

    基于springboot,cas5.3,shiro,pac4j,rest接口获取ticket不再跳转cas server登录页

    基于springboot,cas,shiro,pac4j,实现cas rest接口获取ticket,不再跳转cas server登录页

    AJAX获取服务器当前时间及时间格式输出处理

    本文整理了关于AJAX获取服务器当前时间的知识,不会的朋友可以参考下哈,希望对你有所帮助

    axios 处理 302 状态码的解决方法

    比如说浏览器打开了一个单页面(SPA)应用,过了一段时间token(或者session)过期了,这个时候页面上发起 Ajax请求之后,后端返回302状态码跳转到login页面。 我这是使用的是 Vue + axios ,发现 axios 无法拦截到 ...

    dealwithcors:CORS机制

    文档 ##简单请求Lorsque je fais une requête AJAX simple (Simple requests) depuis mon browser http:localhost:8888 unservice JSON 远程http:localhost:3000 。 Rappel:一个简单的跨站点请求是: 仅使用 GET...

    JAVA上百实例源码以及开源项目

     当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket。  QQ客户端登录界面,中部有三个JPanel,有一个叫选项卡窗口管理。还可以更新...

    spring security 参考手册中文版

    Ajax和JSON请求 145 CookieCsrfTokenRepository 146 18.5 CSRF警告 147 18.5.1超时 148 18.5.2登录 148 18.5.3注销 149 18.5.4多部分(文件上传) 149 在Spring Security之前放置MultipartFilter 150 包含CSRF令牌 ...

    jeesite后台框架

    授权模块,支持CAS单点登录,简单properties配置即可,不用再写很多的xml。 支持多数据源,简单properties配置即可实现,为了安全性吧,暂不提供界面维护数据源,不存数据库。 数据表主键优化,如分类科目表,采用有...

    JAVA上百实例源码以及开源项目源代码

     当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket。  QQ客户端登录界面,中部有三个JPanel,有一个叫选项卡窗口管理。还可以更新...

    java开源包1

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包11

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包2

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包3

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包6

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包5

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包10

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包4

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包8

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包7

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

    java开源包9

    GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的...

Global site tag (gtag.js) - Google Analytics