网络

06 URL的PATH中的反斜杠 阅读更多

示例 首先看个例子: http://www.abc.com/abc/ http://www.abc.com/abc 他们两个不同的地址: /abc/:表示的是目录, /abc:表示的是文件, server端 一般来说,索引页面(如文章列表)作为目录,内容页面作为文件。 在开发的过程中一般也要满足上面约定俗成的规定,因此在路由中匹配Path的时候就要注意这一点。 比如,开发一个静态文件服务器,那么: 文件目录的Path是:/files/datasets/ 具体文件的Path是:/files/pom.xml 子目录的Path是:/files/datasets/ 如果访问目录的时候没有在Path中协商最后的斜杠/,那么有两种情况: 在server端进行处理,自动给Path加上最后一个斜杠 如果server端没有进行上述处理,那么返回301响应 curl -i localhost:10010/datasets HTTP/1.1 301 Moved Permanently Content-Type: text/html; charset=utf-8 Location: /datasets/ Date: Mon, 03 Aug 2020 05:39:29 GMT Content-Length: 47 <a href="/datasets/">Moved Permanently</a>.

05 Pending的请求 阅读更多

现象 发起请求后,Chrome浏览器显示的状态一直是pending,等待响应,最后超时了。 工具 Chrome的日志采集工具:chrome://net-export/ Chrome的日志分析工具:https://netlog-viewer.appspot.com/#import 正常的是这样的,有send,有read: 异常的就是这两个步骤中有一个步骤出现问题了。

04 LDAP 阅读更多

0.1. 简介 0.2. 协议概观 0.3. 目录结构 0.4. 操作命令 0.4.1. Add 0.4.2. Bind(认证) 0.4.3. Delete 0.4.4. Search and Compare 0.4.4.1. baseObject 0.4.4.2. scope 0.4.4.3. filter 0.4.4.4. derefAliases 0.4.4.5. attributes 0.4.4.6. sizeLimit, timeLimit 0.4.4.7. typesOnly 0.4.5. Modify 0.4.6. Modify DN 0.4.7. Extended operations 0.4.7.1. StartTLS 0.4.8. Abandon 0.4.9. Unbind 0.5. URI scheme 0.6. Schema 0.1. 简介 Lightweight Directory Access Protocol (LDAP) 是一个开源的与供应商无关的工业级标准应用层协议,用于通过IP协议访问和维护分布式目录信息服务。 不论是开发内网还是公网应用程序,域目录服务都发挥着重要作用,它允许在整个网络中共享有关用户,系统,网络,服务和应用程序的信息。 因此,目录服务可以提供任何组织的记录集合,通常具有分层结构,例如公司的电子邮件目录。类似的电话目录就是一个带有客户地址和电话号码的列表。 LDAP的最新版本是V3,发布在RFC 4511。[Request for Comments 简称RFCs]。 LDAP的常见用法是提供一个中心位置来存储用户名和密码。这允许许多不同的应用程序和服务连接到LDAP服务器以验证用户。 LDAP是基于X.500标准中包含的标准的更简单子集。因此,LDAP也称为X.500-lite。 0.2. 协议概观 客户端启动一个LDAP会话来连接到一台LDAP服务器(称为目录系统代理DSA),默认连接到TCP和UDP的389端口上,或者在LDAPS(基于SSL的LDAP)的636端口上。 然后客户端发送一个操作请求到服务器,服务器返回响应信息。除了某些例外,客户端无需在发送下一个请求之前等待上一个响应返回,服务器可以按任意顺序返回响应。所有请求和响应信息都使用基本编码规则(Basic Encoding Rules,BER)编码后再进行网络传输。 客户端可以执行如下请求操作: StartTLS:使用LDAPv3 TLS扩展进行安全连接 Bind:认证并指定LDAP协议版本 Search:搜索或检索目录条目 Compare:测试一个给定的条目是否包含给定的属性值 Add:添加一个新条目 Delete:删除一个条目 Modify:修改一个条目 Modify Distinguished Name (DN):移动或重命名一个条目 Abandon:终止上一个请求 Extended Operation:用于定义其他操作的通用操作 Unbind:关闭连接(不是bind的逆操作) 另外,服务器可以发送“未经请求的通知(Unsolicited Notifications)”,这不是对任何请求的响应信息。例如,连接超时之前。 保护LDAP通信的一种常见可选方案是使用SSL隧道。LDAP over SSL的默认端口是636。在LDAPv2中普遍使用基于LDAP over SSL,但从未在任何正式规范中对其进行标准化。LDAPv2已于2003年正式停用。 0.3. 目录结构 LDAP协议提供了一个目录,该目录遵循X.500模型的1993版: 一个条目由一组属性组成 一个属性具有名称(表示属性类型或属性描述)和一个或多个值,这些属性在scheme中定义 每个条目都有一个唯一的标识符:它的专有名称(Distinguished Name,DN),由它的相对专有名称(Relative Distinguished Name,RDN)组成,和条目中的某些属性组成,后面紧跟着父条目的DN。 将DN视为完整文件路径,将RDN视为其父文件夹中的相对文件名(例如,如果/foo/bar/myfile.txt是DN,则myfile.txt是RDN)。 当条目在路径树中移动时,DN可能会在条目的生存期内发生变化。为了可靠且明确地标识条目,可以在条目的操作属性集中提供UUID。 当以LDAP数据交换格式(LDAP Data Interchange Format,LDIF)表示时,条目看起来像这样(LDAP本身是二进制协议): dn: cn=John Doe,dc=example,dc=com cn: John Doe givenName: John sn: Doe telephoneNumber: +1 888 555 6789 telephoneNumber: +1 888 555 1232 mail: john@example.com manager: cn=Barbara Doe,dc=example,dc=com objectClass: inetOrgPerson objectClass: organizationalPerson objectClass: person objectClass: top dn是条目的专有名称,它既不是属性也不是条目的一部分,dc=example,dc=com是DN的父条目,其中dc表示域组件(Domain Component) cn是条目的相对专有名称 剩下的行表示的是这个条目的属性。属性名称通常是助记符字符串,例如: cn:(common name)代表通用名称 dc:(domain component)代表域组件 mail:代表电子邮件地址 sn:(surname)代表姓氏 服务器持有一个从特定条目开始的子路径树,例如:dc=example,dc=com以及它的子级。 服务器可能还会保留对其他服务器的引用,因此尝试访问ou = department,dc = example,dc = com可能会返回对拥有目录树部分的服务器的引用或延续引用。然后,客户端可以联系另一台服务器。 某些服务器还支持链式(chaining),这意味着该服务器将会与另一台服务器通信并将结果返回给客户端。 LDAP很少定义顺序:服务器可以按任意顺序返回属性的值,条目中的属性以及通过搜索操作找到的条目。 正式定义描述:条目定义为一组属性,而属性是一组值,并且这些组不需要排序。 0.4. 操作命令 0.4.1. Add ADD操作将新条目插入目录服务器数据库。如果目录中已经存在Add请求中的DN,则服务器将不会添加重复条目,但会将添加结果中的结果代码设置为十进制的68“entryAlreadyExists”。 在尝试查找条目时,符合LDAP的服务器将永远不会取消在Add请求中传输过来的专有名称的引用,即专有名称不会被解除别名。 符合LDAP的服务器将确保专有名称和所有属性符合命名标准。 要添加的条目必须不存在,并且直接上级必须存在。 dn: uid=user,ou=people,dc=example,dc=com changetype: add objectClass:top objectClass:person uid: user sn: last-name cn: common-name userPassword: password 在上面的例子中: uid=user,ou=people,dc=example,dc=com必须不存在 ou=people,dc=example,dc=com必须已存在 0.4.2. Bind(认证) 创建LDAP会话时,即LDAP客户端连接到服务器时,该会话的身份验证状态设置为匿名。 BIND操作建立会话的身份验证状态。 Simple BIND和SASL PLAIN可以以纯文本形式发送用户的DN和密码,因此,应使用TLS对使用Simple BIND或SASL PLAIN的连接进行加密。服务器通常查看给定条目中的userPassword属性来检查密码。Anonymous BIND(具有空DN和密码)会将连接重置为匿名状态。 SASL(简单身份验证和安全层,Simple Authentication and Security Layer)BIND提供多种认证机制(例如, Kerberos或通过TLS发送的客户端证书。 BIND还通过以整数形式发送版本号来设置LDAP协议版本。如果客户端请求了服务器不支持的版本,则服务器必须在BIND响应中将结果代码设置为协议错误代码。通常,客户端应使用LDAPv3,这是协议中的默认设置,但并非总是LDAP库中的默认设置。 在LDAPv2中,BIND必须是会话中的第一个操作,但是从LDAPv3开始,它不是必需的。在LDAPv3中,每个成功的BIND请求都会更改会话的身份验证状态,而每个失败的BIND请求都会重置会话的身份验证状态。 0.4.3. Delete 要删除一个条目,LDAP客户端应将格式正确的删除请求发送到服务器。 删除请求必须包含要删除条目的DN Request controls也可以附加到删除请求中 服务器在处理删除请求时不会取消引用别名 删除请求只能删除叶条目(没有下属的条目) 一些服务器支持操作属性hasSubordinates,该属性指示条目是否具有任何从属条目 一些服务器支持操作属性numSubordinates,该属性指示从属于包含numSubordinates属性的条目的条目数 某些服务器支持删除子树的request control,该请求允许删除DN以及从属于DN的所有对象。删除请求受访问控制的约束,即是否允许具有给定身份验证状态的连接删除给定条目由服务器特定的访问控制机制决定 0.4.4. Search and Compare Search操作用于搜索和读取条目,其参数如下。 0.4.4.1. baseObject 相对于要执行搜索的基础对象条目(可能的根)的名称。 0.4.4.2. scope 在baseObject下面要搜索哪些元素。可以是: BaseObject:仅搜索命名的条目,通常用于读取一个条目 singleLevel:在base DN下方的条目 WholeSubtree:从base DN开始的整个子树 0.4.4.3. filter 过滤范围内元素。 例如: (&(objectClass = person)(|(givenName = John)(mail = john *))) 选择匹配givenName和mail的objectClass属性中的person元素 请注意,常见的误区是LDAP数据区分大小写,而实际上匹配规则和排序规则来匹配或比较与相对值的关系。 如果要filter来匹配属性值的大小写,则必须使用可扩展的匹配过滤器,例如: (&(objectClass = person)(|(givenName:caseExactMatch:=John)(mail:caseExactSubstringsMatch:=john*))) 0.4.4.4. derefAliases 是否以及如何遵循别名条目(引用其他条目的条目) 0.4.4.5. attributes 在结果条目中返回哪些属性。 0.4.4.6. sizeLimit, timeLimit 返回的最大条目数,以及允许搜索运行的最长时间。 注意,这些值不能覆盖服务器对大小限制和时间限制的设定值。 0.4.4.7. typesOnly 仅返回属性类型,而不返回属性值。 服务器返回匹配的条目和可能的延续引用。这些可以以任意顺序返回。最终结果将包括结果代码。 Compare操作采用DN、属性名称和属性值,并检查命名条目是否包含具有该值的属性。 0.4.5. Modify LDAP客户端使用MODIFY操作来请求LDAP服务器对现有条目进行更改。尝试修改不存在的条目将失败。修改请求受服务器使用的访问控制的约束。 MODIFY操作要求指定条目的DN,并进行一系列更改。序列中的每个更改必须是以下之一: add:添加一个新值,该值必须不存在于属性中 delete:删除现有值 replace:用新值替换现有值 如下示例,向属性添加值的LDIF: dn: dc=example,dc=com changetype: modify add: cn cn: the-new-cn-value-to-be-added - 要替换现有属性的值,请使用replace关键字。如果属性是多值的,则客户端必须指定要更新的属性的值。 要从条目中删除属性,请使用delete关键字和changetype指示符Modify。如果属性是多值的,则客户端必须指定要删除的属性的值。 还有一个Modify-Increment扩展,它允许将可递增的属性值增加指定的数量。 如下示例,使用LDIF将employeeNumber递增5: dn: uid=user.0,ou=people,dc=example,dc=com changetype: modify increment: employeeNumber employeeNumber: 5 - 当LDAP服务器处于复制拓扑中时,LDAP客户端应考虑使用post-read control来验证更新,而不是在更新后进行搜索。 post-read control的设计使应用程序无需在更新后发出搜索请求,这是一种糟糕的形式(因为最终一致性模型),仅仅为了检查一个新的条目在更新后是否生效。 LDAP客户端不应假定每个请求都连接到同一目录服务器,因为可能在LDAP客户端和服务器之间存在负载平衡器或LDAP代理。 0.4.6. Modify DN Modify DN(移动/重命名条目)采用新的RDN,还可以选择新的父级DN,以及一个标志(该标志指示是否删除条目中与旧RDN匹配的值)。 服务器可能支持整个目录子树的重命名。 更新操作是原子性的,而其他的操作将看到新条目或旧条目。 另一方面,LDAP没有定义多个操作的事务:如果读取一个条目然后对其进行修改,则另一个客户端可能同时已更新了该条目。 服务器可以实现支持此功能的扩展。 0.4.7. Extended operations 扩展操作是一种通用的LDAP操作,可以定义不属于原始协议规范的新操作。 StartTLS是最重要的扩展之一。 其他示例包括Cancel和Password Modify。 0.4.7.1. StartTLS StartTLS操作在连接上建立TLS,可以提供: 数据机密性(以防止第三方查看数据) 数据完整性保护(以防止数据被篡改) 在TLS协商期间,服务器发送其X.509证书以证明其身份。客户也可以发送证书以证明其身份。 然后,客户端可以使用SASL/EXTERNAL。 通过使用SASL/EXTERNAL,客户端请求服务器从较低级别提供的凭据(例如TLS)中派生其身份。 尽管从技术上讲,服务器可以使用在任何较低级别建立的任何身份信息,但是通常服务器将使用TLS建立的身份信息。 服务器还通常在单独的端口上支持非标准的LDAPS(Secure LDAP或LDAP over SSL)协议,默认情况下为636。 LDAPS与LDAP有两种不同: 连接时,客户端和服务器建立TLS,然后再传输任何LDAP消息(不执行StartTLS操作) TLS关闭后必须关闭LDAPS连接 某些LDAPS客户端库仅对通信进行加密,并不会根据提供的证书中的名称检查主机名。 0.4.8. Abandon Abandon操作请求服务器中止由消息ID命名的操作。服务器不需要执行该请求。Abandon或成功的Abandon操作都不会返回响应。 类似的Cancel扩展操作会返回响应,但并非所有实现都支持此操作。 0.4.9. Unbind Unbind操作将放弃所有未完成的操作并关闭连接,不返回响应。该操作是历史遗留的,并不是Bind操作的逆操作。 客户端可以通过简单地关闭连接来中止会话,但更合适的操作是应该使用Unbind操作。 Unbind允许服务器正常关闭连接并释放资源,否则该资源将保留一段时间,直到发现客户端放弃连接为止。它还指示服务器取消可以取消的操作,并且不发送对不能取消的操作的响应。 0.5. URI scheme LADP的统一资源标识符方案已存在,客户端在不同程度上都支持该方案,服务器返回引用或延续引用,参考RFC 4516。 ldap://host:port/DN?attributes?scope?filter?extensions 以下描述的大多数部分都是可选的。 host:是要搜索的LDAP服务器的FQDN或IP地址 port:是LDAP服务器的网络端口(默认端口389) DN:是用作搜索基础的专有名称 attributes:是要用逗号分隔的属性列表 scope:指定搜索范围,可以是base(默认),one或sub filter:是一个搜索过滤器。例如(objectClass=*) extensions:是LDAP URL格式的扩展名。 例如: "ldap://ldap.example.com/cn=John%20Doe,dc=example,dc=com" 指向ldap.example.com中John Doe条目中的所有用户属性 "ldap:///dc=example,dc=com??sub?(givenName=John)" 搜索默认服务器中的条目。 注意:三元斜杠表示省略主机,而双问号表示省略属性 与其他URL一样,特殊字符必须进行百分比编码。 对于基于SSL的LDAP,存在类似的非标准ldaps URI方案。这不应与带有TLS的LDAP混淆,后者是通过使用标准ldap方案的StartTLS操作来实现的。 0.6. Schema 子树中条目的内容由directory schema控制,一组与目录信息树(DIT)的结构有关的定义和约束所控制。 Directory Server的模式定义了一组规则,用于管理服务器可以保存的信息种类。它包含许多元素,包括: Attribute Syntaxes:提供有关可以存储在属性中的信息种类的信息 Matching Rules:提供有关如何与属性值进行比较的信息 Matching Rule Uses:指出哪些属性类型可以与特定匹配规则结合使用 Attribute Types:定义对象标识符(OID)和一组可能引用给定属性的名称,并将该属性与语法和一组匹配规则相关联 Object Classes:定义命名的属性集合,并将它们分类为必需和可选属性的集合 Name Forms:为应包含在条目的RDN中的属性集定义规则 Content Rules:定义有关可以与条目结合使用的对象类和属性的其他约束 Structure Rule:定义规则以管理给定条目可能具有的从属条目的类型 属性是负责将信息存储在目录中的元素,Schema定义了可以在条目中使用属性的规则,这些属性可能具有的值的种类以及客户端如何与这些值进行交互。 客户端可以通过检索适当的子schema或子条目来了解服务器支持的schema元素。 该schema定义对象类。每个条目必须具有一个objectClass属性,其中包含在schema中定义的命名类。条目的类别的schema定义了该条目可以代表哪种对象-例如 个人,组织或领域。对象类定义还定义了必须包含值的属性列表和可能包含值的属性列表。

03 Internet Protocol Suite 阅读更多

0.1. 网络拓扑和数据流 0.2. 各层协议 0.2.1. 应用层 0.2.2. 传输层 0.2.3. 网络层 0.2.4. 链路层 0.1. 网络拓扑和数据流 数据流的定义是在一个简单的网络拓扑中的两台主机(A和B)通过各自路由之间的链路相连。每个主机上的应用程序都执行读取和写入操作,就好像这些进程通过某种数据管道直接相互连接。建立此管道后,由于底层的通信原理是在较低的协议层中实现的,因此每个进程都将隐藏大多数通信细节。 以此类推: 在传输层,通信表现为主机到主机,而无需了解应用程序数据结构和连接的路由器 在网络层,则在每个路由器处遍历各个网络边界 通过RFC 1122中对各层的描述来封装应用程序数据并向下传输。 0.2. 各层协议 0.2.1. 应用层 9P, Plan 9 from Bell Labs distributed file system protocol AFP, Apple Filing Protocol APPC, Advanced Program-to-Program Communication AMQP, Advanced Message Queuing Protocol Atom Publishing Protocol BEEP, Block Extensible Exchange Protocol Bitcoin BitTorrent CFDP, Coherent File Distribution Protocol CoAP, Constrained Application Protocol DDS, Data Distribution Service DeviceNet eDonkey ENRP, Endpoint Handlespace Redundancy Protocol FastTrack (KaZaa, Grokster, iMesh) Finger, User Information Protocol Freenet FTAM, File Transfer Access and Management Gopher, Gopher protocol HL7, Health Level Seven HTTP, Hypertext Transfer Protocol H.323, Packet-Based Multimedia Communications System IMAP, Internet Message Access Protocol IRC, Internet Relay Chat IPFS, InterPlanetary File System Kademlia LDAP, Lightweight Directory Access Protocol LPD, Line Printer Daemon Protocol MIME (S-MIME), Multipurpose Internet Mail Extensions and Secure MIME Modbus MQTT Protocol Netconf NFS, Network File System NIS, Network Information Service NNTP, Network News Transfer Protocol NTCIP, National Transportation Communications for Intelligent Transportation System Protocol NTP, Network Time Protocol OSCAR, AOL Instant Messenger Protocol POP, Post Office Protocol PNRP, Peer Name Resolution Protocol RDP, Remote Desktop Protocol RELP, Reliable Event Logging Protocol RFP, Remote Framebuffer Protocol Rlogin, Remote Login in UNIX Systems RPC, Remote Procedure Call RTMP, Real Time Messaging Protocol RTP, Real-time Transport Protocol RTPS, Real Time Publish Subscribe RTSP, Real Time Streaming Protocol SAP, Session Announcement Protocol SDP, Session Description Protocol SIP, Session Initiation Protocol SLP, Service Location Protocol SMB, Server Message Block SMTP, Simple Mail Transfer Protocol SNTP, Simple Network Time Protocol SSH, Secure Shell SSMS, Secure SMS Messaging Protocol TCAP, Transaction Capabilities Application Part TDS, Tabular Data Stream Tor (anonymity network) Tox TSP, Time Stamp Protocol VTP, Virtual Terminal Protocol Whois (and RWhois), Remote Directory Access Protocol WebDAV X.400, Message Handling Service Protocol X.500, Directory Access Protocol (DAP) XMPP, Extensible Messaging and Presence Protocol Z39.50 DNS, Domain Name Services 0.2.2. 传输层 ATP, AppleTalk Transaction Protocol CUDP, Cyclic UDP DCCP, Datagram Congestion Control Protocol FCP, Fibre Channel Protocol IL, IL Protocol MPTCP, Multipath TCP RDP, Reliable Data Protocol RUDP, Reliable User Datagram Protocol SCTP, Stream Control Transmission Protocol SPX, Sequenced Packet Exchange SST, Structured Stream Transport TCP, Transmission Control Protocol UDP, User Datagram Protocol UDP-Lite µTP, Micro Transport Protocol RSVP 0.2.3. 网络层 Anti-replay Gateway-to-Gateway Protocol Internet Control Message Protocol Internet Control Message Protocol for IPv6 Internet Group Management Protocol Internet Group Management Protocol with Access Control Internet Protocol IPv4 IPv6 Locator/Identifier Separation Protocol Seamoby SwIPe (protocol) ECN IPsec 0.2.4. 链路层 Address Resolution Protocol (ARP) Reverse Address Resolution Protocol (RARP) Neighbor Discovery Protocol (NDP) as ARP for IPv6 IS-IS (RFC 1142) is another link-state routing protocol Open Shortest Path First (OSPF) Tunnels(L2TP) PPP MAC (Ethernet Wi-Fi DSL ISDN FDDI)

02 浏览器缓存 阅读更多

0.1. 缓存 0.1.1. 定义 0.1.2. 作用 0.1.3. HTTP头中缓存相关字段及优先级 0.1.3.1. 强缓存 0.1.3.2. 协商缓存 0.2. 浏览器缓存机策略 0.2.1. from memory cache 0.2.2. from disk cache 0.2.3. Not Modified 0.2.4. Chrome缓存机制流程图 0.1. 缓存 0.1.1. 定义 缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。缓存保存位置的不同,可以分为: 浏览器缓存 代理缓存 0.1.2. 作用 当web缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。 这样带来的好处有: 缓解服务器端压力 降低带宽消耗 减少响应时间 0.1.3. HTTP头中缓存相关字段及优先级 0.1.3.1. 强缓存 资源一旦被强缓存,在缓存时间内,浏览器发起二次请求时会直接读取本地缓存,不与服务器进行通讯 强缓存时间过期后,浏览器会判断资源的响应头是否有Last-Modified和Etag字段,有的话执行协商缓存策略 字段 协议版本 优先级 缺陷 Expires: Data (HTTP/1.0) 低 返回的是服务器时间,可能与客户端时间有偏差 Cache-Control: max-age=N(second) (HTTP/1.1) 高 0.1.3.2. 协商缓存 如果响应头中有Etag字段,则客户端将If-None-Match:Etag的值添加到请求头发送给服务器 如果相应头中有Last-Modified字段,则客户端将If-Modified-Since:Last-Modified的值添加到请求头发送给服务器 由源服务器校验,如果资源未过期则返回304状态码,浏览器直接使用缓存,否则返回200OK状态码和新资源。 字段 协议版本 优先级 缺陷 Last-Modified: Data (HTTP/1.0) 低 只能精确到秒,1秒内多次修改的文件无法被识别 Etag: string (HTTP/1.1) 高 0.2. 浏览器缓存机策略 0.2.1. from memory cache 客户端不与服务器通讯,直接从内存中读取缓存。此时的数据保存在内存中,关闭浏览器后,数据被当作垃圾回收清空。 0.2.2. from disk cache 客户端不与服务器通讯,直接从磁盘中读取缓存,因为数据保存在磁盘中,就算关闭浏览器数据还是存在,下次打开只要数据不过期就可以直接读取。 0.2.3. Not Modified 客户端与服务器通讯,服务器验证资源是否需要更新,如果不需要更新服务器返回304状态码,然后客户端直接从缓存中读取数据。 0.2.4. Chrome缓存机制流程图

01 跨域 阅读更多

0.1. 跨域 0.2. CORS 0.2.1. 简单请求 0.2.1.1. Origin不在服务器允许的范围内 0.2.1.2. Origin在服务器允许的范围内 0.2.2. 非简单请求 0.3. 跨域中的Cookie 0.3.1. 导航到目标网站的Get请求 0.4. 跨域的解决方案 0.4.1. 客户端 0.4.1.1. 修改启动参数 0.4.1.2. 修改浏览器设置 0.4.1.3. 修改本地hosts文件 0.4.2. 服务端 0.4.2.1. 代理服务 0.4.2.1.1. 正向代理 0.4.2.1.2. 反向代理 0.4.2.2. 配置CORS 0.4.2.3. 修改应用代码 0.4.2.3.1. JSONP 0.4.2.3.2. window.postMessage 0.4.2.3.3. document.domain + Iframe 0.4.2.3.4. window.location.hash + Iframe 0.4.2.3.5. window.name + Iframe 0.4.2.4. 升级协议 0.1. 跨域 跨域,是指浏览器不能执行其它网站的脚本。它是由浏览器的同源(所谓同源,就是协议、域名、端口均相同)策略造成的,是浏览器对Javascript实施的安全限制。 比如,站点 http://domain-a.com 的某 HTML 页面通过 <img> 的 src 请求 http://domain-b.com/image.jpg,这就发起了一个跨域HTTP请求。 0.2. CORS 随着互联网的发展,同源策略严重影响了项目之间的连接,尤其是大项目,需要多个域名配合完成,因此W3C推出了CORS(Cross-origin resource sharing,跨域资源共享)。 CORS的基本思想就是使用额外的HTTP头部让浏览器与服务器进行沟通,从而决定是否接受跨域请求。跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器,让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。 网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。出于安全原因: 浏览器限制从脚本内发起的跨源HTTP请求 或者跨站请求可以正常发起,但是返回结果被浏览器拦截 例如,XMLHttpRequest和Fetch API遵循同源策略。这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。 跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。 CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题,只能查看浏览器的控制台以得知具体是哪里出现了错误。 浏览器在跨域访问时,会自动添加HTTP头信息,或者发起预检请求,用户对此毫无感知。因此是否支持跨域请求,关键在于服务器是否做了CORS配置,允许跨域访问。 浏览器将跨域请求分为两类:简单请求和非简单请求(预检请求)。对于这两种请求,浏览器的处理方式是不一样的。 0.2.1. 简单请求 简单请求的请求方法只能是: GET POST HEAD 简单请求的请求头只能是: Accept Accept-Language Content-Language Last-Event-ID Content-Type的值只能是: application/x-www-form-urlencoded multipart/form-data text/plain DPR Downlink Save-Data Viewport-Width Width 简单请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 简单请求中没有使用 ReadableStream 对象。 对于简单请求,浏览器采用先请求后判断的方式,即浏览器直接发出CORS请求,在请求头中增加Origin字段来向服务器说明,本次请求来自于哪个源(协议+域名+端口),服务器决定是否允许这个源的访问。 0.2.1.1. Origin不在服务器允许的范围内 返回一个正常的HTTP响应 浏览器判断响应头中是否包含Access-Control-Allow-Origin字段,如果没有,浏览器就知道服务器是不允许跨域访问的,就会抛出错误 0.2.1.2. Origin在服务器允许的范围内 服务器的HTTP响应中,就会包含如下字段: 字段名 字段值 描述 Access-Control-Allow-Credentials true 布尔值,表示是否允许发送Cookie,默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器 Access-Control-Allow-Headers Content-Type、Content-Length、Authorization、Accept、X-Request-With、Cookie 允许浏览器在CORS中发送的头信息 Access-Control-Allow-Methods GET、PUT、POST、DELETE、OPTIONS 允许浏览器在CORS中使用的方法 Access-Control-Allow-Origin 请求头中Origin字段的值或者* *表示接受任意域名的请求,如果服务端指定了具体的域名而非*,那么响应头中的Vary字段的值必须包含Origin。这将告诉客户端:服务器对不同的源站返回不同的内容 浏览器收到服务器返回的HTTP响应后,即可知道什么样的CORS请求是被允许的。 0.2.2. 非简单请求 对于非简单请求,浏览器采用预检请求,询问服务器是否支持跨域请求。 在正式的请求之前,浏览器会预先发送一个额外的OPTIONS请求,询问服务器当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 在这个OPTIONS请求中,包含: Origin Access-Control-Allow-Headers Access-Control-Allow-Methods 用于询问服务器支持跨域访问的请求头和请求方法。 服务器在收到OPTIONS预检请求后,检查上面三个字段,并作出响应。 字段名 字段值 描述 Access-Control-Allow-Credentials true 布尔值,表示是否允许发送Cookie,默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器 Access-Control-Allow-Headers Content-Type、Content-Length、Authorization、Accept、X-Request-With、Cookie 允许浏览器在CORS中发送的头信息 Access-Control-Allow-Methods GET、PUT、POST、DELETE、OPTIONS 允许浏览器在CORS中使用的方法 Access-Control-Allow-Origin 请求头中Origin字段的值或者* *表示接受任意域名的请求,如果服务端指定了具体的域名而非*,那么响应头中的Vary字段的值必须包含Origin。这将告诉客户端:服务器对不同的源站返回不同的内容 Access-Control-Max-Age 3600 用来指定本次预检请求的有效期,单位为秒。如有效期是3600秒,即允许缓存该条回应3600秒,在此期间,可直接发送正式请求,不用再发预检请求 0.3. 跨域中的Cookie cookie 属性: path domain expire HttpOnly Secure SameSite,一种新的防止跨站点请求伪造(CSRF)的HTTP安全特性 Chrome 80 默认将没有设置SameSite设置为SameSite=Lax。 SameSite的值 对应的描述 Strict 最为严格,完全禁止第三方Cookie,跨站点时,任何情况下都不会发送Cookie Lax 稍稍放宽,大多数情况也是不发送第三方Cookie,但是导航到目标网址的 Get 请求除外,见下面表格 None 网站可以选择显式关闭SameSite属性,将其设为None。前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效 0.3.1. 导航到目标网站的Get请求 导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。 请求类型 示例 正常情况 Lax 链接 <a href="..."></a> 发送 Cookie 发送 Cookie 预加载 <link rel="prerender" href="..."/> 发送 Cookie 发送 Cookie GET 表单 <form method="GET" action="..."> 发送 Cookie 发送 Cookie POST 表单 <form method="POST" action="..."> 发送 Cookie 不发送 iframe <iframe src="..."></iframe> 发送 Cookie 不发送 AJAX $.get("...") 发送 Cookie 不发送 Image <img src="..."> 发送 Cookie 不发送 在用户浏览器支持 SameSite 属性的情况下,设置了Strict或Lax以后,基本就杜绝了CSRF 攻击。 0.4. 跨域的解决方案 遇到跨域的报错,可以分别从客户端和服务端去解决。 0.4.1. 客户端 跨域的判断是在浏览器进行的,服务器只是根据客户端的请求做出正常的响应,服务端不对跨域做任何判断。因此如果禁用了浏览器的跨域检查,使浏览器不再对比Origin是否被服务器允许,即可发出正常的请求。 Chrome 80 版本开始,跨站访问时直接不携带cookie进行请求的发送,其他的浏览器可以正常访问。因此,定位到和浏览器版本有关,新增了一个cookie的属性,samesite。 0.4.1.1. 修改启动参数 需要所有客户都修改浏览器的设置,因此只在开发调试的过程中使用,如:给Chrome浏览器设置--disable-web-security启动参数。 0.4.1.2. 修改浏览器设置 谷歌浏览器地址栏输入:chrome://flags/ 找到:SameSite by default cookies、Cookies without SameSite must be secure 设置上面这两项设置成 Disable 0.4.1.3. 修改本地hosts文件 127.0.0.1.1:8888 api.test.com // 把本地请求转向到api接口 0.4.2. 服务端 0.4.2.1. 代理服务 增加代理服务器,和发起跨域请求的服务器放在同一个域名下,接口请求全走代理服务器,这样就变成了同源访问,不存在跨域访问,因此就不会存在跨域的问题。该方式中,所有发往目标服务器的数据,都会经过代理服务器,适用于同一个公司内部不同域名之间相互访问的情况。 使用此方式需注意代理服务器的性能,应与后端的目标服务器的性能相匹配,否则代理服务器会成为整个系统的性能瓶颈。 0.4.2.1.1. 正向代理 // webpack devServer: { port: 8000, proxy: { "/api": { target: "http://localhost:8080" } } } // Vue-cli 2.x proxyTable: { '/api': { target: 'http://localhost:8080', } } // Vue-cli 3.x vue.config.js devServer: { port: 8000, proxy: { "/api": { target: "http://localhost:8080" } } } // Parcel 2.x .proxyrc { "/api": { "target": "http://localhost:8080" } } 0.4.2.1.2. 反向代理 # Nginx server { listen 80; server_name local.test; location /api { proxy_pass http://localhost:8080; proxy_cookie_domain : localhost:8080 local.test; //当reseponse的set-cookie中设置domain时才需要配置此项, 用于修改set-cookie的domain指向 } location / { proxy_pass http://localhost:8000; } } 0.4.2.2. 配置CORS 在目标服务器上配置CORS响应头,这样浏览器经过对比判断之后,就可以发起正常的访问。 add_header Access-Control-Allow-Origin * add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS add_header Access-Control-Allow-Headers * 此方式的优点是不用修改应用代码,缺点是不能做细粒度的编程,从而做到细粒度的控制,如根据请求参数的不同而返回不同的结果。 0.4.2.3. 修改应用代码 由于是通过代码控制,因此可以实现细粒度的控制,在解决跨域问题的同时,可以满足复杂的业务需求。 app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", ctx.headers.origin); ctx.set("Access-Control-Allow-Credentials", true); ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS"); ctx.set( "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept" ); if (ctx.method === "OPTIONS") { ctx.status = 204; return; } await next(); }); // 或者使用现成的库 const cors = require("koa-cors"); app.use(cors()); 0.4.2.3.1. JSONP JSONP(JSON with Padding)是JSON的一种“使用模式”, 利用 script 标签没有跨域限制的特性, 解决跨域问题。 兼容性较好 只能发GET请求 <!-- HTML--> <script src="http://127.0.0.1:3000/list?callback=func"></script> <script> function func(res){ //处理res } </script>// javascript let express = require('express'), app = express() app.listen(8888, _=>{ console.log('ok') }) app.get('/list', (req, res)=>{ let { callback = function(){} } = req.query; let data = { code: 0, message: '返回数据' } res.send(`${callback}(${JSON.stringify(data)})`) }) 0.4.2.3.2. window.postMessage window.postMessage()方法可以安全地实现跨源通信。 应用场景: 页面和其打开的新窗口的数据传递 多窗口之间消息传递 页面与嵌套的 iframe 消息传递 otherWindow.postMessage(message, targetOrigin, [transfer]); otherWindow: 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。 message: 将要发送到其他 window 的数据。 targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件. transfer(可选) : 是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权 <!-- a页面 --> <iframe01-CORS和Cookie src="http://localhost:8080" frameborder="0" id="iframe" onload="load()" ></iframe> <script> function load() { //消息传递需要写在onload事件中 iframe.contentWindow.postMessage("消息", "http://localhost:8080"); //往b发送消息 window.onmessage = e => { console.log(e.data); //接收b返回的消息 }; } </script><!-- b页面 --> <div>hello</div> <script> window.onmessage = e => { //监听来自a的消息 console.log(e.data); e.source.postMessage('返回消息:' + e.data, e.origin); //往a返回消息 }; </script> 不同源的网站依旧可以通过iframe方式引入到html, 但需要一些额外处理才能获取想要传递的信息, 这就出现了下面三种基于iframe的跨域方式。 0.4.2.3.3. document.domain + Iframe 只能适用于二级域名相同的情况下,比如 a.test.com 和 b.test.com , 只需要给两个页面同时添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。 <!-- a.test.com --> <body> <iframe src="http://b.test.com/b.html" frameborder="0" onload="load()" id="frame" ></iframe> <script> document.domain = "test.com"; //设置主域 function load() { console.log(frame.contentWindow.qqq); } var aaa = 999; </script> </body><!-- b.test.com --> <body> hellob <script> document.domain = "test.com";//设置主域 var qqq = 100; console.log(window.parent.aaa) </script> </body> 0.4.2.3.4. window.location.hash + Iframe 原理就是通过 url 带 hash ,通过一个非跨域的中间页面来传递数据,但是hash是在地址栏中的, 能传递的数据有限, 例如chrome < 8k, ie < 2k。 <!-- a.html 和 b.html 是同源的,都是http://localhost:8000,而 c.html 是http://localhost:8080 --> <!-- a.html --> <iframe src="http://localhost:8080/hash/c.html#name1"></iframe> <script> console.log(location.hash); window.onhashchange = function() { console.log(location.hash); }; </script><!-- b.html --> <script> window.parent.parent.location.hash = location.hash; </script><!-- c.html --> <body></body> <script> console.log(location.hash); const iframe = document.createElement("iframe"); iframe.src = "http://localhost:8000/hash/b.html#name2"; document.body.appendChild(iframe); </script> 0.4.2.3.5. window.name + Iframe window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。 a跨域访问c, 之后再指向同源的b (注意,这个重新指向在onload事件中只要执行一次, 否则会造成无限循环), 只要b中没有修改window.name, window就会保持 c 页面中的值。 <!-- a.html 和 b.html 是同源的,都是http://localhost:8000,而 c.html 是http://localhost:8080 --> <!-- a.html --> <iframe src="http://localhost:8080/name/c.html" frameborder="0" onload="load()" id="iframe" ></iframe> <script> let first = true; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name function load() { if (first) { // 第1次onload(跨域页)成功后,切换到同域代理页面 //此处转向只要执行一次,否则相当于一直加载iframe,一直调用onload事件, 这样就是死循环 iframe.src = "http://localhost:8000/name/b.html"; first = false; } else { // 第2次onload(同域b.html页)成功后,读取同域window.name中数据 console.log(iframe.contentWindow.name); } } </script><!-- b.html --> <div></div><!-- c.html --> <script> window.name = "ccc"; </script> 可以在 http 返回头 添加X-Frame-Options: SAMEORIGIN 防止被别人添加至 iframe。 0.4.2.4. 升级协议 使用Websocket而不使用HTTP, 就不会有跨域的限制。 <script> let socket = new WebSocket("ws://localhost:8080"); socket.onopen = function() { socket.send("消息"); }; socket.onmessage = function(e) { console.log(e.data); }; </script>const WebSocket = require("ws"); const server = new WebSocket.Server({ port: 8080 }); server.on("connection", function(socket) { socket.on("message", function(data) { socket.send(data); }); });