本文将详细讲解前端安全的基础概念,包括XSS攻击、CSRF攻击、点击劫持等内容,适合初学者阅读。
1.什么是 XSS(跨站脚本攻击)?XSS 的类型有哪些(存储型、反射型、DOM 型)? 一、XSS(跨站脚本攻击,Cross-Site Scripting)的定义 XSS是一种注入式攻击 ,攻击者通过在网页中注入恶意JavaScript脚本(或HTML代码),当用户访问被注入的页面时,脚本会在用户浏览器中执行,从而窃取用户信息(如Cookie、账号密码)、劫持用户会话、伪造用户操作,甚至控制用户设备。
XSS的核心是“绕过浏览器的安全机制,让恶意脚本被当作合法代码执行” 。由于“Cross-Site Scripting”的缩写“CSS”与层叠样式表(CSS)冲突,因此简称“XSS”。
二、XSS的三种主要类型 根据恶意脚本的“注入方式”和“执行时机”,XSS可分为存储型、反射型、DOM型 ,核心区别在于脚本的“存储位置”和“触发流程”。
1. 存储型XSS(Persistent XSS,持久型) 原理 :攻击者将恶意脚本注入并存储到服务器数据库 (如评论区、用户资料、文章内容),当其他用户访问包含该恶意脚本的页面时,服务器会从数据库读取并返回恶意脚本,导致脚本在用户浏览器中执行。
攻击流程 :
攻击者在网站的用户输入区(如论坛评论框)提交包含恶意脚本的内容(如<script>窃取Cookie的代码</script>);
网站服务器未过滤直接将内容存入数据库;
其他用户访问该页面时,服务器从数据库读取内容并返回给用户浏览器;
浏览器解析页面时,恶意脚本被执行,窃取当前用户的信息并发送给攻击者。
典型场景 :
社交平台的评论区、留言板;
电商网站的商品评价;
博客或论坛的文章内容。
示例 : 攻击者在论坛评论中输入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 <script > alert (document .cookie )</script > ``` 若网站未过滤,该评论会被存入数据库。其他用户查看该评论时,脚本会执行并弹出当前用户的Cookie(实际攻击中会发送给攻击者服务器)。 #### 2. 反射型XSS(Non-Persistent XSS,非持久型) **原理**:恶意脚本**不存储在服务器**,而是通过URL参数、表单提交等方式“携带”到服务器,服务器未过滤处理,直接将脚本“反射”回页面并执行。 **攻击流程**: 1. 攻击者构造包含恶意脚本的URL(如`https://example.com/search?query=<script > 恶意代码</script > `); 2. 诱导用户点击该URL(如通过钓鱼邮件、聊天工具发送); 3. 服务器接收到URL中的参数,未过滤直接拼接到响应页面中并返回给用户; 4. 浏览器解析页面时,恶意脚本执行,窃取用户信息。 **典型场景**: - 搜索框(输入内容作为URL参数返回); - 跳转链接(URL参数包含跳转地址); - 表单提交后的数据回显(如登录失败时显示输入的账号)。 **特点**: - 恶意脚本仅在单次请求中存在,不持久存储; - 依赖用户主动点击恶意URL,攻击范围较存储型小。 #### 3. DOM型XSS(DOM-Based XSS) **原理**:恶意脚本的注入和执行**完全在客户端(浏览器)完成**,不经过服务器处理。攻击者利用页面中DOM操作的漏洞(如使用`innerHTML`、`eval`等危险API处理用户输入),让恶意脚本被执行。 **攻击流程**: 1. 攻击者构造包含恶意脚本的URL参数(如`https://example.com/#<script > 恶意代码</script > `,`#`后的内容不会发送到服务器); 2. 用户点击URL后,页面JS代码从URL参数、本地存储(localStorage)等位置读取数据,并通过`innerHTML`等方式插入到DOM中; 3. 由于未过滤,恶意脚本被直接插入DOM并执行,导致攻击。 **典型场景**: - 页面使用`location.hash`(URL锚点)获取参数并渲染; - 用`innerHTML`、`document.write`处理用户输入或URL参数; - 使用`eval()`执行动态拼接的字符串(含用户输入)。 **特点**: - 服务器全程未接触恶意脚本(仅客户端处理),因此服务器日志中无法记录攻击痕迹; - 本质是客户端JS代码的安全漏洞,与服务器逻辑无关。 ### 三、三种类型的核心区别 | 类型 | 恶意脚本存储位置 | 是否经过服务器 | 攻击持久性 | 典型场景 | |------------|------------------|----------------|------------|------------------------| | 存储型XSS | 服务器数据库 | 是 | 持久(长期有效) | 评论区、用户资料 | | 反射型XSS | 无(仅在URL中) | 是 | 非持久(单次请求) | 搜索框、URL参数回显 | | DOM型XSS | 无(客户端处理) | 否 | 非持久(单次请求) | 客户端DOM操作(如innerHTML) | ### 总结 XSS的本质是“**恶意脚本被浏览器当作合法代码执行**”,三种类型的核心差异在于脚本的注入和传播方式: - 存储型危害最大(脚本长期存在,影响所有访问者); - 反射型依赖用户点击(需诱导); - DOM型完全在客户端(服务器无感知)。 防范XSS的核心手段是**“输入过滤”**(对用户输入的特殊字符转义)和**“输出编码”**(渲染时将数据当作文本而非HTML解析)。 ## 2.如何防范 XSS 攻击?(如输入过滤、输出编码、CSP、HttpOnly Cookie) 防范XSS攻击需采用“多层防御”策略,结合输入验证、输出编码、安全策略等手段,从数据输入、处理到输出的全流程阻断恶意脚本的注入与执行。以下是核心防御措施: ### 一、输入过滤:清洗恶意输入 **原理**:对用户输入的所有数据(如表单提交、URL参数、评论内容)进行严格校验和过滤,移除或转义可能包含恶意脚本的字符/标签。 #### 具体做法 1. **白名单校验**:仅允许符合预期格式的输入(如手机号、邮箱用正则限制格式),拒绝不符合规则的内容。 例:限制用户名只能包含字母、数字和下划线: ```javascript const username = inputValue.trim(); if (!/^[a-zA-Z0-9_]{3,16}$/.test(username)) { alert('用户名格式无效'); return; } ``` 2. **过滤危险标签和属性**:移除HTML中可能执行脚本的标签(如`<script > `、` <iframe>`)和事件属性(如` onclick`、` onload`)。 - 推荐使用成熟库(如` DOMPurify `)而非手动正则(手动过滤易漏检变种攻击,如` <scr<script>ipt>`): ` `` javascript import DOMPurify from 'dompurify' ; const unsafeHtml = '<script>alert("xss") </script > <p > 安全内容</p > '; const safeHtml = DOMPurify.sanitize(unsafeHtml); // 结果:<p > 安全内容</p > (<script > 被移除) ``` ### 二、输出编码:确保数据“纯文本”渲染 **原理**:当需要将用户输入的数据输出到页面(如HTML、JS、CSS中)时,对数据进行“编码”,将特殊字符转换为“实体”或“转义序列”,使浏览器将其视为纯文本而非可执行代码。 #### 分场景编码(核心!不同场景编码规则不同) 1. **输出到HTML标签内**(最常见): 将`&`、`<`、`>`、`"`、`'`等特殊字符转换为HTML实体(如`<`→`< `,`>`→`> `)。 - 原生JS:用`textContent`替代`innerHTML`(`textContent`自动将内容视为文本): ```javascript // 安全:textContent会自动编码,<script > 不会被执行 document.getElementById('userContent').textContent = userInput; // 危险:innerHTML会解析HTML,若userInput含<script > 会执行 // document.getElementById('userContent').innerHTML = userInput; ``` - 模板引擎:使用自动编码功能(如Vue的` {{ }} `、React的JSX默认会转义): ```vue {{ }} <div > {{ userInput }} </div > ``` 2. **输出到HTML属性中**(如`href`、`src`): 除HTML实体编码外,额外限制属性值格式(如`href`只允许`http://`、`https://`开头)。 例:安全处理链接跳转: ```javascript const url = userInput; // 校验URL合法性,仅允许http/https协议 if (!/^https?:\/\//.test(url)) { url = 'https://default.com'; // 非法则替换为默认值 } // 编码后设置到href linkElement.href = encodeURI(url); ``` 3. **输出到JavaScript代码中**(如动态生成JS变量): 使用`JSON.stringify()`进行编码,避免注入恶意代码。 例:安全地将用户输入嵌入JS: ```javascript // 危险:直接拼接会导致XSS(如userInput为'"; alert(1); //) // const script = `const user = '${userInput}';`; // 安全:用JSON.stringify编码 const safeUserInput = JSON.stringify(userInput); const script = `const user = ${safeUserInput};`; // 会被解析为字符串字面量 ``` ### 三、内容安全策略(CSP,Content-Security-Policy) **原理**:通过HTTP响应头或`<meta > `标签,限制页面可加载的资源(脚本、样式、图片等)来源,禁止执行未授权的脚本(如内联脚本、`eval`),即使恶意脚本被注入,也无法执行。 #### 核心配置(通过HTTP头设置,优先级高于`<meta > `) ```http # 示例:严格的CSP策略(推荐) Content-Security-Policy: default-src 'self'; # 默认仅允许本域名资源 script-src 'self' https://trusted-cdn.com; # 仅允许本域名和可信CDN的脚本 style-src 'self' https://trusted-cdn.com; # 仅允许本域名和可信CDN的样式 img-src 'self' data: https://*.trusted-img.com; # 允许图片来源 object-src 'none'; # 禁止加载插件(如Flash,易被利用) frame-ancestors 'none'; # 禁止被嵌入iframe(防点击劫持) upgrade-insecure-requests; # 自动将HTTP请求转为HTTPS ``` #### 关键指令作用 - `script-src 'self' https://trusted.com`:仅允许本域名和`trusted.com`的脚本执行,**禁止内联脚本**(`<script > ... </script > `)和`eval()`,从根源阻断注入的恶意脚本; - `'unsafe-inline'`/`'unsafe-eval'`:若必须使用内联脚本(不推荐),可临时添加,但会降低安全性; - `report-uri /csp-report`:配置违规报告地址,记录CSP拦截的攻击尝试。 ### 四、安全的Cookie设置:防止Cookie被盗 XSS攻击的主要目的之一是窃取用户Cookie(含会话ID),通过设置Cookie的安全属性,即使XSS成功,也无法获取关键Cookie。 #### 核心属性 1. **HttpOnly**:禁止JS通过`document.cookie`访问Cookie,仅允许服务器通过HTTP头读取/设置。 ```http Set-Cookie: sessionId=abc123; HttpOnly; ... ``` (即使页面有XSS,`document.cookie`也无法获取`sessionId`) 2. **Secure**:仅在HTTPS连接中传输Cookie,避免HTTP传输时被窃听。 ```http Set-Cookie: sessionId=abc123; Secure; ... ``` 3. **SameSite**:限制Cookie仅在同站点请求中发送(防跨站请求伪造CSRF,间接增强XSS防护)。 ```http Set-Cookie: sessionId=abc123; SameSite=Strict; ... ``` (`Strict`:完全禁止跨站发送;`Lax`:允许部分跨站请求如GET) ### 五、其他防御措施 1. **避免使用危险的DOM API**: 禁用或谨慎使用会解析HTML的API,如`innerHTML`、`document.write()`、`eval()`、`setTimeout(string)`,改用安全替代(`textContent`、`setTimeout(function)`)。 2. **利用框架自带防护**: 现代前端框架(Vue、React、Angular)默认对文本内容进行转义,避免直接使用`v-html`(Vue)、`dangerouslySetInnerHTML`(React)等“危险指令”,如需使用,必须先过滤内容。 3. **限制输入长度**: 对评论、用户名等输入设置合理长度限制(如评论最多500字),减少恶意脚本的注入空间。 4. **定期安全审计**: 使用工具(如`npm audit`、OWASP ZAP)扫描项目依赖和页面,检测潜在的XSS漏洞。 ### 总结 防范XSS的核心是“**不信任任何用户输入**”,通过多层防御形成闭环: - 输入时过滤清洗(第一道防线); - 输出时按场景编码(核心防线); - 用CSP限制脚本执行(兜底防线); - 保护Cookie(减少攻击收益)。 单一措施难以完全防御(如过滤可能漏检变种攻击),需结合多种手段,针对存储型、反射型、DOM型XSS的不同特点调整策略。 ## 3.什么是 CSRF(跨站请求伪造)?CSRF 的攻击原理是什么? ### 一、CSRF(跨站请求伪造,Cross-Site Request Forgery)的定义 CSRF是一种**利用用户已有的身份认证信息(如Cookie、会话Token),伪造用户主动操作**的攻击方式。攻击者通过诱导用户在“已登录目标网站”的情况下访问恶意页面或点击恶意链接,触发向目标网站的非预期请求(如转账、修改密码、提交表单),由于浏览器会自动携带目标网站的身份凭证,目标网站会误以为请求是用户主动发起的,从而执行恶意操作。 CSRF的核心是“**借用户之身份,行攻击者之意图**”,其本质是“跨域伪造合法请求”,与XSS(注入脚本在目标网站执行)的核心逻辑完全不同(XSS是“在目标网站内执行恶意代码”,CSRF是“跨域伪造请求利用身份”)。 ### 二、CSRF的攻击原理(核心前提+攻击流程) CSRF的成功依赖一个关键**前提**:用户已登录目标网站(如银行、社交平台),且身份凭证(如Cookie)仍在有效期内(会话未过期)。在此前提下,攻击流程可拆解为5个步骤,结合“银行转账”的场景更易理解: #### 场景假设 - **目标网站A**:某银行网站,提供转账功能,转账接口为 `POST /api/transfer`,需携带参数 `to`(收款账户)、`amount`(金额),并通过Cookie验证用户身份(用户登录后,银行会设置 `sessionId=xxx` 的Cookie)。 - **恶意网站B**:攻击者搭建的网站,包含触发恶意请求的代码。 - **用户**:已登录银行网站A(Cookie未过期),且在未退出A的情况下访问了B。 #### 攻击流程(以“诱导转账”为例) 1. **用户登录目标网站A,获取身份凭证** 用户在浏览器中登录银行网站A,A网站验证账号密码后,向用户浏览器设置身份Cookie(如 `sessionId=abc123`),用于后续请求的身份验证(只要Cookie未过期,用户无需重复登录)。 2. **攻击者构造恶意请求** 攻击者分析银行网站A的转账接口规则(如通过抓包得知 `POST /api/transfer` 需 `to=攻击者账户&amount=1000`),并在恶意网站B中构造触发该请求的代码。常见构造方式有两种: - **方式1:自动提交的表单(POST请求)** 恶意网站B中嵌入一个隐藏表单,表单action指向银行A的转账接口,参数预设为“转账给攻击者”,并通过JS自动提交: ```html <form id ="csrfForm" action ="https://bank.com/api/transfer" method ="POST" > <input type ="hidden" name ="to" value ="attacker123" > <input type ="hidden" name ="amount" value ="1000" > </form > <script > document .getElementById ('csrfForm' ).submit (); </script > ``` - **方式2:img标签(GET请求,若接口支持GET)** 若银行A的敏感接口(如转账)错误地使用GET请求(非规范设计),攻击者可通过 `<img > ` 标签的 `src` 触发请求(浏览器加载img时会自动发送GET请求,并携带银行A的Cookie): ```html <img src ="https://bank.com/api/transfer?to=attacker123&amount=1000" style ="display:none" > ``` 3. **用户在未退出A的情况下访问恶意网站B** 攻击者通过钓鱼邮件(如“点击领取红包”)、聊天链接等方式,诱导用户在“未退出银行A”的情况下访问恶意网站B。此时用户浏览器中,银行A的身份Cookie仍有效。 4. **浏览器自动携带目标网站的身份凭证,发送恶意请求** 当用户访问B网站时,B中的恶意代码(表单自动提交、img加载)会触发向银行A的跨域请求(从 `malicious.com` 到 `bank.com`)。根据浏览器的“同源策略”和“Cookie携带规则”: - 跨域请求时,浏览器会自动携带“目标网站域名对应的Cookie”(即银行A的 `sessionId=abc123`),无论请求来源是哪个网站。 5. **目标网站验证身份有效,执行恶意操作** 银行A接收到请求后,检查请求携带的 `sessionId` Cookie: - 若Cookie有效(用户仍处于登录状态),银行A会认为请求是“用户主动发起的合法操作”,从而执行转账逻辑,将用户账户的1000元转给攻击者。 - 用户全程不知情,直到后续查看账户明细才发现异常。 ### 三、CSRF的核心特点(与XSS的关键区别) 为避免混淆,需明确CSRF与XSS的核心差异,二者的攻击逻辑完全不同: | 对比维度 | CSRF(跨站请求伪造) | XSS(跨站脚本攻击) | |----------------|---------------------------------------------|---------------------------------------------| | 核心逻辑 | 跨域伪造请求,利用用户已有的身份凭证(Cookie) | 向目标网站注入恶意脚本,在目标网站内执行 | | 代码执行位置 | 恶意请求发送到目标网站,脚本不在目标网站执行 | 恶意脚本在目标网站的用户浏览器中执行 | | 依赖条件 | 1. 用户已登录目标网站;2. 浏览器自动携带Cookie | 1. 目标网站存在输入未过滤的漏洞;2. 脚本被注入并执行 | | 用户感知度 | 通常被动触发(不知情) | 可能被动触发(如浏览注入脚本的页面) | ### 四、CSRF的典型攻击场景 CSRF常针对“无需用户二次验证”的敏感操作,常见场景包括: - 金融操作:转账、充值、绑定银行卡; - 账户设置:修改密码、修改绑定手机号、更改收货地址; - 内容提交:发布评论、点赞、关注(如恶意关注攻击者账号); - 数据操作:删除数据、导出敏感信息(如导出用户通讯录)。 ### 总结 CSRF的本质是“**借用户身份,发跨域请求**”,其攻击成功的关键依赖两个前提: 1. 用户已登录目标网站,身份凭证(Cookie)未过期; 2. 目标网站仅通过Cookie验证身份,未对请求的“合法性来源”做额外校验。 理解CSRF的原理后,才能针对性设计防御方案(如添加CSRF Token、验证Referer、使用SameSite Cookie等),避免因“信任Cookie而忽略请求来源”导致的安全漏洞。 ## 4.如何防范 CSRF 攻击?(如 Token 验证、SameSite Cookie、Referer 验证) 防范CSRF攻击的核心思路是**确保请求是用户“主动且合法”发起的**,通过验证请求的“来源合法性”“身份唯一性”等手段,阻断跨域伪造的请求。以下是经过实践验证的有效防御措施,建议结合使用形成多层防护: ### 一、CSRF Token验证(最主流、最可靠的方案) **原理**:服务器为每个用户会话生成一个随机、唯一的令牌(CSRF Token),并在用户发起敏感请求时(如转账、修改信息)要求客户端提交该Token。服务器通过验证Token的有效性(是否与会话中存储的一致),判断请求是否为用户主动发起。 #### 实现流程 1. **生成Token**:用户登录后,服务器为当前会话(Session)生成一个随机字符串(如`csrfToken=abc123xyz`),存储在服务器的Session中(或与用户身份绑定)。 2. **前端获取Token**:服务器在返回页面(如表单页)时,将Token嵌入到页面中(如表单隐藏字段、JS变量、响应头)。 ```html <form action ="/api/transfer" method ="POST" > <input type ="hidden" name ="csrfToken" value ="abc123xyz" > <input type ="text" name ="to" placeholder ="收款账户" > <input type ="text" name ="amount" placeholder ="金额" > <button type ="submit" > 转账</button > </form > ``` (对于AJAX请求,可将Token放在请求头中,如`X-CSRF-Token: abc123xyz`) 3. **客户端提交Token**:用户提交请求时,前端自动携带Token(表单提交时随字段发送,AJAX请求随头发送)。 4. **服务器验证Token**:服务器接收请求后,从请求中提取Token,与当前会话中存储的Token对比: - 若一致,说明请求是合法用户在当前会话中主动发起,允许执行; - 若不一致或Token不存在,拒绝请求(返回403 Forbidden)。 #### 关键注意事项 - **Token随机性**:必须使用强随机算法生成(如UUID),避免可预测性(防止攻击者猜测); - **Token时效性**:可设置短期有效期(如1小时),或每次使用后刷新Token(减少被盗用风险); - **存储安全**:Token需与用户会话绑定(如存在服务器Session或Redis中),避免全局共享; - **传输安全**:通过HTTPS传输Token,防止中间人劫持。 ### 二、SameSite Cookie属性(浏览器层面的防御) **原理**:通过设置Cookie的`SameSite`属性,限制Cookie在跨域请求中的发送行为,从根源上阻止攻击者利用用户Cookie发起跨站请求。 #### 核心配置 `SameSite`属性有三个可选值,通过`Set-Cookie`头设置: ```http # 示例:设置SameSite=Strict Set-Cookie: sessionId=abc123; SameSite=Strict; HttpOnly; Secure ``` - **SameSite=Strict**: 完全禁止Cookie在跨域请求中发送。只有当请求的“来源域名”与Cookie的“所属域名”完全一致时(同站请求),才会携带Cookie。 - 优点:安全性最高,彻底阻断跨站请求携带Cookie; - 缺点:可能影响正常跨站交互(如从A网站链接跳转至B网站,B网站的Cookie不会被携带,可能需要重新登录)。 - **SameSite=Lax**(推荐,平衡安全与体验): 允许部分“安全的跨站请求”携带Cookie,仅在以下场景发送Cookie: - 导航到目标域名的GET请求(如点击链接跳转); - 预加载请求(如`<link rel ="prefetch" > `)。 禁止“非安全的跨站请求”(如POST表单、AJAX请求)携带Cookie。 - 优点:既防范了恶意的跨站POST请求(如CSRF核心攻击方式),又不影响正常的跨站跳转体验; - 适用场景:绝大多数网站,是目前浏览器的默认值(现代浏览器如Chrome、Firefox已默认启用)。 - **SameSite=None**: 允许Cookie在所有跨域请求中发送(与不设置SameSite效果一致),但必须同时设置`Secure`属性(仅HTTPS传输),否则无效。 - 适用场景:需要跨域共享Cookie的合法业务(如单点登录),非特殊情况不建议使用。 #### 优势 - 配置简单(仅需服务器修改Cookie响应头),无需前端配合; - 由浏览器自动执行,攻击成本高(攻击者无法绕过浏览器的SameSite限制)。 ### 三、Referer/Origin验证(辅助防御手段) **原理**:通过检查请求头中的`Referer`或`Origin`字段,验证请求的“来源域名”是否为可信域名(如自身域名),若来源不可信则拒绝请求。 #### 字段说明 - **Referer**:记录请求的完整来源URL(如`https://example.com/form`),适用于所有请求; - **Origin**:仅记录来源域名(如`https://example.com`),不包含路径,适用于跨域请求(如POST、PUT),安全性略高于Referer。 #### 实现方式 服务器在处理敏感请求时,提取`Referer`或`Origin`头,检查是否属于可信域名列表(如`example.com`、`api.example.com`): ```javascript // 伪代码:验证Referer/Origin function validateRequest(req) { const trustedDomains = ['https://example.com', 'https://api.example.com']; const referer = req.headers.referer; const origin = req.headers.origin; // 优先验证Origin(存在时) if (origin) { return trustedDomains.includes(origin); } // 其次验证Referer(提取域名部分) if (referer) { const refererDomain = new URL(referer).origin; return trustedDomains.includes(refererDomain); } // 无来源信息,视为不可信 return false; } ``` #### 局限性(需配合其他措施) - `Referer`可能被浏览器或代理服务器移除(如用户隐私设置禁止发送),导致误判; - `Referer`可被部分场景伪造(虽然难度高,但存在风险); - 不适用于“无来源”的请求(如直接在浏览器地址栏输入URL的GET请求)。 ### 四、其他防御措施 1. **敏感操作需二次验证**: 对于高风险操作(如转账、修改密码),强制要求用户输入验证码、短信验证码或密码,即使请求被伪造,攻击者也无法完成验证。 2. **使用POST请求而非GET**: 虽然POST请求也可被CSRF攻击伪造,但GET请求更容易通过`<img > `、`<script > `等标签触发(浏览器加载资源时自动发送GET请求),而POST需要构造表单并提交,攻击成本更高。**核心原则**:敏感操作必须用POST,且GET请求不应修改服务器状态(符合HTTP语义)。 3. **限制Cookie有效期**: 缩短用户会话Cookie的有效期(如2小时),减少攻击者可利用的时间窗口;同时支持“自动登出”(如30分钟无操作则登出)。 ### 总结 防范CSRF攻击需采用“多层防御”策略,推荐组合: - **核心**:CSRF Token验证(确保请求唯一性) + SameSite=Lax Cookie(浏览器层面阻断跨域携带); - **辅助**:Referer/Origin验证(过滤不可信来源) + 敏感操作二次验证(最后防线)。 单一措施(如仅依赖Referer)存在漏洞,多层组合可大幅降低风险。现代网站中,CSRF Token与SameSite Cookie的组合已成为行业标准,既能有效防御攻击,又能平衡用户体验。 ## 5.什么是 SameSite Cookie?SameSite 的取值(Strict、Lax、None)有什么区别? ### 一、SameSite Cookie 的定义 SameSite 是 Cookie 的一个安全属性,用于**限制 Cookie 在跨域请求中的发送行为**,由浏览器自动执行。其核心作用是**防止跨站请求伪造(CSRF)攻击**——通过控制 Cookie 仅在“可信的同站请求”中携带,避免攻击者利用用户的登录状态(Cookie)发起跨域恶意请求。 “同站”的判断基于域名的“公共后缀”(如 ` .com `、` .co .uk `),例如: - ` a.example .com ` 和 ` b.example .com ` 属于**同站**(共享公共后缀 ` .example .com `); - ` example.com ` 和 ` example.net ` 属于**跨站**(公共后缀不同)。 ### 二、SameSite 的三个取值及区别 SameSite 有三个可选值:` Strict `、` Lax `、` None `,核心区别在于**跨域请求时是否携带 Cookie**,以及**允许哪些场景的跨站请求携带 Cookie**。 #### 1. SameSite=Strict(严格模式) - **行为**:**完全禁止 Cookie 在跨站请求中发送**。只有当请求的“来源域名”与 Cookie 的“所属域名”完全一致(同站请求)时,Cookie 才会被携带。 - **示例**: - 若 ` bank.com ` 的 Cookie 设置为 ` SameSite =Strict `: - 用户从 ` bank.com ` 内部页面发起的请求(同站),Cookie 会正常携带; - 用户从 ` malicious.com `(跨站)发起的请求,或从 ` news.com ` 点击链接跳转至 ` bank.com ` 时,Cookie 不会被携带。 - **优点**:安全性最高,彻底阻断跨站请求利用 Cookie 的可能; - **缺点**:影响正常跨站交互体验(如用户从其他网站点击链接进入目标网站时,因 Cookie 未携带,可能需要重新登录); - **适用场景**:安全性要求极高的网站(如银行、支付平台),且不依赖跨站跳转的业务。 #### 2. SameSite=Lax(宽松模式,默认值) - **行为**:**允许部分“安全的跨站 GET 请求”携带 Cookie**,但禁止“非安全的跨站请求”(如 POST 表单、AJAX)携带 Cookie。 允许的跨站场景(仅 GET 请求): - 点击链接跳转(如 ` <a href="https://bank.com/account" >`); - 预加载请求(如 ` <link rel="prefetch" href="https://bank.com/data" >`); - 表单的 GET 提交(如 ` <form method="GET" action="https://bank.com/search" >`)。 禁止的跨站场景: - POST 表单提交(如跨站的 ` <form method="POST" >`); - AJAX/fetch 请求(无论 GET/POST); - 嵌入的资源请求(如 ` <img src="https://bank.com/api/transfer" >`)。 - **示例**: - 若 ` shop.com ` 的 Cookie 设置为 ` SameSite =Lax `: - 用户从 ` blog.com ` 点击链接进入 ` shop.com `(GET 跳转),Cookie 会携带,用户无需重新登录; - 攻击者在 ` malicious.com ` 用 POST 表单伪造 ` shop.com ` 的下单请求,Cookie 不会携带,请求被拒绝。 - **优点**:平衡安全性和用户体验——既防范了 CSRF 核心攻击手段(跨站 POST 请求),又不影响正常的跨站跳转; - **适用场景**:绝大多数网站(如电商、社交平台),是现代浏览器(Chrome、Firefox 等)的默认值。 #### 3. SameSite=None(无限制模式) - **行为**:**允许 Cookie 在所有跨域请求中发送**(与不设置 SameSite 属性的旧行为一致),但**必须同时设置 ` Secure ` 属性**(即 Cookie 仅能通过 HTTPS 传输),否则无效(浏览器会忽略 ` None ` 并视为 ` Lax `)。 - **示例**: - 若 ` sso.example .com ` 的 Cookie 设置为 ` SameSite =None ; Secure `: - 跨站的 ` app1.example .com `、` app2.example .net ` 发起的请求,均可携带该 Cookie(用于单点登录等跨域共享身份的场景)。 - **优点**:支持跨域共享 Cookie,满足单点登录(SSO)、跨域数据同步等业务需求; - **缺点**:安全性最低,需依赖 HTTPS 且需其他防御措施(如 CSRF Token)配合; - **适用场景**:必须跨域共享 Cookie 的业务(如企业内部多系统单点登录),且需确保使用 HTTPS。 ### 三、三者核心区别对比表 | 取值 | 跨站请求是否携带 Cookie? | 允许的跨站场景 | 安全性 | 适用场景 | |------------|---------------------------------|-----------------------------------------|--------|------------------------------| | Strict | 完全不允许 | 仅同站请求 | 最高 | 高安全需求(银行、支付) | | Lax(默认)| 允许部分安全的跨站 GET 请求 | 链接跳转、GET 表单、预加载 | 中 | 绝大多数网站(平衡安全与体验)| | None | 允许所有跨站请求(需配合 Secure)| 所有跨域请求(POST、AJAX 等) | 最低 | 跨域共享 Cookie(如 SSO) | ### 总结 SameSite Cookie 是浏览器层面防范 CSRF 的核心机制: - 优先使用默认的 ` Lax `,平衡安全与体验; - 高安全场景用 ` Strict `,但需接受跨站跳转的登录状态丢失; - 跨域共享 Cookie 时用 ` None `,但必须搭配 ` Secure ` 和 HTTPS,并补充其他安全措施(如 CSRF Token)。 现代浏览器已默认启用 ` Lax `,大幅降低了 CSRF 攻击的成功率,但仍需结合其他防御手段(如 Token 验证)形成完整防护。 ## 6.什么是 CSP(内容安全策略)?CSP 的作用是什么?如何配置 CSP? ### 一、CSP(内容安全策略,Content-Security-Policy)的定义 CSP 是一种由浏览器强制执行的**安全机制**,通过开发者配置的“资源加载规则”,限制网页可加载的资源(如脚本、样式、图片、iframe 等)来源,禁止执行未授权的代码(如内联脚本、` eval `),从根源上阻断 XSS(跨站脚本攻击)、点击劫持等注入式攻击。 CSP 的核心逻辑是“**白名单机制**”:仅允许加载/执行开发者明确指定的“可信资源”,任何不在白名单内的资源或代码都会被浏览器拒绝,即使攻击者注入了恶意脚本,也无法执行。 ### 二、CSP 的核心作用 CSP 主要解决“恶意代码注入”和“不可信资源加载”两大安全问题,具体作用可拆解为以下4点: #### 1. 阻断 XSS 攻击(核心作用) XSS 攻击的关键是“恶意脚本被浏览器执行”,而 CSP 通过以下规则直接阻断: - 禁止内联脚本(如 ` <script>alert (1 )</script > `、`onclick="恶意代码"`); - 禁止 `eval()`、`setTimeout("字符串")` 等动态执行代码的方式; - 限制脚本来源(仅允许从可信域名如自身域名、官方 CDN 加载脚本)。 即使攻击者通过输入漏洞注入了恶意脚本,只要脚本来源不在 CSP 白名单内,或属于内联脚本,浏览器会直接拦截,无法执行。 #### 2. 限制资源加载来源 防止网页加载不可信的资源(如恶意图片、钓鱼 iframe、劫持样式),仅允许从指定域名加载资源: - 限制图片来源(如仅允许自身域名和 `https://img.example.com`); - 禁止加载未知的 iframe(防止点击劫持,如 `object-src 'none'` 禁止插件); - 限制样式来源(防止恶意样式篡改页面,如 `style-src 'self'`)。 #### 3. 防止点击劫持(Clickjacking) 通过 `frame-ancestors` 指令限制网页被嵌入 iframe 的来源,禁止网页被恶意网站嵌套: - 配置 `frame-ancestors 'none'`:禁止任何网站将当前页面嵌入 iframe; - 配置 `frame-ancestors 'self' https://trusted.com`:仅允许自身和可信网站嵌入。 #### 4. 违规报告与监控 CSP 支持“违规报告”功能:当浏览器拒绝某资源/代码时,会自动向开发者指定的地址发送“违规日志”,帮助开发者: - 发现未被覆盖的安全漏洞(如遗漏的可信资源); - 监控潜在的攻击尝试(如频繁的恶意脚本注入)。 ### 三、CSP 的配置方法 CSP 主要通过两种方式配置:**HTTP 响应头**(优先级高,推荐)和 **HTML meta 标签**(适合无法修改服务器配置的场景)。配置的核心是“指令+值”,指令定义“限制的资源类型”,值定义“允许的来源”。 #### 1. 常用 CSP 指令及含义 先理解核心指令,再组合配置: | 指令 | 作用 | 常用值示例 | |---------------------|---------------------------------------|-----------------------------------| | `default-src` | 所有资源的默认加载规则(未单独配置的资源遵循此规则) | `'self'`(自身域名)、`https://cdn.example.com` | | `script-src` | 限制脚本(JS)的加载来源 | `'self'`、`https://js.cdn.com`、`'nonce-xxx'`(允许特定内联脚本) | | `style-src` | 限制样式(CSS)的加载来源 | `'self'`、`https://css.cdn.com` | | `img-src` | 限制图片的加载来源 | `'self'`、`data:`(允许 base64 图片)、`https://img.cdn.com` | | `frame-src` | 限制 iframe 的加载来源(已替代 `child-src`) | `'self'`、`https://iframe.trusted.com` | | `frame-ancestors` | 限制当前页面被嵌入 iframe 的来源 | `'self'`、`'none'`(禁止嵌入) | | `object-src` | 限制插件(如 Flash、Java)的加载来源 | `'none'`(推荐,现代网站几乎不用插件) | | `upgrade-insecure-requests` | 自动将 HTTP 请求转为 HTTPS(防止混合内容) | 无值,仅需指令本身 | | `report-uri`/`report-to` | 配置违规报告地址(浏览器拒绝资源时发送日志) | `/csp-report`(后端接收接口) | #### 2. 配置方式1:HTTP 响应头(推荐) 通过服务器在响应头中添加 `Content-Security-Policy`,所有页面都会遵循此规则(优先级高于 meta 标签)。 **示例(Nginx 配置)**: 在 Nginx 配置文件中添加响应头,限制资源仅从自身域名和可信 CDN 加载: ```nginx server { listen 443 ssl; server_name example.com; # 添加 CSP 响应头 add_header Content-Security-Policy " default-src 'self'; script-src 'self' https://js.cdn.com 'nonce-{{$nonce}}'; # 允许自身、可信CDN,及带nonce的内联脚本 style-src 'self' https://css.cdn.com; img-src 'self' data: https://img.cdn.com; frame-ancestors 'self'; # 仅允许自身嵌入iframe object-src 'none'; # 禁止插件 upgrade-insecure-requests; # HTTP自动转HTTPS report-uri /csp-report; # 违规报告地址 "; # 其他配置... } ``` **说明**: - `'self'` 表示“当前页面的域名”(如 `example.com`),必须加单引号; - `'nonce-xxx'` 用于允许“必要的内联脚本”(如初始化代码):服务器每次生成随机 `nonce`,内联脚本需携带相同 `nonce` 才会被允许(如 `<script nonce ="xxx" > ...</script > `),避免攻击者伪造; - 指令间用分号分隔,换行仅为可读性(实际配置可写一行)。 #### 3. 配置方式2:HTML meta 标签(备用) 若无法修改服务器配置(如静态页面、第三方平台),可在 HTML 的 `<head > ` 中添加 `<meta > ` 标签配置 CSP: **示例**: ```html <head > <meta http-equiv ="Content-Security-Policy" content =" default-src 'self'; script-src 'self' https://js.cdn.com; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; upgrade-insecure-requests " ></head > ``` **局限性**: - 不支持 `report-uri`(需用更复杂的 `report-to` 指令); - 若服务器同时配置了 CSP 响应头,meta 标签的规则会被覆盖。 #### 4. 测试模式:Content-Security-Policy-Report-Only 首次配置 CSP 时,建议先使用“报告模式”(仅报告违规,不阻断资源),避免因规则错误导致网站功能异常: ```http # 报告模式:仅发送违规日志,不拒绝资源 Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-report; ``` 通过查看 `/csp-report` 接口接收的日志,修正规则(如补充遗漏的可信 CDN),确认无误后再切换为正式的 `Content-Security-Policy`。 ### 四、配置注意事项 1. **避免过度宽松**:不建议使用 `default-src '*'`(允许所有资源),或 `script-src 'unsafe-inline'`(允许所有内联脚本),会失去 CSP 的安全意义; 2. **处理内联脚本/样式**:若必须使用内联脚本(如初始化数据),优先用 `'nonce-xxx'` 或 `'sha256-哈希值'`(计算脚本的哈希,仅允许该哈希的内联脚本),避免 `'unsafe-inline'`; 3. **兼容旧浏览器**:部分旧浏览器(如 IE11)不支持 CSP,需配合其他防御措施(如输入过滤); 4. **HTTPS 优先**:配置 `upgrade-insecure-requests` 并强制使用 HTTPS,防止混合内容(HTTP 资源加载到 HTTPS 页面)被拦截。 ### 总结 CSP 是前端安全的“兜底防线”,核心价值是**通过白名单限制资源加载和代码执行**,即使输入过滤存在漏洞,也能阻断恶意脚本。配置时需: 1. 优先用 HTTP 响应头配置,覆盖所有页面; 2. 从严格规则开始,用报告模式测试,逐步调整; 3. 结合其他安全措施(如输入过滤、XSS 编码),形成多层防御。 ## 7.什么是 HTTPOnly Cookie 和 Secure Cookie?它们的作用是什么? ### 一、HTTPOnly Cookie(防脚本访问的Cookie) **定义**:HTTPOnly 是 Cookie 的一个安全属性,通过服务器在设置 Cookie 时添加 `HttpOnly` 标记,**禁止客户端 JavaScript 代码通过 `document.cookie` 访问该 Cookie**。 #### 核心作用:防范 XSS 攻击 XSS(跨站脚本攻击)的典型目的之一是通过注入恶意脚本(如 `<script > alert (document .cookie )</script > `)窃取用户的 Cookie(含会话 ID、登录凭证等敏感信息)。而 HTTPOnly Cookie 直接阻断了这一途径: - 即使页面存在 XSS 漏洞,恶意脚本也无法通过 `document.cookie` 读取到标记为 `HttpOnly` 的 Cookie; - 该 Cookie 仅会在浏览器向服务器发送 HTTP 请求时自动携带(如请求头中的 `Cookie` 字段),不影响服务器对身份的验证。 #### 示例(设置方式) 服务器通过 `Set-Cookie` 响应头设置 HTTPOnly Cookie: ```http // 格式:Set-Cookie: [键]=[值]; HttpOnly; ... Set-Cookie: sessionId=abc123xyz; HttpOnly; Path=/; Max-Age=3600 ``` 此时,前端 JS 执行 `console.log(document.cookie)` 时,**不会包含 `sessionId` 这个 Cookie**,但浏览器向服务器发送请求时仍会自动携带它。 ### 二、Secure Cookie(加密传输的Cookie) **定义**:Secure 是 Cookie 的另一个安全属性,通过服务器添加 `Secure` 标记,**强制该 Cookie 仅能通过 HTTPS 加密连接传输**,禁止在 HTTP 明文连接中发送。 #### 核心作用:防止传输过程中的窃听 Cookie 中常包含用户会话 ID、登录状态等敏感信息,若通过 HTTP 明文传输,可能被中间人(如网络嗅探工具)截获,导致身份被盗用。Secure Cookie 确保: - 只有当浏览器与服务器之间的连接是 HTTPS(加密)时,Cookie 才会被发送; - 若页面通过 HTTP 访问,即使 Cookie 存在于浏览器中,也不会被携带到请求中,避免明文泄露。 #### 示例(设置方式) ```http // 格式:Set-Cookie: [键]=[值]; Secure; ... Set-Cookie: token=def456; Secure; HttpOnly; Path=/; SameSite=Lax ``` 此时,该 `token` Cookie 仅会在 `https://example.com` 的请求中携带,若访问 `http://example.com`(HTTP),则不会发送。 ### 三、两者的区别与协同作用 | 属性 | 核心限制 | 解决的安全问题 | 注意事项 | |------------|-----------------------------------|------------------------------|-----------------------------------| | HTTPOnly | 禁止 JS 访问 Cookie | XSS 攻击导致的 Cookie 窃取 | 不影响 Cookie 在 HTTP 请求中携带 | | Secure | 仅允许 HTTPS 传输 Cookie | HTTP 明文传输导致的窃听 | 需配合 HTTPS 部署,否则无效 | #### 协同作用 实际场景中,两者通常结合使用,同时配合 `SameSite` 等属性,形成 Cookie 安全的“多重防护”: ```http // 综合示例:同时设置 HttpOnly、Secure、SameSite Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400 ``` - `HttpOnly` 防 XSS 窃取; - `Secure` 防传输过程窃听; - `SameSite=Lax` 防 CSRF 攻击。 ### 总结 - **HTTPOnly Cookie**:通过禁止 JS 访问,阻断 XSS 攻击对 Cookie 的窃取,保护会话凭证安全; - **Secure Cookie**:通过强制 HTTPS 传输,防止 Cookie 在明文网络中被截获,保护传输层安全。 两者是保护 Cookie 安全的基础手段,尤其对于包含用户身份信息的会话 Cookie,建议始终同时启用。 ## 8.什么是跨域?跨域产生的原因是什么?浏览器的同源策略是什么? 要理解“跨域”问题,需先掌握**浏览器同源策略**——它是跨域产生的根源。三者的逻辑关系是:**同源策略是浏览器的安全规则 → 违背该规则的请求被视为跨域 → 跨域的本质是同源策略的限制**。以下分三部分详细解析: ### 一、浏览器的同源策略(Same-Origin Policy) 同源策略是浏览器为保护用户数据安全而设计的**核心安全机制**,规定:**只有当“当前页面的源”与“目标资源的源”完全一致时,浏览器才允许页面访问目标资源(如读取DOM、获取Cookie、发送AJAX请求)**。 #### 1. “同源”的定义:三个“完全相同” “源”(Origin)由**协议、域名、端口**三部分组成,三者必须完全一致才算“同源”。只要有一项不同,就是“不同源”。 | 对比场景 | 当前页面URL | 目标资源URL | 是否同源? | 原因分析 | |-------------------------|---------------------------|---------------------------|------------|-----------------------------------| | 协议不同 | `http://example.com` | `https://example.com` | 否 | 协议:http ≠ https | | 域名不同 | `http://example.com` | `http://api.example.com` | 否 | 域名:example.com ≠ api.example.com(子域名不同) | | 端口不同 | `http://example.com:8080` | `http://example.com:3000` | 否 | 端口:8080 ≠ 3000(默认端口80/443可省略) | | 三者完全相同 | `https://example.com` | `https://example.com/path`| 是 | 协议、域名、端口均一致(路径不同不影响同源) | #### 2. 同源策略的核心限制范围 同源策略并非禁止所有跨域行为,而是针对性限制“可能泄露敏感数据”的操作,主要包括三类: - **限制1:禁止访问不同源的DOM** 无法通过JS操作不同源页面的DOM(如iframe嵌套不同域页面时,父页面无法读取子页面的内容)。 例:`http://a.com` 的页面嵌套 `<iframe src ="http://b.com" > `,父页面执行 `iframe.contentDocument.body` 会被浏览器拦截。 - **限制2:禁止读取不同源的存储数据** 无法通过JS读取不同源的Cookie、LocalStorage、SessionStorage(这些数据常包含用户登录凭证、个人信息)。 例:`http://a.com` 的JS无法读取 `http://b.com` 存储的Cookie,避免恶意网站窃取用户身份。 - **限制3:禁止发送跨域的“可读取响应”的请求** 浏览器允许发送跨域的AJAX/fetch请求(请求能到达服务器),但会**拦截服务器返回的响应**,导致前端无法获取响应数据。 例:`http://localhost:8080`(前端)向 `http://localhost:3000`(后端)发送AJAX请求,服务器能收到请求并返回数据,但浏览器会因跨域拦截响应,前端无法使用数据。 #### 3. 同源策略的目的:保护用户安全 同源策略的本质是“隔离不同源的资源”,防止恶意网站通过跨域操作窃取用户数据: - 若没有同源策略,`http://malicious.com`(恶意网站)可读取 `http://bank.com`(银行网站)的Cookie(含用户会话ID),伪造用户身份进行转账; - 若没有同源策略,恶意网站可通过iframe嵌套合法网站,伪装成“官方页面”诱导用户输入密码(点击劫持)。 ### 二、什么是跨域? 跨域是指**浏览器发起的请求,其目标资源的“源”与当前页面的“源”不一致**,导致请求触发浏览器同源策略限制的场景。 需注意: - 跨域是**浏览器端的限制**,服务器之间的请求(如后端调用第三方API)不存在跨域问题; - 并非所有跨域行为都会被拦截:浏览器允许“被动加载”跨域资源(如 `<script > `、`<link > `、`<img > ` 加载不同域的JS、CSS、图片),但禁止“主动读取”这些资源的内容(如无法通过JS读取跨域图片的像素数据)。 ### 三、跨域产生的原因 跨域的根本原因是**浏览器的同源策略限制**,但具体触发场景与前端面试模式密切相关,常见场景包括: #### 1. 前后端分离开发(最常见) 现代前端项目常使用“前端工程化”开发(如Vue/React项目运行在 `localhost:8080`),后端API部署在另一个端口(如 `localhost:3000`)或域名(如 `https://api.example.com`): - 前端页面源:`http://localhost:8080`; - 后端API源:`http://localhost:3000`(端口不同); - 当前端通过AJAX/fetch调用后端API时,触发跨域。 #### 2. 加载不同域的资源 开发中需加载其他域的资源(如第三方SDK、图片、字体),若资源所在域与当前页面域不同,可能触发跨域限制: - 例1:`http://a.com` 加载 `http://b.com/sdk.js`(`<script > ` 标签允许加载,但若SDK尝试读取 `a.com` 的Cookie,会被拦截); - 例2:`http://a.com` 用Canvas绘制 `http://b.com/image.png`(跨域图片),浏览器会禁止读取Canvas的像素数据(防止恶意网站爬取图片内容)。 #### 3. iframe嵌套不同域页面 使用iframe嵌套其他域的页面时,父页面与子页面的交互会触发跨域: - 例:`http://a.com` 嵌套 `<iframe src ="http://b.com" > `,父页面尝试通过 `iframe.contentWindow` 调用子页面的JS函数,会被浏览器拦截。 #### 4. 跨域请求带Credentials(Cookie) 当跨域请求需要携带Cookie(如用户登录状态)时,即使服务器允许跨域,浏览器也会因同源策略限制,默认不携带Cookie;若强制携带,需服务器和前端配合配置(如后端设置 `Access-Control-Allow-Credentials: true`),否则触发跨域拦截。 ### 总结 三者的核心逻辑关系: 1. **同源策略**:浏览器的安全规则,通过“协议+域名+端口”判断是否允许访问资源,目的是保护用户数据; 2. **跨域**:浏览器请求的目标资源与当前页面源不一致的场景,是同源策略的“产物”; 3. **跨域原因**:根本是同源策略限制,具体因前后端分离、跨域资源加载、iframe交互等开发场景触发。 理解这些概念是解决跨域问题的基础(如后续学习CORS、JSONP等跨域解决方案)。 ## 9.如何解决跨域问题?(如 CORS、JSONP、代理服务器、postMessage) 跨域问题的本质是浏览器同源策略的限制,解决思路围绕“**绕开同源策略**”或“**让浏览器允许跨域请求**”展开。不同场景适用不同方案,以下是4种核心解决方案(含原理、实现步骤、适用场景): ### 一、CORS(跨域资源共享,Cross-Origin Resource Sharing) **核心原理**:后端通过设置特定的HTTP响应头,明确告知浏览器“允许某个跨域源的请求访问资源”,浏览器收到响应后会验证这些头信息,若符合规则则放行响应数据,否则拦截。 CORS是目前**最主流、最标准**的跨域解决方案,支持GET/POST/PUT/DELETE等所有HTTP方法,且支持携带Cookie(需额外配置)。 #### 1. CORS的两种请求类型 浏览器根据请求的复杂度,将CORS请求分为“简单请求”和“预检请求”,处理逻辑不同: - **简单请求**(无需预检): 同时满足以下条件: 1. 请求方法为GET/POST/HEAD; 2. 请求头仅包含`Accept`、`Accept-Language`、`Content-Language`、`Content-Type`(且值为`application/x-www-form-urlencoded`/`multipart/form-data`/`text/plain`)。 处理逻辑:直接发送请求,服务器返回带CORS头的响应,浏览器验证后放行。 - **预检请求(Preflight)**(需先预检): 不满足简单请求条件的请求(如PUT/DELETE方法、自定义请求头、`Content-Type: application/json`),浏览器会先发送一个**OPTIONS请求**(预检),询问服务器“是否允许该跨域请求”。 处理逻辑: 1. 浏览器发送OPTIONS预检请求,携带`Origin`(当前源)、`Access-Control-Request-Method`(请求方法)、`Access-Control-Request-Headers`(自定义头); 2. 服务器返回预检响应,确认允许的源、方法、头; 3. 若预检通过,浏览器再发送真实请求;若不通过,直接拦截。 #### 2. 核心CORS响应头(后端配置) 后端需配置以下响应头,控制跨域权限: | 响应头 | 作用 | 示例值 | |-------------------------|---------------------------------------|---------------------------------| | `Access-Control-Allow-Origin` | 允许访问的跨域源(必填) | `https://example.com`(指定源)、`*`(允许所有源,不支持携带Cookie) | | `Access-Control-Allow-Methods` | 允许的请求方法(预检请求需配置) | `GET,POST,PUT,DELETE` | | `Access-Control-Allow-Headers` | 允许的自定义请求头(预检请求需配置) | `Content-Type,Authorization` | | `Access-Control-Allow-Credentials` | 是否允许携带Cookie(可选) | `true`(允许,此时`Allow-Origin`不能为`*`) | | `Access-Control-Max-Age` | 预检响应的缓存时间(可选,减少预检次数) | `86400`(1天,单位:秒) | #### 3. 实现示例 - **后端配置(Node.js/Express)**: 用中间件统一处理CORS(推荐使用`cors`包): ```javascript const express = require('express'); const cors = require('cors'); const app = express(); // 基础配置:允许https://example.com跨域 app.use(cors({ origin: 'https://example.com', // 允许的源(不能为*,若需携带Cookie) methods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的方法 allowedHeaders: ['Content-Type', 'Authorization'], // 允许的头 credentials: true, // 允许携带Cookie maxAge: 86400 // 预检缓存1天 })); // 接口示例 app.get('/api/data', (req, res) => { res.json({ message: '跨域数据' }); }); app.listen(3000); ``` - **前端配置(Axios)**: 若需携带Cookie,需开启`withCredentials`: ```javascript import axios from 'axios'; const instance = axios.create({ baseURL: 'http://localhost:3000', withCredentials: true // 允许携带Cookie(需后端配合credentials: true) }); // 发送请求 instance.get('/api/data') .then(res => console.log(res.data)) .catch(err => console.log(err)); ``` #### 4. 适用场景 - 前后端分离项目(如Vue/React前端 + Node/Java后端); - 需支持所有HTTP方法(如PUT/DELETE)或携带Cookie的场景; - 后端可修改配置(核心前提)。 ### 二、JSONP(JSON with Padding) **核心原理**:利用`<script > `标签不受同源策略限制的特性(浏览器允许加载不同域的JS脚本),通过动态创建`<script > `标签,请求后端接口,后端返回“包裹JSON数据的回调函数调用”,前端通过预定义的回调函数接收数据。 JSONP是**老项目常用方案**,但仅支持GET请求,且存在安全风险(如注入恶意代码)。 #### 1. 实现步骤 1. 前端预定义一个回调函数(如`handleJsonpData`),用于接收后端返回的数据; 2. 前端动态创建`<script > `标签,`src`指向后端JSONP接口,同时通过URL参数传递回调函数名(如`?callback=handleJsonpData`); 3. 后端解析URL参数中的回调函数名,返回“`回调函数名(JSON数据)`”格式的JS代码(如`handleJsonpData({"name":"xxx"})`); 4. 浏览器加载并执行该JS脚本,触发预定义的回调函数,前端获取数据。 #### 2. 实现示例 - **前端代码**: ```javascript // 1. 预定义回调函数 function handleJsonpData(data) { console.log('JSONP返回数据:', data); // 接收后端数据 } // 2. 动态创建<script > 标签 function fetchJsonp() { const script = document.createElement('script'); // 传递回调函数名,后端接口地址 script.src = 'http://localhost:3000/api/jsonp?callback=handleJsonpData'; document.body.appendChild(script); // 3. 加载完成后移除<script > 标签(避免冗余) script.onload = () => { document.body.removeChild(script); }; } // 调用函数发起请求 fetchJsonp(); ``` - **后端代码(Node.js/Express)**: ```javascript const express = require('express'); const app = express(); // JSONP接口 app.get('/api/jsonp', (req, res) => { const callbackName = req.query.callback; // 获取前端传递的回调函数名 const data = { name: 'JSONP数据', value: '跨域成功' }; // 要返回的JSON数据 const jsonpStr = `${callbackName}(${JSON.stringify(data)})`; // 拼接回调函数调用 res.type('text/javascript'); // 设置响应类型为JS res.send(jsonpStr); // 返回JS代码(触发前端回调) }); app.listen(3000); ``` #### 3. 优缺点与适用场景 - **优点**:兼容性好(支持所有浏览器,包括IE),无需后端复杂配置; - **缺点**:仅支持GET请求,存在XSS风险(后端需过滤回调函数名,避免注入恶意代码); - **适用场景**:老浏览器兼容、后端无法修改CORS配置、仅需简单GET请求的场景(如获取第三方接口数据)。 ### 三、代理服务器(Proxy Server) **核心原理**:跨域是浏览器的限制,服务器之间不存在跨域问题。通过搭建一个“代理服务器”,让前端请求先发送到代理服务器,代理服务器再转发请求到目标后端服务器,最后将后端响应返回给前端,从而绕开浏览器的同源策略。 代理服务器分为“开发环境代理”和“生产环境代理”,是**前后端分离开发中最常用的本地调试方案**。 #### 1. 开发环境代理(以Vue CLI为例) Vue CLI、Create React App等前端工程化工具内置了代理功能,只需简单配置即可: - **Vue CLI配置(vue.config.js)**: ```javascript module.exports = { devServer: { proxy: { // 匹配所有以/api开头的请求 '/api': { target: 'http://localhost:3000', // 目标后端服务器地址(跨域地址) changeOrigin: true, // 开启代理:将请求头中的Origin改为目标服务器地址(欺骗后端) pathRewrite: { '^/api': '' // 路径重写:若后端接口没有/api前缀,需删除(如前端请求/api/data → 代理转发到http://localhost:3000/data) } } } } }; ``` - **前端请求示例**: 前端直接请求本地代理(无需写跨域地址): ```javascript import axios from 'axios'; // 前端请求本地代理(http://localhost:8080/api/data) axios.get('/api/data') .then(res => console.log(res.data)) .catch(err => console.log(err)); ``` 代理流程: 前端(8080)→ 代理服务器(8080)→ 后端(3000)→ 代理服务器(8080)→ 前端(8080) #### 2. 生产环境代理(以Nginx为例) 生产环境需用Nginx、Apache等服务器搭建代理,配置反向代理规则: - **Nginx配置(nginx.conf)**: ```nginx server { listen 80; server_name localhost; # 前端访问的域名 # 前端静态资源(如Vue/React打包后的dist目录) location / { root /usr/local/nginx/html; # 静态资源路径 index index.html; try_files $uri $uri/ /index.html; # 解决SPA路由刷新404问题 } # 代理后端API请求 location /api { proxy_pass http://localhost:3000; # 目标后端服务器地址 proxy_set_header Host $host; # 传递请求头Host proxy_set_header X-Real-IP $remote_addr; # 传递真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` 访问流程: 用户 → Nginx(80)→ 前端静态资源(/路径)或后端API(/api路径) #### 3. 适用场景 - 开发环境本地调试(前端工程化项目); - 生产环境需隐藏后端真实地址(增强安全性); - 后端无法修改配置(如调用第三方API,对方不支持CORS)。 ### 四、postMessage(跨窗口/iframe通信) **核心原理**:`postMessage`是浏览器提供的API,允许不同域的窗口(或iframe)之间安全地发送消息,绕开同源策略的限制。 适用于**iframe嵌套跨域页面**、**多窗口跨域通信**的场景(如父窗口与子iframe通信)。 #### 1. 核心API - **发送消息**:`window.postMessage(data, targetOrigin, [transfer])` - `data`:要发送的数据(可序列化的JSON数据); - `targetOrigin`:目标窗口的源(如`https://example.com`,`*`表示允许所有源,不推荐,存在安全风险); - `transfer`:可选,传递的Transferable对象(如Blob)。 - **接收消息**:监听`message`事件 ```javascript window.addEventListener('message', (event) => { // 1. 验证消息来源(防止恶意网站发送伪造消息) if (event.origin !== 'https://trusted.com') return; // 2. 处理消息数据 console.log('收到消息:', event.data); // 3. 可选:向发送方回复消息 event.source.postMessage('已收到消息', event.origin); }, false); ``` #### 2. 实现示例(父窗口与子iframe通信) - **父窗口(源:<http://parent.com)**:> ```html <iframe id ="childIframe" src ="http://child.com" width ="300" height ="200" > </iframe > <script > const iframe = document .getElementById ('childIframe' ); iframe.onload = () => { iframe.contentWindow .postMessage ( { type : 'greet' , content : 'Hello 子窗口' }, 'http://child.com' ); }; window .addEventListener ('message' , (event ) => { if (event.origin !== 'http://child.com' ) return ; console .log ('父窗口收到回复:' , event.data ); }); </script > ``` - **子窗口(源:<http://child.com)**:> ```javascript // 1. 接收父窗口的消息 window.addEventListener('message', (event) => { if (event.origin !== 'http://parent.com') return; // 验证来源 console.log('子窗口收到消息:', event.data); // 2. 向父窗口回复消息 event.source.postMessage( { type: 'reply', content: 'Hi 父窗口' }, event.origin // 回复的源为父窗口的源 ); }); ``` #### 3. 适用场景 - iframe嵌套跨域页面(如嵌入第三方支付页面、广告页面); - 多窗口跨域通信(如弹出窗口与父窗口交互); - 需双向通信的跨域场景。 ### 五、其他补充方案 1. **WebSocket**:全双工通信协议,不受同源策略限制,适合实时通信场景(如聊天、实时数据更新); 2. **document.domain**:仅限主域相同的子域(如`a.example.com`和`b.example.com`),通过设置`document.domain = 'example.com'`实现跨域,现已较少使用; 3. **window.name**:利用`window.name`在页面刷新后仍保留值的特性,传递跨域数据,兼容性好但实现复杂,现已被CORS替代。 ### 总结:各方案适用场景对比 | 解决方案 | 核心优势 | 适用场景 | 局限性 | |----------|-------------------------|-------------------------------------------|---------------------------------| | CORS | 标准、支持所有HTTP方法 | 前后端分离项目、需携带Cookie | 后端需修改配置 | | JSONP | 兼容性好(支持IE) | 老浏览器、仅GET请求、第三方接口 | 仅支持GET、有XSS风险 | | 代理服务器 | 无前端修改、隐藏后端地址 | 开发环境调试、生产环境隐藏接口 | 需搭建代理服务器 | | postMessage | 跨窗口/iframe通信 | iframe嵌套、多窗口交互 | 仅限窗口间通信 | 实际开发中,优先选择**CORS**(生产环境)或**代理服务器**(开发环境),特殊场景(如iframe)用**postMessage**,老项目兼容用**JSONP**。 ## 10.什么是 CORS(跨域资源共享)?CORS 的工作原理是什么?常见的 CORS 响应头有哪些? ### 一、CORS(跨域资源共享,Cross-Origin Resource Sharing)的定义 CORS 是 **W3C 标准**定义的跨域解决方案,其核心逻辑是:**后端通过在 HTTP 响应中添加特定的“授权头”,明确告知浏览器“允许某个跨域源的请求访问资源”**;浏览器收到响应后,会验证这些头信息是否符合规则,若符合则放行响应数据(允许前端读取),否则拦截数据(触发跨域错误)。 CORS 是目前解决跨域问题的 **主流方案**,它突破了浏览器同源策略的限制,支持 GET/POST/PUT/DELETE 等所有 HTTP 方法,且能安全携带用户身份凭证(如 Cookie、Token),同时兼顾安全性(需后端明确授权,而非无差别允许跨域)。 ### 二、CORS 的工作原理 CORS 的工作流程依赖“浏览器与服务器的协作”,浏览器会根据请求的“复杂度”自动将 CORS 请求分为 **“简单请求”** 和 **“预检请求(Preflight)”**,两种请求的处理逻辑不同。 #### 1. 核心前提:浏览器的自动判断 浏览器在发送跨域请求前,会先判断请求是否为“简单请求”——若满足所有条件则为简单请求(直接发送),否则为预检请求(先发送 OPTIONS 预检,再发送真实请求)。 ##### (1)简单请求的判定条件(需同时满足) - **请求方法限制**:仅为 GET、POST、HEAD 三者之一; - **请求头限制**:仅包含浏览器默认允许的头,或以下自定义头: `Accept`、`Accept-Language`、`Content-Language`、`Content-Type`; - **Content-Type 限制**:若包含 `Content-Type`,其值仅能为以下三种之一: `application/x-www-form-urlencoded`(表单默认)、`multipart/form-data`(文件上传)、`text/plain`(纯文本)。 **示例**:跨域发送 GET 请求获取数据、跨域提交表单(Content-Type 为 application/x-www-form-urlencoded),均属于简单请求。 ##### (2)预检请求的触发场景(非简单请求) 只要不满足“简单请求”的任一条件,即为预检请求,常见场景包括: - 请求方法为 PUT、DELETE、PATCH 等非简单方法; - 自定义请求头(如 `Authorization`(Token 认证)、`X-Requested-With`); - `Content-Type` 为 `application/json`(JSON 格式数据)、`application/xml` 等非简单类型; - 请求中携带 Cookie(需前端开启 `withCredentials`)。 **示例**:跨域发送 `Content-Type: application/json` 的 POST 请求、跨域发送 DELETE 请求删除数据,均会触发预检请求。 #### 2. 两种请求的具体工作流程 ##### (1)简单请求的流程(3步) 简单请求无需提前“打招呼”,直接发送真实请求,浏览器通过响应头验证授权: 1. **前端发送跨域请求**:请求头中自动携带 `Origin` 字段(值为当前页面的源,如 `https://frontend.com`),告知服务器“我来自哪个域”; 2. **服务器返回响应**:服务器检查 `Origin` 是否在允许的跨域列表中,若允许,则在响应头中添加 CORS 授权头(如 `Access-Control-Allow-Origin`);若不允许,则不添加任何 CORS 头; 3. **浏览器验证响应**: - 若响应中包含合法的 CORS 头(如 `Access-Control-Allow-Origin` 匹配 `Origin`),浏览器放行数据,前端可正常读取响应; - 若响应中无 CORS 头或头信息不匹配,浏览器拦截数据,前端触发跨域错误(如 `Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy`)。 ##### (2)预检请求的流程(5步) 预检请求相当于“提前确认”:浏览器先发送 OPTIONS 请求询问服务器“是否允许该跨域请求”,确认允许后再发送真实请求,避免直接发送复杂请求导致资源浪费或安全风险: 1. **前端发送 OPTIONS 预检请求**:请求头中携带 3 个核心字段,告知服务器请求的关键信息: - `Origin`:当前页面的源(如 `https://frontend.com`); - `Access-Control-Request-Method`:真实请求的方法(如 `PUT`); - `Access-Control-Request-Headers`:真实请求的自定义头(如 `Authorization, Content-Type`); 2. **服务器返回预检响应**:服务器根据预检请求的字段,判断是否允许该跨域请求,若允许,则在响应头中添加 CORS 授权头(如 `Access-Control-Allow-Methods`、`Access-Control-Allow-Headers`);若不允许,则返回普通响应(无 CORS 头); 3. **浏览器验证预检响应**: - 若预检响应的 CORS 头合法(如允许的方法包含 `PUT`、允许的头包含 `Authorization`),则通过预检; - 若预检不通过(如无 CORS 头或头信息不匹配),浏览器直接拦截,不发送真实请求,前端触发跨域错误; 4. **预检通过后,发送真实请求**:流程与“简单请求”的第 1-2 步一致(真实请求头仍携带 `Origin`); 5. **服务器返回真实响应**:流程与“简单请求”的第 3 步一致,浏览器验证响应头后放行或拦截数据。 ### 三、常见的 CORS 响应头(后端配置核心) CORS 的授权逻辑完全由后端通过响应头控制,以下是最核心的 CORS 响应头,需根据业务需求配置: | 响应头名称 | 核心作用 | 取值示例 | 注意事项 | |-----------------------------|-------------------------------------------|-----------------------------------|-------------------------------------------| | `Access-Control-Allow-Origin` | **必填**,指定允许访问的跨域源 | - 具体域名:`https://frontend.com`(仅允许该域);<br > - 通配符:`*`(允许所有域,**不支持携带Cookie**) | 若前端需携带 Cookie(`withCredentials: true`),此值**不能为 `*`**,必须指定具体域名 | | `Access-Control-Allow-Methods` | 允许的请求方法(预检请求需配置) | `GET, POST, PUT, DELETE, OPTIONS` | 需包含真实请求的方法,建议包含 OPTIONS(预检请求的方法) | | `Access-Control-Allow-Headers` | 允许的自定义请求头(预检请求需配置) | `Content-Type, Authorization, X-User-ID` | 需包含真实请求中所有的自定义头(如 Token 对应的 `Authorization`) | | `Access-Control-Allow-Credentials` | 是否允许携带用户凭证(Cookie、Token等) | `true`(允许) / `false`(默认,不允许) | 若设为 `true`,则 `Access-Control-Allow-Origin` 必须为具体域名(不能是 `*`);前端需配合开启 `withCredentials: true`(如 Axios 配置) | | `Access-Control-Max-Age` | 预检响应的缓存时间(单位:秒) | `86400`(1天) / `3600`(1小时) | 缓存期间,相同的跨域请求无需重复发送预检请求,减少服务器压力 | | `Access-Control-Expose-Headers` | 允许前端读取的非默认响应头(可选) | `X-Total-Count, X-Page-Size` | 浏览器默认仅允许前端读取 `Cache-Control`、`Content-Length` 等少数响应头;若需读取自定义头(如分页信息 `X-Total-Count`),需通过此头指定 | ### 四、总结 CORS 的本质是“**后端授权,浏览器放行**”: 1. 浏览器自动判断请求类型(简单/预检),并携带 `Origin` 等字段告知服务器跨域信息; 2. 后端通过 CORS 响应头明确授权范围(允许的源、方法、头、凭证); 3. 浏览器验证授权头,符合规则则放行数据,否则拦截。 配置 CORS 时,核心是后端正确设置 `Access-Control-Allow-Origin`、`Access-Control-Allow-Credentials` 等头,前端按需配合开启 `withCredentials`(如需携带 Cookie),即可安全解决跨域问题。 ## 11.CORS 中的简单请求和预检请求(Preflight)有什么区别?预检请求的触发条件是什么? 在CORS(跨域资源共享)机制中,**简单请求**和**预检请求(Preflight)** 是浏览器处理跨域请求的两种核心模式,其本质区别在于“请求复杂度”和“浏览器处理流程”——简单请求直接发送真实数据,预检请求则先通过`OPTIONS`方法“提前确认服务器授权”,再发送真实请求。以下从「核心区别」和「预检请求触发条件」两方面详细解析: ## 一、简单请求与预检请求的核心区别 两者的差异贯穿“请求发起流程、HTTP方法、请求头、服务器响应要求”等多个维度,具体对比如下: | 对比维度 | 简单请求(Simple Request) | 预检请求(Preflight Request) | |-------------------------|-------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| | **核心定义** | 符合浏览器“低风险”判定的跨域请求,无需提前确认,直接发送真实数据。 | 不符合“低风险”判定的跨域请求,需先发送`OPTIONS`请求“询问服务器是否允许”,再发送真实请求。 | | **请求流程** | 1. 前端直接发送真实请求(携带`Origin`头);<br > 2. 服务器返回响应(携带CORS授权头);<br > 3. 浏览器验证授权头,放行/拦截数据。 | 1. 前端自动发送`OPTIONS`预检请求(携带`Origin`、`Access-Control-Request-Method`等头);<br > 2. 服务器返回预检响应(确认允许的方法、头);<br > 3. 预检通过→发送真实请求;<br > 4. 服务器返回真实响应,浏览器验证放行。 | | **HTTP方法限制** | 仅允许 **GET、POST、HEAD** 三种方法。 | 支持所有HTTP方法(如PUT、DELETE、PATCH),但预检时需通过`Access-Control-Request-Method`告知服务器真实方法。 | | **请求头限制** | 仅允许浏览器默认头或以下4种自定义头:<br > `Accept`、`Accept-Language`、`Content-Language`、`Content-Type`。 | 允许任意自定义头(如`Authorization`、`X-Token`、`X-Page`),但预检时需通过`Access-Control-Request-Headers`告知服务器。 | | **Content-Type限制** | 仅允许三种值:<br > `application/x-www-form-urlencoded`(表单默认)、`multipart/form-data`(文件上传)、`text/plain`(纯文本)。 | 无限制(如`application/json`、`application/xml`、`image/png`等均支持),但需在预检中声明。 | | **服务器响应核心要求** | 必须返回`Access-Control-Allow-Origin`(允许的源);<br > 若需携带Cookie,需额外返回`Access-Control-Allow-Credentials: true`。 | 1. 预检响应:需返回`Access-Control-Allow-Origin`、`Access-Control-Allow-Methods`(允许的方法)、`Access-Control-Allow-Headers`(允许的头);<br > 2. 真实响应:同简单请求。 | | **是否缓存响应** | 不缓存(每次请求均需服务器验证`Origin`)。 | 预检响应可通过`Access-Control-Max-Age`设置缓存时间(如1天),缓存期间无需重复发送预检。 | | **适用场景** | 简单数据获取(如GET请求拉取列表)、传统表单提交(如POST提交用户名密码)。 | 复杂交互(如PUT更新数据、DELETE删除数据)、JSON格式提交(`Content-Type: application/json`)、带Token认证(`Authorization`头)。 | | **示例** | ```javascript<br > // GET请求拉取数据(简单请求)<br > fetch('https://api.example.com/data')<br > ``` | ```javascript<br > // POST提交JSON数据(预检请求)<br > fetch('https://api.example.com/submit', {<br > method: 'POST',<br > headers: { 'Content-Type': 'application/json' }, // 触发预检<br > body: JSON.stringify({ name: 'test' })<br > })<br > ``` | ## 二、预检请求(Preflight)的触发条件 预检请求的本质是“非简单请求”——只要跨域请求**不满足简单请求的任一判定条件**,浏览器就会自动触发预检(发送`OPTIONS`请求)。具体触发条件可分为4类: ### 1. 请求方法不是 GET、POST、HEAD 若跨域请求使用除`GET`、`POST`、`HEAD`之外的任何HTTP方法(如PUT、DELETE、PATCH、OPTIONS、TRACE等),必然触发预检。 **示例**: ```javascript // 使用DELETE方法删除数据(触发预检) fetch('https://api.example.com/delete/123', { method: 'DELETE' });
2. 请求头包含“非默认自定义头” 若请求中携带了浏览器默认头之外的自定义头(即不属于Accept、Accept-Language、Content-Language、Content-Type的头),触发预检。常见场景 :
带Token认证:Authorization: Bearer xxx;
自定义业务头:X-Token: xxx、X-Page: 1、X-User-ID: 456;
跨域携带Cookie时的X-Requested-With: XMLHttpRequest。
示例 :
1 2 3 4 fetch ('https://api.example.com/data' , { headers : { 'Authorization' : 'Bearer abc123' } });
3. Content-Type 不是允许的三种值 若请求的Content-Type不属于简单请求允许的三类(application/x-www-form-urlencoded、multipart/form-data、text/plain),触发预检。常见场景 :
提交JSON数据:Content-Type: application/json(最常见的触发场景);
提交XML数据:Content-Type: application/xml;
提交二进制数据:Content-Type: application/octet-stream。
示例 :
1 2 3 4 5 6 fetch ('https://api.example.com/submit' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ age : 20 }) });
4. 其他特殊情况 部分浏览器对“跨域携带凭证”的场景有额外处理(虽不直接触发预检,但需服务器配合):
若前端开启withCredentials: true(携带Cookie),即使是简单请求,服务器也需返回Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin不能为*(需指定具体域名);
若携带凭证的同时满足上述1-3任一条件,仍会触发预检。
示例 :
1 2 3 4 5 fetch ('https://api.example.com/data' , { credentials : 'include' , headers : { 'X-Token' : 'xxx' } });
三、总结 简单请求与预检请求的核心差异是“风险等级”:
简单请求被浏览器判定为“低风险”,直接发送真实数据,仅需服务器验证“源”;
预检请求被判定为“高风险”(如修改数据、带敏感头),需先通过OPTIONS确认服务器授权,再发送真实请求,避免无效请求或安全风险。
理解两者的区别和预检触发条件,能帮助开发者快速定位跨域问题(如“为何我的POST请求发了两次?”——因为触发了预检),并指导后端正确配置CORS响应头(如预检请求需返回Access-Control-Allow-Methods)。
12.什么是 JSONP?JSONP 的工作原理是什么?JSONP 有哪些局限性? 一、JSONP 的定义 JSONP(JSON with Padding,带填充的JSON )是一种早期的跨域数据交互方案,核心是利用 <script> 标签不受浏览器同源策略限制 的特性(浏览器允许加载不同域的 JS 脚本),实现跨域获取数据。
它并非真正的 AJAX(AJAX 基于 XMLHttpRequest/fetch,受同源策略限制),而是通过动态创建 <script> 标签,让后端返回“包裹 JSON 数据的 JS 函数调用”,前端通过预定义的函数接收并处理数据。
二、JSONP 的工作原理 JSONP 的工作流程依赖“前端预定义回调 + 后端返回回调执行代码”的协作,具体分为 5 个步骤,结合示例更易理解:
步骤 1:前端预定义“回调函数” 前端先定义一个全局函数(如 handleJsonpData),用于接收和处理后端返回的跨域数据。该函数的参数就是最终需要的 JSON 数据。
1 2 3 4 5 function handleJsonpData (data ) { console .log ("跨域获取的数据:" , data); }
步骤 2:前端动态创建 <script> 标签 前端通过 JS 动态创建 <script> 标签,其 src 属性指向 后端的 JSONP 接口 ,并通过 URL 参数(如 callback=handleJsonpData)将“回调函数名”传递给后端,告知后端“用这个函数包裹数据返回”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fetchDataByJsonp ( ) { const script = document .createElement ("script" ); script.src = "https://api.example.com/jsonp?callback=handleJsonpData" ; document .body .appendChild (script); script.onload = function ( ) { document .body .removeChild (script); }; } fetchDataByJsonp ();
步骤 3:后端接收并解析回调函数名 后端接收到请求后,从 URL 参数中提取 callback 的值(即前端传递的回调函数名 handleJsonpData),并准备需要返回的 JSON 数据(如用户信息、列表数据)。
步骤 4:后端返回“回调函数包裹的 JSON 数据” 后端将 JSON 数据作为参数,拼接成“回调函数名(JSON数据)”格式的 JS 代码字符串(如 handleJsonpData({"name":"张三","age":20})),并设置响应头 Content-Type: text/javascript(告知浏览器“这是 JS 脚本,需要执行”)。
后端示例(Node.js/Express) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const app = express ();app.get ("/jsonp" , (req, res ) => { const callbackName = req.query .callback ; const userData = { name : "张三" , age : 20 , gender : "男" }; const jsCode = `${callbackName} (${JSON .stringify(userData)} )` ; res.type ("text/javascript" ); res.send (jsCode); }); app.listen (3000 );
步骤 5:浏览器执行 JS 脚本,触发回调函数 浏览器加载后端返回的 JS 脚本(如 handleJsonpData({"name":"张三","age":20}))后,会自动执行该脚本,进而触发前端预定义的 handleJsonpData 函数,函数参数就是后端返回的 JSON 数据——至此,前端成功跨域获取数据。
三、JSONP 的局限性 JSONP 是早期跨域方案的产物,随着 CORS 等标准方案的普及,其局限性日益明显,主要体现在以下 4 点:
1. 仅支持 GET 请求,无法支持 POST/PUT/DELETE 等方法 JSONP 依赖 <script> 标签加载资源,而 <script> 标签仅能发起 GET 请求 (浏览器加载脚本时默认用 GET),无法发送 POST、PUT 等其他 HTTP 方法的请求。
这导致 JSONP 仅适用于“跨域获取数据”(如拉取列表、详情),无法满足“跨域提交数据”(如提交表单、更新数据)的需求。
2. 存在严重的安全风险 JSONP 的安全风险源于“后端返回的 JS 脚本会被浏览器直接执行”,若后端或传输过程被劫持,可能注入恶意代码:
XSS 攻击风险 :若后端未过滤回调函数名或 JSON 数据中的特殊字符,攻击者可能构造恶意回调名(如 callback=alert(1)//),导致后端返回 alert(1)//({"name":"张三"}),浏览器执行后触发 XSS;
恶意后端风险 :若 JSONP 接口是第三方提供的,第三方可能返回恶意 JS 代码(如窃取 Cookie、跳转钓鱼页面),前端无法拦截;
无请求验证机制 :JSONP 无法像 CORS 那样通过 Access-Control-Allow-Origin 限制源,任何网站都能调用该接口,可能导致数据泄露。
3. 无法捕获请求错误,容错性差 <script> 标签加载脚本时,浏览器仅能监听 onload(加载成功)和 onerror(加载失败)事件,但 onerror 无法获取具体的错误原因 (如 HTTP 404、500 错误),前端无法区分“网络错误”“接口不存在”“数据错误”等场景。
例如:若后端接口返回 404,<script> 的 onerror 会触发,但前端无法知道是 404 错误,只能笼统地判断“请求失败”,难以排查问题。
4. 不支持携带用户凭证(如 Cookie、Token) 跨域场景下,<script> 标签发起的 GET 请求 默认不会携带目标域的 Cookie (浏览器同源策略限制),且 JSONP 没有类似 CORS 的 withCredentials 配置项,无法主动携带 Cookie 或 Token 进行身份验证。
这导致 JSONP 无法用于“需要登录状态的跨域接口”(如获取登录用户的个人信息)。
四、JSONP 的适用场景(已逐步被淘汰) 目前 JSONP 仅在以下特殊场景中偶有使用,大部分场景已被 CORS 替代:
兼容老旧浏览器 (如 IE6/7,不支持 CORS);
调用第三方接口 (如早期的天气、地图接口),且第三方仅提供 JSONP 方案,不支持 CORS;
简单的跨域数据获取 (无身份验证、无安全风险)。
总结 JSONP 的核心是“利用 <script> 标签绕开同源策略”,工作原理是“前端预定义回调 + 后端返回回调执行代码”;但其 仅支持 GET、安全风险高、容错性差 等局限性,使其逐渐被更标准、更安全的 CORS 方案取代。在现代前端面试中,除非有特殊兼容需求,否则优先使用 CORS。
13.什么是 iframe 跨域?如何实现 iframe 之间的跨域通信(如 postMessage、document.domain)? 要理解 iframe 跨域 和对应的通信方案,需先结合浏览器同源策略——当 iframe 嵌入的页面(子页面)与父页面(嵌入 iframe 的页面)不同源 (协议、域名、端口任一不同)时,浏览器会限制两者的交互(如禁止访问 DOM、Cookie),这就是 iframe 跨域 。
解决 iframe 跨域通信的核心思路是“绕开同源策略限制”,主流方案有 postMessage(通用方案) 和 document.domain(仅主域相同场景) ,以下详细解析:
一、什么是 iframe 跨域? iframe 是 HTML 标签,用于在当前页面中嵌入另一个页面(子页面)。当 父页面的源 与 子页面的源 不满足“协议、域名、端口完全一致”时,即触发 iframe 跨域,浏览器会施加以下限制:
父页面无法访问子页面的 document(如 iframe.contentDocument.body)、window(如 iframe.contentWindow.location);
子页面无法访问父页面的 parent.document、top.document;
两者无法共享 Cookie、LocalStorage(即使域名相关);
无法通过 JS 直接调用对方的函数或传递数据。
示例 :
父页面 URL:https://parent.com(源:https://parent.com);
子页面 URL:https://child.com(源:https://child.com);
父页面嵌入 <iframe src="https://child.com"></iframe>,此时两者跨域,父页面执行 iframe.contentDocument 会被浏览器拦截,报错:Blocked a frame with origin "https://parent.com" from accessing a cross-origin frame。
二、iframe 跨域通信的实现方案 方案 1:postMessage(通用方案,推荐) postMessage 是浏览器提供的 跨源通信 API ,支持任意源的 iframe 之间(或窗口之间)安全传递消息,不受主域、子域限制,是现代前端的首选方案。
1. 核心原理
发送方 :通过 targetWindow.postMessage(data, targetOrigin) 向目标窗口(如子 iframe 的 contentWindow、父页面的 parent)发送消息,指定消息内容和目标源;
接收方 :通过监听 window 的 message 事件,接收消息并验证来源(防止恶意消息),再处理数据;
浏览器会自动处理跨域逻辑,确保消息仅在指定源之间传递。
2. 关键 API 说明
(2)子 iframe 接收消息并回复 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script > window .addEventListener ('message' , (event ) => { if (event.origin !== 'https://parent.com' ) return ; const receivedData = JSON .parse (event.data ); console .log ('子页面收到消息:' , receivedData); const replyMessage = { type : 'reply' , content : 'Hi 父页面!我已收到你的消息' , timestamp : Date .now () }; event.source .postMessage (JSON .stringify (replyMessage), event.origin ); }); </script >
4. 适用场景与优势
适用场景 :所有 iframe 跨域场景(主域不同、协议不同、端口不同均可),如嵌入第三方支付页面、广告页面、多窗口通信;
优势 :
通用性强,支持任意源;
安全性高(需验证 event.origin,防止恶意消息);
支持双向通信,可传递复杂数据(需序列化)。
方案 2:document.domain(仅主域相同的子域跨域) document.domain 是早期的跨域方案,仅适用于“主域相同但子域不同” 的场景(如父页面 a.example.com、子 iframe b.example.com,主域均为 example.com)。其核心原理是“将两者的 document.domain 统一设置为相同的主域,让浏览器认为它们同源”。
1. 核心原理 浏览器判断“同源”时,若两个页面的 document.domain 被显式设置为相同的值(且该值是两者的公共主域),则视为同源。例如:
父页面 a.example.com 设置 document.domain = 'example.com';
子 iframe b.example.com 也设置 document.domain = 'example.com';
此时浏览器认为两者同源,允许访问对方的 DOM、Cookie 等。
2. 实现示例(主域相同的子域跨域) 假设场景:父页面 a.example.com 嵌入子 iframe b.example.com,主域均为 example.com。
(1)父页面(a.example.com) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <iframe id ="childIframe" src ="https://b.example.com" width ="400" height ="300" > </iframe > <script > document .domain = 'example.com' ; const iframe = document .getElementById ('childIframe' ); iframe.onload = () => { const childTitle = iframe.contentDocument .title ; console .log ('子页面标题:' , childTitle); iframe.contentWindow .childFunction (); }; window .parentFunction = () => { alert ('子页面调用了父页面的函数!' ); }; </script >
(2)子 iframe 页面(b.example.com) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script > document .domain = 'example.com' ; window .childFunction = () => { alert ('父页面调用了子页面的函数!' ); }; const parentTitle = parent.document .title ; console .log ('父页面标题:' , parentTitle); parent.parentFunction (); </script >
3. 局限性与注意事项
适用范围极窄 :仅支持主域相同的子域跨域,主域不同(如 example.com 和 test.com)或协议/端口不同时完全无效;
安全风险 :设置 document.domain = 'example.com' 后,所有 *.example.com 的子域页面都能访问当前页面的 DOM,可能导致数据泄露;
不可逆性 :document.domain 只能从“子域”设置为“主域”(如 a.example.com → example.com),无法从主域改回子域;
Cookie 共享问题 :即使设置了 document.domain,跨子域共享 Cookie 仍需后端配合(在 Set-Cookie 时指定 domain=example.com)。
三、其他补充方案(已逐步淘汰)
window.name :利用 window.name 在页面刷新后仍保留值的特性,通过 iframe 加载跨域页面,将数据存入 window.name,再切换 iframe 源为同源页面读取数据。缺点是实现复杂,数据大小限制较大(约 2MB)。
location.hash :通过修改 iframe 的 location.hash(URL 锚点)传递数据,子页面监听 hashchange 事件读取数据。缺点是数据暴露在 URL 中,安全性差,大小限制小(约 2000 字符)。
四、总结:方案选择建议
方案
适用场景
优势
劣势
postMessage
所有 iframe 跨域(主域/协议/端口不同均可)
通用、安全、支持双向通信
需序列化数据,需验证 origin
document.domain
主域相同的子域跨域
实现简单,可直接访问 DOM
适用范围窄,安全风险高
window.name/location.hash
早期兼容场景(已淘汰)
兼容旧浏览器
实现复杂,安全性差,数据大小受限
现代开发首选 postMessage ——通用性和安全性均优于其他方案,仅在“主域相同的子域跨域且无需兼容现代浏览器”的 legacy 项目中,可考虑 document.domain。
14.什么是 SQL 注入?前端如何防范 SQL 注入?(如输入过滤、参数化查询) 一、什么是 SQL 注入? SQL 注入(SQL Injection)是一种常见的网络攻击手段,攻击者通过在用户输入(如表单、URL 参数、搜索框等)中插入恶意 SQL 代码 ,使数据库执行非预期的操作(如窃取数据、修改数据、删除表甚至控制服务器)。
其核心原理是:后端程序直接将用户输入拼接进 SQL 语句 ,而未对输入进行安全处理,导致恶意代码被数据库当作合法 SQL 执行。
举个直观的例子 假设一个登录功能的后端 SQL 语句是这样拼接的(错误示范):
-- 后端获取用户输入的用户名和密码,直接拼接 SQL
sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"
如果攻击者在“用户名”输入框中填入:' OR '1'='1,密码任意输入,拼接后的 SQL 会变成:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '任意值'
由于 '1'='1' 恒为真,这条 SQL 会查询出所有用户数据,攻击者无需正确密码即可登录(甚至获取管理员权限)。
二、前端如何防范 SQL 注入? 注意 :SQL 注入的本质是“后端对用户输入处理不当”,前端防范仅能作为辅助手段 (第一道防线),无法彻底解决问题(因为前端代码可被篡改,如通过抓包修改请求参数)。核心防御必须依赖后端,但前端可通过以下方式降低风险:
1. 输入验证与过滤(前端基础防护) 对用户输入的内容进行严格校验,限制输入格式和内容,过滤或转义可能包含恶意 SQL 代码的特殊字符。
步骤 1:格式验证(白名单) 只允许符合预期格式的输入(如手机号、邮箱、用户名等),使用正则表达式拒绝不符合格式的内容。 例:限制用户名只能包含字母、数字和下划线:
// 前端用户名输入验证(正则白名单)
function validateUsername(username) {
const regex = /^[a-zA-Z0-9_]{4,16}$/; // 仅允许4-16位字母、数字、下划线
return regex.test(username);
}
// 使用示例
const userInput = document.getElementById('username').value;
if (!validateUsername(userInput)) {
alert('用户名只能包含字母、数字和下划线,长度4-16位');
return false; // 阻止表单提交
}
步骤 2:特殊字符过滤/转义(黑名单辅助) 对可能用于构造 SQL 注入的特殊字符(如单引号 '、双引号 "、分号 ;、注释符 --、关键字 OR/UNION 等)进行过滤或转义。 例:转义单引号(在 SQL 中,单引号是字符串边界,转义后会被视为普通字符):
// 前端特殊字符转义(示例:转义单引号)
function escapeSpecialChars(input) {
if (!input) return '';
// 转义单引号(' → '',SQL中两个单引号表示一个普通单引号)
return input.replace(/'/g, "''")
.replace(/;/g, '') // 过滤分号
.replace(/--/g, ''); // 过滤注释符
}
// 使用示例
const userInput = document.getElementById('username').value;
const safeInput = escapeSpecialChars(userInput);
// 提交处理后的safeInput
2. 限制输入长度 恶意 SQL 注入代码通常需要一定长度(如 UNION SELECT...),通过限制用户输入的长度(如用户名最长 20 位、密码最长 30 位),可增加攻击难度。
<!-- 前端通过input的maxlength限制长度 -->
<input type="text" name="username" maxlength="20" placeholder="用户名(最长20位)">
3. 避免直接拼接 SQL(前端责任边界) 前端应避免参与任何 SQL 语句的拼接逻辑(即使是“前端临时处理数据”的场景)。所有与数据库交互的逻辑必须由后端处理,前端仅负责传递用户输入的“原始数据”,不处理 SQL 相关的字符串拼接。
三、核心防御:后端必须做的事(前端无法替代) 前端防护是“锦上添花”,后端的参数化查询(预编译语句)才是防范 SQL 注入的根本手段 ,以下是后端核心措施:
1. 参数化查询(预编译语句) 参数化查询将 SQL 语句的“结构”与“用户输入的参数”分离:SQL 语句先预编译(确定结构),用户输入作为“数据参数”传入,数据库会将参数视为纯数据,而非可执行的 SQL 代码。
示例(Java + JDBC) :
// 正确:使用 ? 作为占位符,参数化查询
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username); // 传入用户名参数(自动处理特殊字符)
pstmt.setString(2, password); // 传入密码参数
ResultSet rs = pstmt.executeQuery();
示例(Python + MySQLdb) :
# 正确:使用 %s 作为占位符
sql = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(sql, (username, password)) # 参数作为元组传入,自动转义
2. 使用 ORM 框架 ORM(对象关系映射)框架(如 Java 的 MyBatis、Hibernate,Python 的 SQLAlchemy)内部已实现参数化查询,避免手动拼接 SQL,从根源减少注入风险。
3. 最小权限原则 数据库账号仅授予必要的权限(如查询、插入),禁止使用 root 等高权限账号连接应用,即使发生注入,攻击者也无法执行删除表、修改权限等高危操作。
4. 禁止返回详细错误信息 后端应捕获 SQL 执行错误,返回通用提示(如“服务器繁忙”),而非详细的错误堆栈(如“SQL 语法错误 near ‘OR 1=1’”),避免攻击者通过错误信息推测 SQL 结构。
四、总结
SQL 注入本质 :后端将用户输入直接拼接进 SQL 语句,导致恶意代码被执行。
前端防范 :通过输入验证(正则白名单)、特殊字符过滤、限制长度等方式,降低攻击成功率,但无法彻底防范(前端代码可被篡改)。
核心防御 :后端必须使用参数化查询(预编译语句) ,配合 ORM 框架、最小权限原则等,从根本上阻断 SQL 注入。
最佳实践 :前端做基础过滤 + 后端做参数化查询,形成多层防护。
15.什么是点击劫持(Clickjacking)?如何防范点击劫持?(如 X-Frame-Options、framebusting) 一、什么是点击劫持(Clickjacking)? 点击劫持是一种视觉欺骗攻击 ,攻击者通过精心构造页面,将目标网站(如银行、支付平台、社交网站)的敏感操作区域(如“转账”“确认付款”“关注”按钮)隐藏在看似无害的元素(如“领取奖励”“关闭广告”按钮)下方,诱导用户点击。
用户看似点击的是攻击者设计的按钮,实际点击的是被隐藏的目标网站敏感按钮,从而在不知情的情况下完成恶意操作(如转账、授权登录、删除数据等)。
点击劫持的典型攻击流程
攻击者创建恶意页面 :用 iframe 嵌套目标网站(如 bank.com 的转账页面),并设置 iframe 为透明(opacity: 0),覆盖在恶意按钮上方;
诱导用户访问 :通过钓鱼链接(如“免费领取红包”)欺骗用户打开恶意页面;
用户误操作 :用户点击“领取红包”按钮时,实际点击的是 iframe 中目标网站的“确认转账”按钮,完成非自愿操作。
示例场景 :
<!-- 攻击者的恶意页面 -->
<style>
/* 目标网站的iframe设为透明,覆盖在恶意按钮上 */
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0; /* 完全透明,用户看不见 */
z-index: 2; /* 层级高于恶意按钮,确保点击穿透 */
}
.fake-button {
position: absolute;
top: 100px;
left: 100px;
z-index: 1; /* 层级低于iframe */
padding: 20px 40px;
font-size: 20px;
background: red;
color: white;
}
</style>
<!-- 嵌套目标网站(如银行转账页面) -->
<iframe src="https://bank.com/transfer"></iframe>
<!-- 诱导用户点击的虚假按钮 -->
<button class="fake-button">点击领取100元红包</button>
用户点击“领取红包”按钮时,实际点击的是 iframe 中银行页面的“确认转账”按钮(假设位置重合)。
二、如何防范点击劫持? 防范核心是阻止页面被恶意网站通过 iframe 嵌套 ,或在被嵌套时提示用户/禁止操作 ,主要手段包括后端响应头控制和前端代码防御。
1. 后端设置 X-Frame-Options 响应头(推荐) X-Frame-Options 是 HTTP 响应头,由后端配置,直接告诉浏览器“是否允许当前页面被嵌入 iframe”,是最直接有效的防御手段。
该头有三个可选值:
值
作用
适用场景
DENY
禁止当前页面被任何网站的 iframe 嵌套(无论同源还是跨域)。
不允许任何嵌套的页面(如登录页、支付页)。
SAMEORIGIN
仅允许当前页面被同域 的 iframe 嵌套(不同域的嵌套会被阻止)。
允许自身域名下的嵌套(如本站内的 iframe 复用组件)。
ALLOW-FROM uri
仅允许当前页面被 uri 指定的域名的 iframe 嵌套(如 https://trusted.com)。
需被特定可信域名嵌套的场景(如合作方网站)。
配置示例 :
Nginx 配置 :
# 禁止任何iframe嵌套(推荐用于高安全页面)
add_header X-Frame-Options "DENY";
# 仅允许同域嵌套(适用于站内iframe)
# add_header X-Frame-Options "SAMEORIGIN";
# 仅允许指定域名嵌套(如信任的合作方)
# add_header X-Frame-Options "ALLOW-FROM https://trusted.com";
Apache 配置 (httpd.conf 或 .htaccess):
Header always set X-Frame-Options "DENY"
Node.js/Express 配置 :
const express = require('express');
const app = express();
// 全局设置X-Frame-Options
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
2. 前端 framebusting 代码(辅助防御) framebusting 是前端通过 JavaScript 检测页面是否被嵌入 iframe,若被嵌套则采取防御措施(如跳转、隐藏内容、提示用户)。
核心原理 :通过比较 window.top(最顶层窗口)和 window.self(当前窗口),判断页面是否被嵌套。
常用 framebusting 实现 :
// 方法1:若被嵌套,则跳转到自身(打破iframe)
if (window.top !== window.self) {
window.top.location = window.self.location; // 顶层窗口跳转到当前页面URL
}
// 方法2:若被嵌套,隐藏页面内容
if (window.top !== window.self) {
document.body.style.display = 'none'; // 隐藏内容,防止被点击
alert('警告:此页面不允许在框架中显示!');
}
// 方法3:更严格的检测(防止被部分绕过)
try {
// 尝试访问顶层窗口的location,跨域时会抛错
if (window.top.location.hostname !== window.self.location.hostname) {
throw new Error('跨域嵌套');
}
} catch (e) {
// 跨域嵌套时执行防御
window.top.location = window.self.location;
}
局限性 :framebusting 可能被攻击者通过以下方式绕过(因此需配合 X-Frame-Options 使用):
攻击者在 iframe 中设置 sandbox 属性(如 sandbox="allow-scripts"),限制 window.top.location 跳转;
利用 HTML5 history API 或其他手段阻止跳转。
3. 使用 CSP(内容安全策略)的 frame-ancestors 指令(现代替代方案) frame-ancestors 是 CSP(Content-Security-Policy)中的指令,用于限制“哪些域名可以通过 iframe、frame 等标签嵌套当前页面”,功能类似 X-Frame-Options,但更灵活(支持多个域名、通配符),且是 W3C 标准,推荐现代网站使用。
语法 :
Content-Security-Policy: frame-ancestors <source>;
常用值 :
'none':禁止任何域名嵌套(等价于 X-Frame-Options: DENY);
'self':仅允许同域嵌套(等价于 X-Frame-Options: SAMEORIGIN);
具体域名:如 https://trusted.com https://*.example.com(允许指定域名及子域嵌套)。
配置示例 :
Nginx 配置 :
# 禁止任何嵌套(推荐)
add_header Content-Security-Policy "frame-ancestors 'none';";
# 仅允许同域和trusted.com嵌套
# add_header Content-Security-Policy "frame-ancestors 'self' https://trusted.com;";
优先级 :frame-ancestors 会覆盖 X-Frame-Options(若同时设置,以 CSP 为准),但为兼容旧浏览器(如 IE),可同时配置两者。
4. 其他辅助措施
敏感操作二次验证 :对“转账”“删除”等高危操作,增加验证码、密码确认或短信验证,即使被点击劫持,也需二次验证才能完成操作;
视觉混淆防御 :在页面中添加随机位置的“干扰元素”,增加攻击者对齐敏感按钮和虚假按钮的难度;
检测异常点击 :通过前端/后端分析点击频率、位置等,识别可疑的自动化点击或异常操作。
三、总结 点击劫持的核心危害是“用户在不知情的情况下执行敏感操作”,防范需从“阻止被嵌套”和“增加操作门槛”两方面入手:
首选方案 :后端配置 Content-Security-Policy: frame-ancestors 'none'(现代浏览器)或 X-Frame-Options: DENY(兼容旧浏览器),从根源禁止被恶意嵌套;
辅助方案 :前端添加 framebusting 代码,应对特殊场景下的绕过;
兜底方案 :敏感操作增加二次验证,即使被劫持也能阻止最终危害。
综合使用多种措施,可有效抵御点击劫持攻击。
16.什么是敏感数据泄露?前端如何保护敏感数据?(如加密存储、避免明文传输) 一、什么是敏感数据泄露? 敏感数据泄露指的是能够识别用户身份、涉及隐私或财产安全的数据 (如密码、身份证号、银行卡信息、Token、地理位置、医疗记录等),因存储、传输或处理不当,被未授权的第三方获取的安全事件。
敏感数据泄露的危害极大:可能导致用户隐私泄露、账号被盗、财产损失(如银行卡盗刷)、身份冒用等,甚至引发企业声誉危机和法律风险(如违反《个人信息保护法》)。
常见的敏感数据泄露场景
前端存储不当 :将密码、Token等明文存储在 localStorage、sessionStorage 或 Cookie 中,被 XSS 攻击窃取;
传输未加密 :通过 HTTP 明文传输敏感数据(如登录密码),被中间人拦截;
代码硬编码 :在前端 JS 代码中直接写入 API 密钥、数据库密码等,被攻击者通过“查看源码”获取;
过度暴露数据 :前端展示或处理完整敏感信息(如显示完整身份证号、银行卡号),被截屏、录屏或恶意插件窃取;
第三方依赖风险 :引入的第三方 SDK 或插件恶意收集前端存储的敏感数据。
二、前端如何保护敏感数据? 前端作为数据交互的“入口”,需从存储、传输、展示、处理 四个环节入手,配合后端形成“多层防护”(注意:前端防护是辅助,核心安全依赖后端,如权限校验、加密存储)。
1. 敏感数据加密存储(避免明文存储) 前端存储敏感数据(如用户 Token、临时凭证)时,禁止明文存储 ,需加密后再存入 localStorage/sessionStorage 或内存中,且优先选择“短期存储”。
加密算法选择 : 使用对称加密(如 AES)对敏感数据加密,密钥需动态获取(而非硬编码在前端)。例如:
// 示例:用AES加密敏感数据(需引入crypto-js等库)
import CryptoJS from 'crypto-js';
// 密钥从后端动态获取(如登录后返回临时密钥,避免硬编码)
let secretKey = '动态获取的密钥';
// 加密函数
function encryptData(data) {
return CryptoJS.AES.encrypt(JSON.stringify(data), secretKey).toString();
}
// 解密函数
function decryptData(encryptedData) {
const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey);
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}
// 使用示例:存储加密后的Token
const userToken = '敏感的用户Token';
const encryptedToken = encryptData(userToken);
localStorage.setItem('token', encryptedToken); // 存储加密后的数据
存储方式优先级 :
最高安全:仅在内存中暂存(如 Vue/React 的 state),页面刷新后销毁;
次选:sessionStorage(会话级存储,关闭标签页后删除);
避免:localStorage(持久化存储,易被长期窃取),若必须使用,需加密+定期更新。
2. 避免明文传输(强制加密传输) 敏感数据在前端与后端之间传输时,必须确保全程加密 ,防止中间人拦截。
强制使用 HTTPS : HTTPS 会对传输的数据进行 TLS 加密,是传输层安全的基础。前端需确保所有请求(尤其是登录、支付等)通过 https:// 发起,禁止 http://。可通过前端代码检测并强制跳转:
// 检测协议,非HTTPS则强制跳转
if (window.location.protocol !== 'https:') {
window.location.href = `https:${window.location.href.substring(window.location.protocol.length)}`;
}
应用层二次加密 : 即使使用 HTTPS,对超高敏感数据(如密码、银行卡号)可在前端再进行一次加密(如 RSA 非对称加密),后端解密后处理。例如:
// 示例:用RSA加密密码后发送(公钥由后端提供)
import JSEncrypt from 'jsencrypt';
// 后端提供的公钥(用于加密,私钥在后端)
const publicKey = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----';
// 加密密码
function encryptPassword(password) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
return encryptor.encrypt(password); // 加密后的密码(非明文)
}
// 登录请求:发送加密后的密码
async function login(username, password) {
const encryptedPwd = encryptPassword(password);
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password: encryptedPwd }) // 非明文传输
});
}
3. 最小化数据暴露(按需获取,隐藏展示) 前端应遵循“最小权限原则 ”:仅获取和展示必要的敏感数据,减少泄露风险。
按需请求数据 : 后端返回数据时,前端只请求“必要字段”。例如:用户资料接口无需返回完整身份证号,仅返回 ****1234(后端处理后),前端不接触完整数据。
隐藏敏感信息展示 : 展示时对敏感数据进行“脱敏处理”,避免完整暴露:
// 示例:敏感数据脱敏展示
function maskSensitiveData(data, type) {
switch (type) {
case 'idCard': // 身份证号:显示前6后4
return data.replace(/(\d{6})(\d{8})(\d{4})/, '$1********$3');
case 'bankCard': // 银行卡号:显示后4位
return '**** **** **** ' + data.slice(-4);
case 'phone': // 手机号:中间4位隐藏
return data.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3');
default:
return data;
}
}
// 使用示例
const idCard = '110101199001011234';
console.log(maskSensitiveData(idCard, 'idCard')); // 输出:110101********1234
禁止在 URL 中携带敏感信息 : URL 可能被浏览器日志、代理服务器记录,禁止将 Token、用户 ID 等放在 URL 参数中(如 https://example.com?token=xxx),应放在请求头(如 Authorization)或请求体中。
4. 防止 XSS 攻击(间接保护敏感数据) XSS 攻击是前端敏感数据泄露的主要途径(攻击者通过注入脚本窃取 localStorage、Cookie 或 DOM 中的敏感数据),需做好 XSS 防护:
输入过滤 :对用户输入的内容(如表单、评论)过滤 <script>、onclick 等恶意代码;
输出编码 :展示用户输入时(如渲染评论),对特殊字符(<、>、& 等)进行 HTML 编码(如 < → <);
使用安全的 API :避免 innerHTML、eval() 等易触发 XSS 的 API,优先用 textContent 渲染文本。
5. 安全管理 Cookie(若存储敏感数据) 若需用 Cookie 存储敏感数据(如 SessionID),需通过以下属性限制访问:
HttpOnly:禁止 JS 读取 Cookie(防 XSS 窃取);
Secure:仅允许通过 HTTPS 传输 Cookie;
SameSite:限制跨域请求携带 Cookie(防 CSRF 攻击);
Max-Age:设置合理的过期时间(如 2 小时),减少泄露后的影响。
示例(后端设置) :
// 安全的Cookie配置(后端响应头)
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=7200; Path=/
6. 避免硬编码敏感信息 前端代码(JS、HTML、打包后的文件)可被轻易获取(如通过“查看源码”或抓包),禁止在代码中硬编码敏感信息 :
错误做法:在 JS 中直接写入 API 密钥(const apiKey = 'abc123def456');
正确做法:敏感密钥由后端在需要时动态返回(如登录后通过接口获取临时密钥),且密钥定期失效。
三、总结 前端保护敏感数据的核心原则是“加密存储、加密传输、最小暴露、防窃取 ”,但需明确:前端防护是“第一道防线”,无法完全替代后端安全(如后端需对敏感数据加密存储、做权限校验、审计日志等)。
综合措施:
敏感数据加密后存储,优先用内存或 sessionStorage;
全程 HTTPS 传输,超高敏感数据加做应用层加密;
数据展示和传输时脱敏,仅处理必要信息;
严防 XSS 攻击,避免敏感数据被脚本窃取;
禁止硬编码敏感信息,依赖后端动态授权。
通过多层防护,最大限度降低敏感数据泄露风险。
17.什么是 DDoS 攻击?前端如何应对 DDoS 攻击?(如 CDN 防护、限流、验证码) 一、什么是 DDoS 攻击? DDoS(分布式拒绝服务攻击,Distributed Denial of Service)是一种通过控制大量“傀儡机”(被入侵的设备,如电脑、服务器、物联网设备)向目标服务器发送海量恶意请求 ,导致目标服务器资源(带宽、CPU、内存)被耗尽,无法响应正常用户请求的攻击方式。
其核心目的是让目标服务“瘫痪”,常见于电商网站(如促销期间)、游戏服务器、企业官网等,可能造成经济损失(如交易中断)或声誉影响。
DDoS 攻击的常见类型(按攻击层面划分)
网络层攻击 :针对网络带宽,通过海量数据包(如 UDP 报文、ICMP 报文)占用目标服务器的网络链路,使其无法接收正常数据。例如“UDP Flood”攻击,短时间发送大量随机 UDP 包,耗尽带宽。
传输层攻击 :针对 TCP 连接,通过伪造半连接(如“SYN Flood”)消耗服务器的连接资源。例如攻击者发送大量 TCP 连接请求(SYN 包),但不完成三次握手,导致服务器保留大量半连接队列,无法处理新连接。
应用层攻击 (与前端关联较深):针对应用服务(如 HTTP 服务器、API 接口),通过模拟正常用户行为发送高频请求(如反复刷新页面、调用登录接口),消耗服务器的 CPU/内存资源。例如“HTTP Flood”攻击,利用大量傀儡机发送 GET/POST 请求,使后端处理不过来。
二、前端如何应对 DDoS 攻击? DDoS 攻击的核心防御依赖网络层(如防火墙、流量清洗)和服务器层(如负载均衡、集群扩容) ,但前端可作为“第一道防线”,配合整体防护体系减轻攻击影响。前端的应对思路是“减少无效请求、降低服务器负载、辅助识别恶意流量 ”。
1. 利用 CDN(内容分发网络)分流与防护 CDN 是应对 DDoS 最基础的前端配合手段,其核心作用是“将静态资源(HTML、CSS、JS、图片)部署到全球边缘节点 ”,让用户从就近节点获取资源,减少源服务器的直接访问压力。
CDN 的 DDoS 防护能力 : 主流 CDN(如 Cloudflare、阿里云 CDN)内置 DDoS 防护功能:
边缘节点会过滤掉部分恶意流量(如异常 UDP 包、高频重复请求);
即使边缘节点被攻击,源服务器仍可通过“回源策略”隔离,保障核心服务;
提供“带宽弹性扩容”,应对突发流量攻击。
前端配合方式 :
将所有静态资源(JS、CSS、图片、字体)通过 CDN 域名加载,而非直接访问源站域名;
启用 CDN 的“静态资源缓存”和“压缩”功能,减少回源请求(回源越少,源站压力越小)。
<!-- 示例:通过CDN加载静态资源 -->
<script src="https://cdn.example.com/vue.min.js"></script> <!-- 而非源站的https://example.com/vue.min.js -->
<img src="https://cdn.example.com/logo.png" alt="网站logo">
2. 前端限流:减少高频无效请求 针对应用层 DDoS(如恶意脚本高频调用 API),前端可通过“限制用户操作频率”减少无效请求,降低后端压力。
常见实现方式 :
按钮防重复点击 :用户点击按钮后(如“提交订单”“登录”),短期内禁用按钮,防止快速重复点击;
// 示例:按钮点击限流(1秒内仅允许一次点击)
const submitBtn = document.getElementById('submitBtn');
let isClickable = true;
submitBtn.addEventListener('click', async () => {
if (!isClickable) return; // 未到时间,禁止点击
isClickable = false;
submitBtn.disabled = true; // 禁用按钮
try {
await submitData(); // 调用提交接口
} finally {
// 1秒后恢复可点击状态
setTimeout(() => {
isClickable = true;
submitBtn.disabled = false;
}, 1000);
}
});
接口请求限流 :对同一接口(如搜索、分页加载)设置请求间隔(如 500ms 内仅允许一次请求),用防抖(debounce)或节流(throttle)实现;
// 示例:搜索接口节流(500ms内仅发一次请求)
import { throttle } from 'lodash';
const searchInput = document.getElementById('searchInput');
// 节流处理:500ms内多次输入,仅最后一次触发请求
const throttledSearch = throttle(async (value) => {
await fetch(`/api/search?keyword=${value}`);
}, 500);
searchInput.addEventListener('input', (e) => {
throttledSearch(e.target.value);
});
3. 验证码:区分人机,阻止自动化攻击 应用层 DDoS 常通过“自动化脚本”发起高频请求(如批量注册、刷接口),前端可通过验证码 验证“请求是否来自真实用户”,拦截脚本攻击。
适用场景 :登录、注册、搜索、评论、下单等高频接口,尤其在检测到“异常请求频率”时触发(如 1 分钟内请求超过 10 次)。
实现方式 :
集成第三方验证码(如极验、腾讯防水墙),通过滑动验证、点选验证等方式区分人机;
简单场景可用图形验证码(如数字字母组合),但安全性较低(易被 OCR 识别)。
// 示例:检测到高频请求时,强制验证码验证
let requestCount = 0; // 记录请求次数
const MAX_REQUESTS = 5; // 阈值:5次/分钟
async function callApi() {
requestCount++;
// 超过阈值,要求验证码
if (requestCount > MAX_REQUESTS) {
const isVerified = await showCaptcha(); // 显示验证码并验证
if (!isVerified) {
alert('请完成验证码验证');
return; // 未通过验证,终止请求
}
}
// 发送正常请求
await fetch('/api/data');
}
// 重置计数器(每分钟)
setInterval(() => { requestCount = 0; }, 60000);
4. 资源优化:减少服务器负载 前端通过优化资源加载和渲染,降低服务器的处理压力,间接提升抗 DDoS 能力(服务器资源消耗越少,越能承受攻击)。
静态资源优化 :
压缩 JS/CSS(如用 Webpack 压缩、Gzip 压缩),减少传输大小和服务器响应时间;
图片懒加载(仅加载可视区域图片),减少非必要请求;
合理使用缓存(localStorage 缓存非敏感数据、HTTP 缓存静态资源),减少重复请求。
减少后端依赖 :
非核心数据用前端本地计算(如简单的表单验证、数据格式化),而非调用后端接口;
用 Service Worker 缓存 API 响应(适用于非实时数据),减少回源请求。
5. 配合后端:识别与拦截恶意流量 前端可通过收集“客户端特征”(如设备指纹、行为轨迹),辅助后端识别恶意流量,提升拦截精度。
设备指纹 :通过浏览器 UA、屏幕分辨率、时区等信息生成唯一标识,标记可疑设备;
行为分析 :记录用户操作轨迹(如点击间隔、页面停留时间),异常行为(如无任何点击直接调用接口)标记为可疑,触发二次验证(如验证码)。
三、前端应对的局限性与整体防护思路 前端防护仅能作为“辅助手段”,无法单独抵御 DDoS 攻击,原因是:
前端代码可被篡改(如攻击者绕过前端限流逻辑);
网络层/传输层攻击(如 UDP Flood)完全在前端控制范围之外。
整体防护需多层配合 :
网络层 :运营商/防火墙进行流量清洗,过滤异常数据包;
服务器层 :部署负载均衡(如 Nginx、云负载均衡)、集群扩容,分散压力;
应用层 :后端接口限流(如基于 IP 的 QPS 限制)、恶意 IP 封禁;
前端层 :如上述措施,减少无效请求、辅助人机识别。
总结 DDoS 攻击的核心是“用海量请求耗尽资源”,前端应对的核心是“减少无效请求、配合识别恶意流量”:
依赖 CDN 分流和边缘防护,减少源站暴露;
通过限流、验证码阻止高频自动化请求;
优化资源加载,降低服务器负载。
但需明确:前端是整体防护体系的一环,需与网络层、服务器层协同,才能有效抵御 DDoS 攻击。
18.什么是前端加密?常用的前端加密算法有哪些(如 MD5、SHA、AES、RSA)? 一、什么是前端加密? 前端加密指的是在浏览器或客户端(如Web页面、小程序)中对数据进行加密处理 的过程,目的是保护数据在传输(如发送给后端)或存储(如存入localStorage)时的安全性,防止数据被中间人窃取或恶意篡改。
前端加密是数据安全的“第一道防线”,但需注意:前端加密不能替代后端加密 (因为前端代码可被篡改、密钥可能泄露),通常需与后端加密、HTTPS等措施配合,形成多层防护。
二、常用的前端加密算法 前端加密算法可分为哈希算法 (不可逆,用于校验或摘要)和加密算法 (可逆,用于数据加密)两大类,以下是最常用的几种:
1. 哈希算法(不可逆,用于数据校验) 哈希算法通过特定计算将任意长度的数据转换为固定长度的“哈希值”(如32位、64位字符串),且无法从哈希值反推原始数据 ,主要用于验证数据完整性(如文件是否被篡改)或存储密码(存储哈希值而非明文)。
(1)MD5(Message-Digest Algorithm 5)
特点 :输出128位哈希值(通常显示为32位十六进制字符串),计算速度快,但安全性较低(已被破解,存在碰撞风险——不同数据可能生成相同哈希值)。
用途 :早期用于密码存储、文件校验(如校验下载文件是否完整),现在已不推荐用于安全场景。
前端实现 (需引入crypto-js库):
import CryptoJS from 'crypto-js';
const data = 'hello world';
const md5Hash = CryptoJS.MD5(data).toString();
console.log(md5Hash); // 输出:5eb63bbbe01eeed093cb22bb8f5acdc0
(2)SHA系列(Secure Hash Algorithm) SHA是更安全的哈希算法,包括SHA-1、SHA-256、SHA-512等,其中SHA-1已被破解,推荐使用SHA-256及以上。
特点 :
SHA-256输出256位哈希值(64位十六进制字符串);
安全性远高于MD5,碰撞概率极低,计算速度略慢于MD5。
用途 :密码存储(配合盐值Salt)、数字签名、区块链(如比特币用SHA-256)。
前端实现 :
// SHA-256示例
const sha256Hash = CryptoJS.SHA256('hello world').toString();
console.log(sha256Hash); // 输出:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
2. 对称加密算法(可逆,加密解密用同一密钥) 对称加密算法使用同一个密钥 进行加密和解密,加密速度快,适合处理大量数据,但密钥传输需安全(一旦密钥泄露,数据可被破解)。
AES(Advanced Encryption Standard) AES是目前最流行的对称加密算法,替代了安全性不足的DES,被广泛用于金融、通信等领域。
特点 :
支持128位、192位、256位密钥长度(密钥越长,安全性越高,推荐256位);
加密解密速度快,适合加密大文件或高频接口数据;
安全性高,至今未被有效破解。
用途 :前端存储敏感数据(如localStorage中的Token)、加密API请求体(如用户信息)。
前端实现 (AES-256-CBC模式,需密钥和偏移量iv):
// 密钥(256位=32个字符)和偏移量iv(16个字符,CBC模式必需)
const key = CryptoJS.enc.Utf8.parse('1234567890abcdef1234567890abcdef'); // 32位密钥
const iv = CryptoJS.enc.Utf8.parse('1234567890abcdef'); // 16位iv
// 加密函数
function aesEncrypt(data) {
const srcs = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC, // 加密模式
padding: CryptoJS.pad.Pkcs7 // 填充方式
});
return encrypted.ciphertext.toString(); // 输出加密后的字符串
}
// 解密函数
function aesDecrypt(encryptedData) {
const encryptedHexStr = CryptoJS.enc.Hex.parse(encryptedData);
const srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypted = CryptoJS.AES.decrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8); // 输出原始数据
}
// 使用示例
const data = '敏感数据:123456';
const encrypted = aesEncrypt(data);
const decrypted = aesDecrypt(encrypted);
console.log(encrypted); // 加密后的数据
console.log(decrypted); // 解密后:敏感数据:123456
3. 非对称加密算法(可逆,加密解密用不同密钥) 非对称加密算法使用一对密钥 (公钥Public Key和私钥Private Key):公钥可公开,用于加密数据;私钥需保密,用于解密数据(或用私钥加密、公钥解密,用于签名)。安全性高,但加密速度慢,适合处理小数据(如密钥交换)。
RSA(Rivest-Shamir-Adleman) RSA是最常用的非对称加密算法,基于大数分解难题,密钥长度通常为1024位、2048位(推荐2048位及以上)。
特点 :
公钥加密的数据仅能由对应私钥解密,私钥加密的数据仅能由对应公钥解密;
安全性高,但加密速度比AES慢100-1000倍,不适合大量数据。
用途 :加密对称加密的密钥(如AES密钥通过RSA加密后传输)、数字签名(验证数据来源)、HTTPS握手(证书验证)。
前端实现 (需引入jsencrypt库):
import JSEncrypt from 'jsencrypt';
// 1. 生成密钥对(实际场景中,公钥由后端提供,私钥在后端保存)
const encryptor = new JSEncrypt();
const privateKey = encryptor.getPrivateKey(); // 私钥(后端保存)
const publicKey = encryptor.getPublicKey(); // 公钥(前端使用)
// 2. 用公钥加密数据(前端操作)
const data = '需要加密的小数据(如AES密钥)';
encryptor.setPublicKey(publicKey);
const encrypted = encryptor.encrypt(data); // 加密后的数据
// 3. 用私钥解密数据(后端操作)
const decryptor = new JSEncrypt();
decryptor.setPrivateKey(privateKey);
const decrypted = decryptor.decrypt(encrypted); // 解密后的数据
console.log(decrypted); // 输出:需要加密的小数据(如AES密钥)
三、算法对比与适用场景
算法类型
代表算法
核心特点
适用场景
缺点
哈希算法
MD5、SHA-256
不可逆,固定长度输出
密码存储(加盐)、文件完整性校验
MD5安全性低;哈希值无法反推原始数据
对称加密
AES
速度快,加密解密用同一密钥
大量数据加密(如API请求体、本地存储数据)
密钥传输需安全(易泄露)
非对称加密
RSA
安全性高,公钥加密私钥解密
密钥交换(如加密AES密钥)、数字签名
速度慢,不适合大量数据
四、前端加密的注意事项
密钥管理 :前端密钥(如AES密钥、RSA公钥)需动态获取(如从后端接口请求),禁止硬编码在JS代码中(易被破解);
加密范围 :前端仅加密敏感数据(如密码、银行卡号),非敏感数据无需加密(浪费性能);
配合后端 :前端加密不能替代后端加密,后端需对接收的数据再次验证和加密存储;
避免误区 :MD5/SHA等哈希算法不适合直接存储密码(需配合随机盐值Salt,防止彩虹表攻击)。
总结 前端加密是保护数据安全的重要手段,需根据场景选择算法:
验证数据完整性或存储密码(加盐)→ 用SHA-256;
加密大量敏感数据(如本地存储、API请求)→ 用AES;
加密小数据(如密钥交换、签名)→ 用RSA。
同时,需结合后端加密、HTTPS等措施,构建完整的安全体系。
19.如何防范前端的恶意代码注入?(如过滤危险标签和属性、使用安全的 API) 前端恶意代码注入最常见的形式是XSS(跨站脚本攻击) ,即攻击者通过输入或其他方式将恶意脚本(如<script>标签、onclick事件等)注入到页面中,导致脚本被浏览器执行,进而窃取用户数据(如Cookie、Token)、篡改页面内容或发起进一步攻击。
防范前端恶意代码注入的核心思路是:“不信任任何用户输入” ,通过输入过滤、输出编码、使用安全API等手段,阻止恶意脚本被解析和执行。以下是具体实现方案:
一、严格过滤危险标签和属性(输入验证) 对用户输入的内容(如表单提交、评论、URL参数等)进行白名单过滤 ,仅允许安全的标签和属性,过滤或移除可能执行脚本的危险内容。
1. 过滤危险HTML标签 攻击者常通过<script>、<iframe>、<img onerror>等标签注入脚本,需过滤掉这些危险标签或限制允许的标签(白名单)。
示例:基础标签过滤(用正则)
// 仅允许常见的安全标签(白名单):p、div、span、b、i等
function filterDangerousTags(html) {
if (!html) return '';
// 保留白名单标签,过滤其他标签(如script、iframe、svg等)
return html.replace(/<\/?[^>]+>/gi, (tag) => {
// 白名单标签列表
const safeTags = ['p', 'div', 'span', 'b', 'i', 'em', 'strong', 'br', 'h1', 'h2', 'h3'];
// 提取标签名(如<div> → div)
const tagName = tag.replace(/<\/?|>/g, '').toLowerCase();
// 若不在白名单中,移除标签(保留内容)
return safeTags.includes(tagName) ? tag : tag.replace(/<|>/g, '');
});
}
// 使用示例:过滤包含<script>的恶意输入
const userInput = '<script>alert("XSS")</script><p>安全内容</p>';
const safeHtml = filterDangerousTags(userInput);
console.log(safeHtml); // 输出:alert("XSS")<p>安全内容</p>(<script>标签被移除)
2. 过滤危险属性(尤其是事件属性) 即使标签安全,危险属性(如onclick、onerror、href="javascript:")也可能执行脚本,需过滤这些属性。
示例:过滤危险属性
function filterDangerousAttrs(html) {
if (!html) return '';
// 危险属性列表(事件属性、javascript协议等)
const dangerousAttrs = [
/on\w+/gi, // 匹配所有on开头的事件属性(onclick、onerror等)
/href\s*=\s*["']javascript:/gi, // 匹配href="javascript:"
/src\s*=\s*["']javascript:/gi // 匹配src="javascript:"
];
let safeHtml = html;
// 移除所有危险属性
dangerousAttrs.forEach(reg => {
safeHtml = safeHtml.replace(reg, '');
});
return safeHtml;
}
// 使用示例:过滤onclick和javascript:协议
const userInput = '<img src=x onerror=alert("XSS")><a href="javascript:stealData()">点击</a>';
const safeHtml = filterDangerousAttrs(userInput);
console.log(safeHtml);
// 输出:<img src=x ><a href="">点击</a>(危险属性被移除)
3. 使用成熟的过滤库(推荐) 手动过滤难以覆盖所有场景(如嵌套标签、特殊编码绕过),推荐使用专业的HTML过滤库(如DOMPurify),它能处理复杂的XSS绕过手段。
二、使用安全的API(避免危险的DOM操作) 前端许多DOM操作API会直接解析HTML或执行脚本,需优先使用“仅处理纯文本”的安全API,避免使用可能触发脚本执行的危险API。
1. 避免危险API,使用安全替代方案
危险API(可能执行脚本)
安全替代方案(仅处理纯文本)
说明
element.innerHTML
element.textContent
innerHTML会解析HTML,textContent仅渲染文本
document.write()
element.appendChild()
document.write可能覆盖页面或执行脚本,改用DOM方法添加节点
eval()、new Function()
避免使用(或严格验证输入)
执行字符串为代码,易被注入恶意脚本
setTimeout(string, time)
setTimeout(function, time)
第一个参数为字符串时会被解析为代码
示例:用textContent替代innerHTML
// 危险:使用innerHTML,若userInput含恶意脚本会被执行
const userInput = '<script>alert("XSS")</script>';
// document.getElementById('content').innerHTML = userInput; // 危险!
// 安全:使用textContent,内容会被当作纯文本显示
document.getElementById('content').textContent = userInput;
// 页面显示:<script>alert("XSS")</script>(脚本不会执行)
2. 动态生成DOM时的安全实践 动态创建元素时,避免直接拼接HTML字符串,改用document.createElement等API分步创建,明确设置属性(避免危险属性)。
错误示例(拼接HTML字符串) :
const userId = '<img src=x onerror=alert("XSS")>'; // 恶意用户输入
// 危险:直接拼接HTML,恶意代码会被执行
document.body.innerHTML += `<div>用户ID:${userId}</div>`;
正确示例(分步创建DOM) :
const userId = '<img src=x onerror=alert("XSS")>'; // 恶意输入
const div = document.createElement('div');
div.textContent = `用户ID:${userId}`; // 用textContent设置文本,避免解析HTML
document.body.appendChild(div);
// 页面显示:用户ID:<img src=x onerror=alert("XSS")>(脚本不执行)
三、输出编码(根据上下文进行转义) 当必须将用户输入插入到HTML、CSS、JS等上下文时,需根据所处环境进行针对性编码 ,将特殊字符转换为安全的实体形式,防止被解析为代码。
1. HTML上下文编码(最常见) 在HTML标签内插入内容时,需将<、>、&、"、'等特殊字符转换为HTML实体(如<→<、>→>)。
示例:HTML编码函数
function htmlEncode(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 使用示例
const userInput = '<script>alert("XSS")</script>';
const encoded = htmlEncode(userInput);
// 输出:<script>alert("XSS")</script>
document.getElementById('content').innerHTML = encoded; // 显示为原文本,不执行脚本
2. 其他上下文编码
CSS上下文 :若用户输入作为CSS值(如style="color: {input}"),需限制输入为安全值(如仅允许颜色名或十六进制色值),或使用CSS编码;
JS上下文 :若用户输入作为JS变量(如var x = "{input}"),需使用JSON编码(如JSON.stringify()),避免闭合引号注入脚本;
URL上下文 :若用户输入作为URL参数(如href="http://example.com?param={input}"),需用encodeURIComponent()编码。
四、内容安全策略(CSP,从浏览器层面拦截) CSP(Content-Security-Policy)是浏览器提供的安全机制,通过后端设置响应头,限制页面可加载的资源(脚本、样式、图片等)和脚本执行方式 ,即使恶意代码注入成功,也会被浏览器拦截。
1. CSP核心规则(防御XSS)
default-src 'self':默认仅允许加载同域资源;
script-src 'self':仅允许执行同域脚本(禁止执行inline脚本、eval()、外部域脚本);
style-src 'self':仅允许加载同域样式;
img-src 'self' data::仅允许加载同域图片和data协议图片(防止img onerror注入)。
2. 配置示例(后端设置响应头) # Nginx配置示例
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'";
配置后,若页面中存在<script>alert("XSS")</script>等inline脚本,或加载外部域恶意脚本,浏览器会直接拦截并报错,阻止执行。
五、其他辅助措施
设置Cookie的HttpOnly属性 :禁止JS读取Cookie,即使发生XSS,攻击者也无法窃取Cookie;
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
使用iframe的sandbox属性 :嵌入第三方内容时,通过sandbox限制权限(如allow-scripts禁止执行脚本);
<!-- 禁止iframe中的脚本执行、表单提交等 -->
<iframe src="https://third-party.com" sandbox></iframe>
定期更新依赖 :第三方库(如jQuery、UI组件)可能存在XSS漏洞,需及时更新修复;
前端框架的自动转义 :现代框架(Vue、React)默认会对插值内容进行转义(如Vue的{{ }}、React的{}),避免直接使用v-html、dangerouslySetInnerHTML等危险指令(如需使用,必须先过滤)。
总结 防范前端恶意代码注入需构建多层防御体系 :
输入过滤 :用白名单+专业库(如DOMPurify)过滤危险标签和属性;
输出编码 :根据上下文(HTML/CSS/JS)对用户输入进行转义;
安全API :优先使用textContent等安全方法,避免innerHTML、eval;
浏览器机制 :通过CSP限制脚本执行,设置HttpOnly保护Cookie;
框架与依赖 :利用框架自动转义,定期更新依赖修复漏洞。
核心原则:“不信任任何用户输入,始终假设输入是恶意的” ,通过多重验证和限制,将风险降到最低。
20.简述前端安全开发的最佳实践(如输入验证、输出编码、最小权限原则、定期更新依赖) 前端安全开发的核心目标是降低数据泄露、恶意攻击(如XSS、CSRF)、服务中断等风险 ,需从“输入处理、数据传输、存储、权限控制、依赖管理”等全链路建立防护体系。以下是前端安全开发的最佳实践:
一、严格的输入验证:拒绝“脏数据”进入系统 核心原则 :“不信任任何用户输入”,对所有外部输入(表单、URL参数、第三方数据)进行严格校验,过滤恶意内容。
白名单验证优先 : 仅允许符合预期格式的输入(如手机号、邮箱、用户名),用正则表达式或成熟库(如validator.js)定义白名单。例如:
手机号仅允许11位数字;
用户名限制为字母、数字、下划线,长度4-16位;
禁止输入<script>、onclick等可能包含恶意脚本的内容。
全场景覆盖 : 不仅验证用户主动输入(如表单),还需校验间接输入(如URL的query参数、localStorage中读取的数据、第三方接口返回的不可信数据)。
前后端双重验证 : 前端验证作为“用户体验优化”(快速反馈错误),后端必须再次验证(前端验证可被绕过),避免单一防线失效。
二、输出编码:防止恶意代码被解析执行 核心原则 :当用户输入或不可信数据需要插入到页面(HTML、CSS、JS、URL等上下文)时,需根据场景进行针对性编码 ,将特殊字符转换为“安全实体”,阻止浏览器将其解析为可执行代码。
三、遵循最小权限原则:仅授予必要的权限 核心原则 :系统各组件(如Cookie、iframe、API接口)仅拥有完成功能所需的最小权限,减少被攻击时的影响范围。
Cookie安全配置 :
HttpOnly:禁止JS读取Cookie(防XSS窃取);
Secure:仅允许HTTPS传输Cookie;
SameSite:限制跨域请求携带Cookie(如SameSite=Lax或SameSite=Strict,防CSRF);
Max-Age:设置合理过期时间(如2小时),减少泄露后的风险。
iframe权限控制 : 嵌入第三方内容时,用sandbox属性限制权限(如sandbox="allow-same-origin"仅允许同域操作,禁止allow-scripts可阻止脚本执行)。
API数据最小化 : 后端接口仅返回前端必需的字段(如用户资料无需返回完整身份证号,仅返回****1234),避免过度暴露敏感数据。
功能权限隔离 : 敏感操作(如支付、修改密码)需单独验证(如二次密码、验证码),与普通操作隔离,防止权限滥用。
四、强化传输与存储安全:保护数据全生命周期
强制使用HTTPS : 所有请求(尤其是登录、支付、API调用)必须通过HTTPS传输,利用TLS加密防止中间人攻击。前端可通过代码检测并强制跳转HTTPS:
if (window.location.protocol !== 'https:') {
window.location.href = `https:${window.location.href.substring(window.location.protocol.length)}`;
}
敏感数据加密存储 :
禁止明文存储敏感数据(如密码、Token),用AES等对称加密算法加密后再存入localStorage/sessionStorage;
优先选择内存存储(如Vue/React的state)或sessionStorage(会话级,关闭标签页后删除),避免localStorage持久化存储;
超高敏感数据(如银行卡号)不存储在前端,用完即销毁。
避免URL暴露敏感信息 : 不将Token、用户ID等放在URL参数中(如https://example.com?token=xxx),URL可能被浏览器日志、代理服务器记录,应放在请求头(如Authorization)或请求体中。
五、防御常见攻击:针对性阻断已知风险
六、依赖管理:消除第三方库漏洞
定期更新依赖 : 第三方库(如npm包)可能存在已知漏洞(如XSS、代码执行漏洞),需定期用npm audit、snyk等工具扫描,及时更新到安全版本。
最小化依赖引入 : 仅引入必要的库,避免冗余依赖(依赖越多,漏洞风险越高)。例如:无需完整jQuery时,用轻量库(如zepto)替代。
审查第三方代码 : 引入未知库前,检查其源码或社区评价,避免使用被植入恶意代码的“钓鱼库”(如名称类似知名库的恶意包)。
七、安全的错误处理与日志:不泄露敏感信息
八、安全测试与流程规范:提前发现风险
自动化安全扫描 : 集成工具(如ESLint的安全规则、OWASP ZAP)在开发阶段扫描代码,检测潜在XSS、CSRF等风险。
渗透测试 : 定期模拟攻击者视角(如尝试注入脚本、伪造请求),验证防护措施有效性。
安全编码规范 : 团队制定明确的安全规则(如“禁止用innerHTML插入用户输入”“Cookie必须加HttpOnly”),并通过代码审查确保执行。
总结 前端安全是“多层防御体系”,核心在于“不信任任何外部输入,最小化权限暴露,全链路保护数据”。需结合输入验证、输出编码、HTTPS、安全存储、依赖管理等措施,并通过持续测试和规范流程,将安全风险降到最低。记住:前端安全无法单独保障系统安全,必须与后端安全(如接口验证、数据加密存储)协同,形成完整的安全闭环。