前后端分离,shiro,跨域问题

跨域问题是进行ajax请求时,js文件所在域名,与请求域名不一致,触发跨域限制。

解决思路是利用filter修改response header信息。shiro本身依赖fiter实现。所以shiro时filter是shiro特定的filter。

可以在nginx容器中,配置filter。

还可以用nginx的反向代理,实现相同域名,就不会出现跨域问题。





前后端分离

    前后端分离的好处

    最大的好处就是前端JS可以做很大部分的数据处理工作,对服务器的压力减小到最小。

    后台错误不会直接反映到前台,错误接秒较为友好。

    由于后台是很难去探知前台页面的分布情况,而这又是JS的强项,而JS又是无法独立和服务器进行通讯的。所以单单用后台去控制整体页面,又或者只靠JS完成效果,都会难度加大,前后台各尽其职可以最大程度的减少开发难度。

    个人理解上存在两种解释

    第一种只是单纯的前后端分离,实在物理层面上的,将View层的任务分配给前端,Controller和Model层给后端,这就存在一个问题,就是后端的同事需要去关注前端的展示逻辑、而前端只要存在变化,后端的数据处理需要做相应的改变。

    第二种是基于职责层面上的分离,将View和Controller层分配的前端,后端只处理Model和业务处理,这就需要Controller使用Node.js,M-V-C对应的是JAVA/PHP-JAVASCRIPT、HTML、CSS-Node.js。

跨域问题存在的原因

        随着前后端分离技术的越来越盛行,跨域问题也逐渐凸显了出来。跨域问题的根本原因:因为浏览器收到同源策略的限制,当前域名的js只能读取同域下的窗口属性。什么叫做同源策略?就是不同的域名, 不同端口, 不同的协议不允许共享资源的,保障浏览器安全。同源策略是针对浏览器设置的门槛。如果绕过浏览就能实现跨域,所以说早期的跨域都是打着安全路数的擦边球,都可以认为是 hack 处理。这一段是我从别的地方cp过来的,大家将就着看吧。

        这里要注意的是,只有访问类型为xhr(XMLHttpRequest)的才会出现跨域。

跨域问题的解决方案

    修改浏览器的设置

    修改请求的方式:jsonp

    CORS

修改浏览器配置解决跨域

        以Google Chrome为例,浏览器以

    "C:\ProgramFiles(x86)\Google\Chrome\Application\chrome.exe"

        --disable-web-security--user-data-dir

中模式打开,右键点击浏览器快捷方式,在目标中输入上述代码即可解决(不推荐)。

使用jsonp解决跨域

JQuery中的正常AJAX请求代码片段

    $("#demo1").click(function(){

        $.ajax({

            url : 'http://www.tpadmin.top/Index/Test/crossDomain',

            data : {},

            type : 'get',

            success : function (res) {

                //No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1' is therefore not allowed access. 在执行时候报出的错误,这代表了跨域错误

                alert(res);

            }

        });

    });

JQuery中的使用JSONP的AJAX请求代码:

    $("#demo2").click(function(){

        $.ajax({

            url : 'http://www.tpadmin.top/Index/Test/crossDomain',

            data : {},

            type : 'get',

            dataType : 'jsonp',

            success : function (res) {

                alert(res);

            }

        });

    });

这时候我们看到 请求的网址自动变成了

http://www.tpadmin.top/Index/Test/crossDomain?callback=jQuery331015214102388989237_1534993962395&_=1534993962396

这是为什么呢?原来由于跨域访问的只限制xhr类型的请求(上文中已经说了),所以js中就利用了这一特点,让服务端不在返回的是一个JSON格式的数据,而是返回一段JS代码,将JSON的数据以参数的形式传递到这个函数中,而函数的名称就是callback参数的值,所以我们还需要修改服务端的代码,代码如下:

    <?php

        $callback = isset($_GET['callback'])?$_GET['callback']:'';

        if (!empty($callback)) {

            $arr = ['code' => 200, 'name' => 'cui'];

            $data = json_encode($arr);

            exit($callback . '(' . $data . ')');

        }

    ?>

OK,现在问题解决了,但是JSONP存在着诸多限制,下面将列出两个个我知道的:

    JSONP只支持GET请求,什么?你要提交表单,sorry,此路不通

    它只支持跨域HTTP请求

虽然只有两个,但是让很多人不得不放弃它,所以出现了下面的解决办法。

CORS解决跨域

回归问题本质,跨域问题为什么会产生,上面已经说了,是由于浏览器的限制,那么在执行过程中有什么不同,下面两张度分析一下(主要看请求头的部分):

这是非跨域请求

这是跨域请求

这时我们发现跨域访问的请求头中存在Origin的字段,用来记录当前的访问域名,我们可以再服务端增加一个响应头Access-Control-Allow-Origin来告诉浏览器我们支持它获取就可以了,代码实现:

    <?php

    header('Access-Control-Allow-Origin:http://127.0.0.1');

    $arr = ['code' => 200, 'name' => 'cui'];

    echo $data = json_encode($arr);

    ?>

那如果我有多个域名进行跨域访问呢

    <?php

    $requestHeader = getallheaders();

    $origin = isset($requestHeader['Origin'])?$requestHeader['Origin']:'';

    switch ($origin) {

        case 'http://127.0.0.1':

            header('Access-Control-Allow-Origin:http://127.0.0.1');

            break;

        case 'http://localhost':

            header('Access-Control-Allow-Origin:http://localhost');

            break;

        default:

            break;

    }

    $arr = ['code' => 200, 'name' => 'cui'];

    echo $data = json_encode($arr);

    //注意,不支持下面这种写法

    //header('Access-Control-Allow-Origin:http://localhost,http://127.0.0.1');

    ?>

或者直接写成(很不安全,不推荐这么写)

    <?php

    header('Access-Control-Allow-Origin:*');

    $arr = ['code' => 200, 'name' => 'cui'];

    echo $data = json_encode($arr);

    ?>

到这里,其实已经结束了,但还有一些其他的特殊情况

    请求方法不是GET、HEAD、POST

    请求头中存在自定义头

    Content-Type不是text/plain、multipart/form-data、application/x-www-form-urlencoded

    希望获取到服务端的Cookie

为了应对种种限制,我们再来看一段代码

    $("#demo1").click(function(){

        $.ajax({

            url : 'http://cui.tpadmin.top/crossDomain.php',

            data : {},

            type : 'PUT',

            contentType : 'application/json',

            header: {

                token:'asdfgqwerttyyazxcvbvb'

            },

            success : function (res) {

                alert(res);

            }

        });

    });

虽然我们在服务端加入了Access-Control-Allow-Origin响应头,但是如果出现上面所说的情况时,我们需要做一些特殊的设置,修改服务端代码为:

    <?php

    //这里增加了两行代码

    header('Access-Control-Allow-Headers:Content-Type');

    header('Access-Control-Allow-Methods:PUT');

    $requestHeader = getallheaders();

    $origin = isset($requestHeader['Origin'])?$requestHeader['Origin']:'';

    switch ($origin) {

        case 'http://127.0.0.1':

            header('Access-Control-Allow-Origin:http://127.0.0.1');

            break;

        case 'http://localhost':

            header('Access-Control-Allow-Origin:http://localhost');

            break;

        default:

            break;

    }

    $arr = ['code' => 200, 'name' => 'cui'];

    echo $data = json_encode($arr);

    ?>

这里虽然成功了,但是我们发现每次请求的时候会出现两条请求记录(这个可不是我请求了两次,看下面截图)

这里我们需要进行一下区分(简单请求模式与非简单请求模式)

    请求方法只能为GET、HEAD、POST

    请求头中无自定义头

    Content-Type必须为text/plain、multipart/form-data、application/x-www-form-urlencoded

符合以上条件的为简单请求,否则为非简单请求,注意,非简单请求中,浏览器会默认发送两条请求,第一条为预检请求(OPTION),第二条为AJAX的请求,处于服务器的性能考量,我们需要将预检命令进行缓存,而不是每次都执行预检请求,我们可以修改代码如下:

    <?php

    header('Access-Control-Allow-Headers:Content-Type');

    header('Access-Control-Allow-Methods:PUT');

    //看这里

    header('Access-Control-Max-Age:3600');

    $requestHeader = getallheaders();

    $origin = isset($requestHeader['Origin'])?$requestHeader['Origin']:'';

    switch ($origin) {

        case 'http://127.0.0.1':

            header('Access-Control-Allow-Origin:http://127.0.0.1');

            break;

        case 'http://localhost':

            header('Access-Control-Allow-Origin:http://localhost');

            break;

        default:

            break;

    }

    $arr = ['code' => 200, 'name' => 'cui'];

    echo $data = json_encode($arr);

    ?>

这次我请求了两次,发现第二次请求没有发送预检请求,新增加的代码代表允许缓存的时间(3600S)。

另外还有一些常用的方法,我都放到这里了,有需要的小伙伴可以参考

    <?php

    //支持Cookie

    header('Access-Control-Allow-Credentials:true');

    //支持自定义头

    header('Access-Control-Allow-Headers:token,Content-Type,...');​

由于篇幅有限,Cookie的例子小伙伴们自己做就可以了,使用Cookie的时候需要注意Access-Control-Allow-Origin不可设置为*。

服务软件实现跨域

上面的例子实现了跨域访问,但是如果我不想在代码中修改,还有其他的方法吗,当然有了,我们可以直接修改服务软件,下面将从最常用的Apache与Nignx两个方面说明

基于Apache的服务

    <VirtualHost *:80>

      ServerName localhost

      ServerAlias localhost

      DocumentRoot "${INSTALL_DIR}/www"


      Header always set Access-Control-Allow-Header "PUT"

      Header always set Access-Control-Allow-Credentials "true"

      Header always set Access-Control-Max-Age "3600"

      #这里设置的为全匹配

      Header always set Access-Control-Allow-Origin "expr=%{req:origin}"

      #这里设置的为全匹配

      Header always set Access-Control-Allow-Headers "expr=%{req:Access-Control-Allow-Headers}"


      <Directory "${INSTALL_DIR}/www/">

        Options +Indexes +Includes +FollowSymLinks +MultiViews

        AllowOverride All

        Require local

      </Directory>

    </VirtualHost>

这里说明一下,由于Apache都是模块管理,所以这里要想使用Header的话,需要加载mod_headers.so这个模块,去看一下自己的主配置文件,这个模块是否开启,如果没有开启,apache会启动失败的。

LoadModule headers_module modules/mod_headers.so

由于本人学艺不精,Apache的配置中的判断没有完全闹懂,所以这里没有进行区分的匹配,如果有大神知道,请指导一下,不胜感激。

基于Nignx的服务

在nignx服务的配置中修改为如下代码(注意位置,这个例子是修改本地的nignx,没有域名,如果没有熟悉过nignx的小伙伴可以自行百度)。

    server {

        listen      80 default_server;

        listen      [::]:80 default_server;

        server_name  _;

        root        /usr/share/nginx/html;

        # Load configuration files for the default server block.

        include /etc/nginx/default.d/*.conf;

        location / {

                #支持其他请求

                add_header Access-Control-Allow-Methods PUT;

                #设置预检请求的缓存

                add_header Access-Control-Max-Age 3600;

                #允许Cookie

                add_header Access-Control-Allow-Credentials true;

                #这里最好做判断,怕麻烦的话就写*,但是不建议

                if ($http_origin = http://localhost){

                        add_header Access-Control-Allow-Origin http://localhost;

                }

                if ($http_origin = http://127.0.0.1){

                        add_header Access-Control-Allow-Origin http://127.0.0.1;

                }

                #为了方便,这样写了

                add_header Access-Control-Allow-Headers $http_access_control_request_headers;

                if ($request_method = OPTIONS){

                        return 200;

                }

        }

        error_page 404 /404.html;

            location = /40x.html {

        }

        error_page 500 502 503 504 /50x.html;

            location = /50x.html {

        }

    }

另外再说一句,如果您使用的是JAVA的话,可以尝试一下正反向代理。



背景

最近做一个前后端分离的项目时,使用shiro做权限管理时遇到跨域问题,这里做一下记录。

,,


原因

后端通过Shiro配置URL过滤,

shiroFilterFactoryBean.setLoginUrl("/unauth");

默认对于没有授权的访问请求会redirect至LoginUrl.但在跨域访问时,redirect失败.原因是基于安全考虑,redirect发生时Response Header的信息会被清除,导致client端的访问被server端拒绝.

解决


@Component

public class CORSFilter extends OncePerRequestFilter {

/*

* 在ResponseBodyWrapHandler中已处理跨域问题

* 但是在shiro验证未通过跳转/unauth时, 因为redirect 重定向会丢失所有请求头,跨域问题重新出现

* */

@Override

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {

HttpServletResponse res = (HttpServletResponse) servletResponse;

res.setContentType("text/html;charset=UTF-8");

res.setHeader("Access-Control-Allow-Origin", "*");

res.setHeader("Access-Control-Allow-Methods", "*");

res.setHeader("Access-Control-Max-Age", "0");

res.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");

res.setHeader("Access-Control-Allow-Credentials", "true");

res.setHeader("XDomainRequestAllowed","1");

filterChain.doFilter(servletRequest, servletResponse);

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354