一, 背景
登录是一个网站最基础的功能. 有人说它很简单, 其实不然, 登录逻辑很简单, 但涉及知识点比较多, 如: 密码加密, cookie,session,token,JWT 等.
我们看一下传统的做法, 前后端统一在一个服务中:
如图所示, 逻辑处理和页面放在一个服务中, 用户输入用户名, 密码后, 后台服务在 session 中设置登录状态, 和用户的一些基本信息, 然后将响应 (Response) 返回到浏览器 (Browser), 并设置 Cookie. 下次用户在这个浏览器(Browser) 中, 再次 访问服务时, 请求中会带上这个 Cookie, 服务端根据这个 Cookie 就能找到对应的 session, 从 session 中取得用户的信息, 从而 维持了用户的登录状态. 这种机制被称作 Cookie-Session 机制.
近几年, 随着前后端分离的流行, 我们的项目结构也发生了变化, 如下图:
我们访问一个网站时, 先去请求静态服务, 拿到页面后, 再异步去后台请求数据, 最后渲染成我们看到的带有数据的网站. 在这种结构下, 我们的登录状态怎么维持呢? 上面的 Cookie-Session 机制还适不适用?
这里又分两种情况, 服务 A 和服务 B 在同一域下, 服务 A 和服务 B 在不同域下. 在详细介绍之前, 我们先普及一下浏览器的同源策略.
二, 同源策略
同源策略是浏览器保证安全的基础, 它的含义是指, A 网页设置的 Cookie,B 网页不能打开, 除非这两个网页同源. 所谓同源是指:
协议相同
域名相同
端口相同
例如: http://www.a.com/login, 协议是 http, 域名是 www.a.com, 端口是 80. 只要这 3 个相同, 我们就可以在请求 (Request) 时带上 Cookie, 在响应 (Response) 时设置 Cookie.
三, 同域下的前后端分离
我们了解了浏览器的同源策略, 接下来就看一看同域下的前后端分离, 首先看服务端能不能设置 Cookie, 具体代码如下:
后端代码:
- @RequestMapping("setCookie")
- public String setCookie(HttpServletResponse response){
- Cookie cookie = new Cookie("test","same");
- cookie.setPath("/");
- response.addCookie(cookie);
- return "success";
- }
我们设置 Cookie 的 path 为根目录 "/", 以便在该域的所有路径下都能看到这个 Cookie.
前端代码:
- <!DOCTYPE html>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- test
- </title>
- <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js">
- </script>
- <script>
- $(function() {
- $.Ajax({
- url: "/test/setCookie",
- method: "get",
- success: function(JSON) {
- console.log(JSON);
- }
- });
- })
- </script>
- </head>
- <body>
- aaa
- </body>
- </HTML>
我们在浏览器访问 http://www.a.com:8888/index.html, 访问前先设置 hosts, 将 www.a.com 指向我们本机. 访问结果如图所示:
我们可以看到服务器成功设置了 Cookie. 然后我们再看看同域下, 异步请求能不能带上 Cookie, 代码如下:
后端代码:
- @RequestMapping("getCookie")
- public String getCookie(HttpServletRequest request,HttpServletResponse response){
- Cookie[] cookies = request.getCookies();
- if (cookies != null && cookies.length>0) {
- for (Cookie cookie : cookies) {
- System.out.println("name:" + cookie.getName() + "-----value:" + cookie.getValue());
- }
- }
- return "success";
- }
前端代码如下:
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- user
- </title>
- <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js">
- </script>
- <script>
- $(function() {
- $.Ajax({
- url: "http://www.b.com:8888/test/getCookie",
- method: "get",
- success: function(JSON) {
- console.log(JSON);
- }
- });
- })
- </script>
- </head>
- <body>
- </body>
- </HTML>
访问结果如图所示:
再看看后台打印的日志:
name:test-----value:same
同域下, 异步请求时, Cookie 也能带到服务端.
所以, 我们在做前后端分离时, 前端和后端部署在同一域下, 满足浏览器的同源策略, 登录不需要做特殊的处理.
四, 不同域下的前后端分离
不同域下, 我们的响应 (Response) 能不能设置 Cookie 呢? 请求时能不能带上 Cookie 呢? 我们实验结果如下, 这里就不给大家贴代码了.
由于我们在 a.com 域下的页面跨域访问 b.com 的服务, b.com 的服务不能设置 Cookie.
如果 b.com 域下有 Cookie, 我们在 a.com 域下的页面跨域访问 b.com 的服务, 能不能把 b.com 的 Cookie 带上吗? 答案是也带不上. 那么我们怎么解决 跨域问题呢?
4.1 JSONP 解决跨域
JSONP 的原理我们可以在维基百科上查看, 上面写的很清楚, 我们不做过多的介绍. 我们改造接口, 在每个接口上增加 callback 参数:
- @RequestMapping("setCookie")
- public String setCookie(HttpServletResponse response,String callback){
- Cookie cookie = new Cookie("test","same");
- cookie.setPath("/");
- response.addCookie(cookie);
- if (StringUtils.isNotBlank(callback)){
- return callback+"('success')";
- }
- return "success";
- }
- @RequestMapping("getCookie")
- public String getCookie(HttpServletRequest request,HttpServletResponse response,String callback){
- Cookie[] cookies = request.getCookies();
- if (cookies != null && cookies.length>0) {
- for (Cookie cookie : cookies) {
- System.out.println("name:" + cookie.getName() + "-----value:" + cookie.getValue());
- }
- }
- if (StringUtils.isNotBlank(callback)){
- return callback+"('success')";
- }
- return "success";
- }
如果 callback 参数不为空, 将返回 JS 函数. 前端改造如下:
设置 Cookie 页面改造如下:
- <script>
- $(function () {
- $.Ajax({
- url : "http://www.b.com:8888/test/setCookie?callback=?",
- method: "get",
- dataType : 'jsonp',
- success : function (JSON) {
- console.log(JSON);
- }
- });
- })
- </script>
请求 Cookie 时改造如下:
- <script>
- $(function () {
- $.Ajax({
- url : "http://www.b.com:8888/test/getCookie?callback=?",
- method: "get",
- dataType : 'jsonp',
- success : function (JSON) {
- console.log(JSON);
- }
- });
- })
- </script>
所有的请求都加了 callback 参数, 请求的结果如下:
很神奇吧! 我们设置了 b.com 域下的 Cookie. 如果想知道为什么? 还是看一看 JSONP 的原理吧. 我们再访问第二个页面, 看看 Cookie 能不能 传到服务. 后台打印日志为:
name:test-----value:same
好了, 不同域下的前后端分离, 可以通过 JSONP 跨域, 从而保持登录状态. 但是, JSONP 本身没有跨域安全规范, 一般都是后端进行安全限制, 处理不当很容易造成安全问题.
4.2 CORS 解决跨域
CORS 是一个 W3C 标准, 全称是 "跨域资源共享"(Cross-origin resource sharing).CORS 需要浏览器和服务器同时支持. 目前, 所有浏览器都支持该功能, IE 浏览器不能低于 IE10. 整个 CORS 通信过程, 都是浏览器自动完成, 不需要用户参与. 对于开发者来说, CORS 通信与同源的 Ajax 通信没有差别, 代码完全一样. 浏览器一旦发现 Ajax 请求跨源, 就会自动添加一些附加的头信息, 有时还会多出一次附加的请求, 但用户不会有感觉. 如果想要详细理解原理, 请参考维基百科
CORS 请求默认不发送 Cookie 和 HTTP 认证信息. 若要发送 Cookie, 浏览器和服务端都要做设置, 咱们要解决的是跨域后的登录问题, 所以要允许跨域发送 Cookie.
后端要设置允许跨域请求的域和允许设置和接受 Cookie.
- @RequestMapping("setCookie")
- @CrossOrigin(origins="http://www.a.com:8888",allowCredentials = "true")
- public String setCookie(HttpServletResponse response){
- Cookie cookie = new Cookie("test","same");
- cookie.setPath("/");
- response.addCookie(cookie);
- return "success";
- }
- @RequestMapping("getCookie")
- @CrossOrigin(origins="http://www.a.com:8888",allowCredentials = "true")
- public String getCookie(HttpServletRequest request,HttpServletResponse response){
- Cookie[] cookies = request.getCookies();
- if (cookies != null && cookies.length>0) {
- for (Cookie cookie : cookies) {
- System.out.println("name:" + cookie.getName() + "-----value:" + cookie.getValue());
- }
- }
- return "success";
- }
我们通过 @CrossOrigin 注解允许跨域, origins 设置了允许跨域请求的域, allowCredentials 允许设置和接受 Cookie.
前端要设置允许发送和接受 Cookie.
- <script>
- $(function () {
- $.Ajax({
- url : "http://www.b.com:8888/test/setCookie",
- method: "get",
- success : function (JSON) {
- console.log(JSON);
- },
- xhrFields: {
- withCredentials: true
- }
- });
- })
- </script>
- <script>
- $(function () {
- $.Ajax({
- url : "http://www.b.com:8888/test/getCookie",
- method: "get",
- success : function (JSON) {
- console.log(JSON);
- },
- xhrFields: {
- withCredentials: true
- }
- });
- })
- </script>
我们访问页面看一下效果.
没有 Cookie 吗? 别急, 我们再从浏览器的设置里看一下.
有 Cookie 了, 我们再看看访问能不能带上 Cookie, 后台打印结果如下:
name:test-----value:same
我们使用 CORS, 也解决了跨域.
总结
前后端分离, 基于 Cookie-Session 机制的登录总结如下
前后端同域 -- 与普通登录没有区别
前后端不同域
JSONP 方式实现
CORS 方式实现
来源: https://www.cnblogs.com/boboooo/p/9779355.html