前端知识

  • 接下来的内容会是我 面试 / 遇到 不会的点,或者是掌握得不清楚的点,我会尽可能将整个流程写明白,让我自己能跑通整个流程。

浏览器的同源策略?

What

  • 当我们写好一个后端程序和前端程序的时候,前端向后端发送请求,提示CORS未通过,这通常是浏览器的同源策略导致的。
  • 同源策略是浏览器对跨源读写的限制,例子:js不能读另一个源的响应内容。
  • 链路:浏览器先执行同源策略 – 如果跨源,则需要带上CORS响应头显式访问 – 否则浏览器拦截

    同源策略是拦截策略,而CORS是服务器给浏览器的许可证明

Why

  • 这是为了防止Web里的跨源攻击,运行在某个 origin 下的脚本,不能读取其他 origin 的存储(localStorage/cookie/DOM)和响应内容;哪怕这些请求是用户本人在同一个浏览器里触发的。

How

  • 服务端通过 CORS 配置决定向哪些 Origin 返回允许头;若未允许,浏览器会拦截前端 JS 读取响应(即使请求到达并拿到 200)。
  • 校验三大部分:协议、主机名、端口
1
2
3
4
5
6
7
8
9
10
11
12
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}

XSS攻击?

What

  • XSS攻击是用户将一段脚本(js)代码伪装成评论插入到系统里,当别人展开评论的时候,这段脚本会在网站里执行(因为当你展开评论的时候,浏览器把用户输入当 HTML 解析,前端可能会将评论使用innerHTML插入进到页面里,脚本被执行)
    • 用户可能会看到奇奇怪怪的弹窗或者跳转到别的页面
    • 账号被莫名改资料
  • 如果你是权限比较高的用户,执行了恶意脚本,很可能就会让系统崩溃。

How to prevent

  • 核心原则永远不要把用户的输入当成HTML渲染
    • React默认渲染字符串是转义的,{content}
    • 坑:如果为了支持富文本(图片,代码,换行)使用了dangerouslySetInnerHTML,这样就会插入脚本。
  • 解决方法:
    1. 输出编码要默认转义,用纯文本来渲染,不允许用html
    2. 白名单清洗:如果必须要富文本,过滤掉事件脚本,事件属性,危险的url结构
    3. token尽量不要放在localstorage里,可以放在HttpOnly cookie,这样js读不到cookie
    4. 设置SameSite,有三种模式,strict,lax以及none,分别控制跨站是否自动带上cookie
  • 解决markdown富文本的XSS攻击策略
    1. 设置白名单,只允许我们可以的标签,比如p,a,strong,code等,对属性也要白名单,禁止所有的事件属性,比如on*
    2. 禁止inline script
    3. 代码块永远escape(转义)后放进code和pre里展示

JWT更安全的登录?双Token

What

  • 我发现了原来的逻辑是改Localstorage就可以让前端渲染出管理员的界面,这样不行
  • 于是引入了JWT的accessToken以及refreshToken
    • accessToken一般时间短,15-30分钟,存放在React的内存里,一刷新就没有
    • refreshToken时间在7-30天,存放在httponly的cookie里
    • 此外这个refreshToken还存放在了redis里,使用版本号控制,让非法用户强制下线

How

登录功能

  • 用户首次登陆的时候后端校验账户和密码是否合法,如果合法就签发两个token
  • access token 过期 → 前端请求业务接口返回 401
  • 前端调用 /api/v1/auth/refresh(不带 refresh token,浏览器会自动附带 HttpOnly Cookie)
  • 后端从 Cookie 里拿 refresh token,去 Redis 校验是否存在/是否有效
  • 校验通过 → 生成新 access token(和新的 refresh token),并在 Redis 里更新
  • 返回新的 access token 给前端,继续请求

    来回时间就是一个rtt

踢人功能

  • 在redis里添加tokenVersion,因为我们登陆的时候用户还是有当前的accessToken,所以他还能存活这么久
    • 加一个user:tokenVersion:<uid>在redis里,签发的时候把ver写进JWT,
    • 踢人就增加版本号,这样让他的所有旧accessToken失效。原因是我们的在redis里的逻辑是 user:tokenVersion:<uid> = N,拦截器校验时比较Redis ver,如果不一致就401