前端遇到302处理方式以及设置第三方Cookie研究
努力搬砖的宣 2024-06-10 16:33:03 阅读 74
前端遇到302处理方式以及设置第三方Cookie研究
1、如何解决后端302重定向问题
背景:由于认证中心网关检测到用户是未登录态情况下的话,会将用户重定向到认证中心的登录页。
此时,假如是使用Oauth2协议,登录成功后,前端需要带着登录成功的信息(jwt),访问/Oauth2/1/authorize接口,此时该接口将会重定向回redirect_uri的地址,这个时候的重点在于如何访问/Oauth2/1/authorize接口。
我们列举一下几种请求方式:
AjaxFetchLocation.hrefForm表单Nvigator.sendBeacon
技术 | 特点 | 是否可用 |
---|---|---|
Ajax(XMLHttpRequest ) | 不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,捕获不到302状态码,并且假如后端响应头的Location返回的路径与Ajax的Referer不一致的话,会产生跨域报错。 | × |
Fetch | 针对 302 的情况,其实 fetch 有个特殊的配置叫 redirect,可以捕获302的部分内容。但是非常可惜的是,目前 response 的内容非常有限,仅能捕获到该接口访问是否存在302,捕获不到302的具体跳转路径,并且也不能跟随跳转。 | × |
Location.href | 浏览器地址直接跳转接口,默认采用Get请求,不存在跨域问题,浏览器也可以直接跟随后端产生的302重定向。 | √ |
Form表单 | 通过刷新整个页面进行访问请求,可使用Get/Post方法,也可以跟随后端进行的302跳转,缺点是需要新建一个表单,操作比较复杂。 | √ |
Nvigator.sendBeacon | navigator.sendBeacon() 方法可用于通过 HTTP POST 将少量数据异步传输到 Web 服务器。同时避免了用传统技术(如:XMLHttpRequest )发送分析数据的一些问题。缺点是仅支持POST请求,以及是异步操作,也不跟随后端302。 | √ |
根据以上方式做一个测试:
我们做一个302重定向的接口,重定向的路径是分别为http://localhost:3000/login和https://www.baidu.com,客户端的域名为localhost,情况如下图:
假如采用常用的Ajax,例如Axios或者Fetch,前端代码以及效果如下:
1、Axios:当点击按钮时候,调用/redirectTo接口,后端返回302重定向,此时我们可以看到,重定向后的地址并不是按照预期在浏览器上跳转,而是重新用XHR请求重定向之后的地址,此时由于接口请求头的Referer为localhost:3000与RequestURL:https:www.baidu.com存在跨域问题,故报错。
那假如后端返回重定向的地址不存在跨域问题呢(此时和客户端同域)?
此时虽然不存在跨域问题了,但是重定向后的地址因为是使用XHR访问的,然而你客户端并没有开启Servlet等服务,故接口会返回404NotFound。
综上所述,使用Ajax处理302是行不通的。
2、采用Fetch,网上对于fetch众说纷纭,经测试,fetch也不能跟随302跳转页面,会将请求后的报文体返回回来。
3、使用Location.href,无论重定向后的地址是否跨域,均可以成功重定向。
4、使用form表单做302跳转,也是可以成功的,但是需要创建一个虚拟节点,处理起来较为复杂。
5、Nvigator.sendBeacon,由于该方法必须采用Post,故更改一下后端代码。虽然使用该方法没有跨域问题,但是由于是异步方法,并不会让浏览器跟随302操作。
总结:使用Location.href直接访问接口
上述五种方式,Ajax和Fetch皆是异步请求,不能跟随浏览器302的操作,并且还获取不到接口返回的Location等信息,所以不采用。Form表单可以使用,功能也较为全面,但是实现方式较为复杂,且Form表单通常用于表单内容提交,与场景语义不符,故Pass**。Location.href的方式即以Get请求直接使用浏览器访问该接口,参数携带方便,也能跟随重定向操作,故采用。**
附上代码:
import serviceInstance from "../../services";// const res = serviceInstance({ // url: "/redirectTo", //不用引入,直接在api后面接接口// method: "get",// data: {},// });// console.log(res);function Home() { const setRedirect = () => { const res = serviceInstance({ url: "/redirectTo", //不用引入,直接在api后面接接口 method: "get", data: { }, }); console.log(res); }; const useFetchSetRedirect = () => { const res = fetch("/redirectTo",{ method: "get", redirect:'follow' }); console.log(res); }; const useLocationRedirect = () => { window.location.href = "http://localhost:8080/redirectTo" }; const useFormData = ()=>{ const form = document.createElement("form"); form.action = "http://localhost:8080/redirectTo"; document.getElementById("container").appendChild(form); form.submit(); document.getElementById("container").removeChild(form); } const useSendBeacon = ()=>{ navigator.sendBeacon("http://localhost:8080/redirectToPost") } return ( <div id="container"> <button onClick={ setRedirect}>测试Axios重定向</button> <button onClick={ useFetchSetRedirect}>测试Fetch重定向</button> <button onClick={ useLocationRedirect}>测试location重定向</button> <button onClick={ useFormData}>测试Form表单重定向</button> <button onClick={ useSendBeacon}>测试SendBeacon重定向</button> </div> );}export default Home;
package com.xuan.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@RestControllerpublic class TestController { @GetMapping("/redirectTo") void testRedirect(HttpServletResponse response) throws IOException { String testUrl = "https://www.baidu.com";// String testUrl2 = "http://localhost:3000/login"; response.sendRedirect(testUrl); } @PostMapping ("/redirectToPost") void testRedirectPost(HttpServletResponse response) throws IOException { String testUrl = "https://www.baidu.com";// String testUrl2 = "http://localhost:3000/login"; response.sendRedirect(testUrl); }}
2、子系统(接入认证中心的系统)如何接入认证中心,获取登录态?
背景:由于子系统和认证中心是不同的系统,在认证中心登录成功后重定向回子系统的过程中,如何得知已登录的登录态呢?
此时我们在重定向的过程中,需要传递信息给其他系统,仅有三种方式(据我所知):
浏览器地址栏携带
Cookie
Window.postMessage
实现方式 | 特点 | 是否可用 |
---|---|---|
浏览器地址栏携带 | 优点:操作方便,无同域限制 ; 缺点:信息完全暴露在地址栏,安全性不高 | √ |
Cookie | 优点:在设置Cookie的时候可以通过Domain、Path、SameSite等字段,将Cookie设置在需要获取的应用上 ,安全性以及准确性较高; 缺点:遵循浏览器的同源限制,设置的Cookie仅在本域名或者子域名下共享。 | √ |
Window.postMessage | 是一种浏览器提供的跨域传输信息方式,在认证中心完成登录操作,拿到Token的话,可以使用该方式传输信息。但是与使用场景不符,故Pass掉 | × |
总结: 浏览器地址栏携带和Cookie均可以完成302后传递信息(assess-token或者code)的操作,其中各有优缺点,应根据项目需要以及具体情况具体采用不同的方式。同域建议采用Cookie,不同域推荐采用浏览器地址栏携带。
子系统与认证中心同域
采用Cookie方式可以较为简单地实现单点登录,设置Cookie(access-token)到具体的域(Domain),通过Path去限制相应的系统,这样可以发挥认证中心实现单点登录的效果。但是浏览器必须把Cookie打开,以及应对Cookie具体设置对应的条件,以防其他系统通过CSRF等手段,获取到用户信息。
子系统与认证中心跨域
建议采用浏览器地址栏携传递信息,这个时候考虑到安全问题,不应该采用明文的形势将access-token放到地址栏,而是将重定向后携带的code通过地址栏传输回去子系统,然后子系统通过这个code调用接口获取access-token(采用空白页的方式)。
3、拓展:302跳转如何SetCookie到对应的系统。
背景:出现在本地联调的情况,后端设置重定向到对应的系统,并SetCookie到开发环境,设置Domain为开发环境的ip/域名,但是发现Cookie并未设置上。
故排查原因,发现是因为重定向的接口的请求头中Host与后端设置的Domain不一致,Cookie被屏蔽掉了。报错如下图:
为此我做了个测试,模拟设置Cookie到百度的网站上:
首先设置Host文件(127.0.0.1 test.baidu.com),模拟百度的域名到本机ip;
然后在后端代码重定向到www.baidu.com,同时设置cookie到baidu.com这个域名上。
@GetMapping("/setCookieRedirect") void testCookieRedirect(HttpServletResponse response) throws IOException { String testUrl = "http://baidu.com"; Cookie cookie = new Cookie("testCookie","test"); cookie.setDomain("baidu.com"); cookie.setMaxAge(43200); cookie.setSecure(false); cookie.setHttpOnly(false); response.addCookie(cookie); response.sendRedirect(testUrl); }
重点:前端调用该后端接口时,不可以用localhost调用,而是用test.baidu.com这个域名调用,如下:
const testThirdCookie = ()=>{ window.location.href = "http://test.baidu.com:8080/setCookieRedirect" }
最后成功设置Cookie到百度上,效果如下:
总结:关键就是请求头的host与设置的Domain对象域名得一致,或者父级域名包括子级域名即可。
const testThirdCookie = ()=>{ window.location.href = "http://test.baidu.com:8080/setCookieRedirect" }
最后成功设置Cookie到百度上,效果如下:
总结:关键就是请求头的host与设置的Domain对象域名得一直,或者父级域名包括子级域名即可。
上一篇: 2024年前端最新程序员看过都说好的资源网站,看看你都用过哪些?_都懂都懂网站,web开发需求
下一篇: 正确解决org.springframework.web.HttpMediaTypeNotAcceptableException异常的有效解决方法
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。