Skip to content

2023/03/03: 如何为 NextJS 静态页面做鉴权保护? #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wangpin34 opened this issue Mar 3, 2023 · 0 comments
Open

2023/03/03: 如何为 NextJS 静态页面做鉴权保护? #129

wangpin34 opened this issue Mar 3, 2023 · 0 comments
Labels

Comments

@wangpin34
Copy link
Owner

wangpin34 commented Mar 3, 2023

背景

我有一个文档网站,底层技术用的是 NextJS,所有页面都是 build time 生成,最终都是静态页面。我想要实现这样的效果:

  1. 当用户没有登录时,跳转到 sign in 页面
  2. 当登录成功时,跳转到之前请求的配置。

假设用户起初访问的是 /welcome,因为没有登录,所以跳转到 /sign_in?redirect=welcome。登录成功,则跳转到 /welcome 页面。

不太理想的尝试

对于 Auth ,NextJS 官网给了两种思路。第一种,页面load到浏览器后,用浏览器 js 判定登录状态和跳转。第二种,用 server page 判定登录状态和跳转。
第一种方案,实现比较简单,问题是 page props 会先 load 到页面,再判定登录状态和跳转。存在敏感数据泄露的可能,不能满足数据保护的要求。
第二种方案,实现稍微要复杂一些,也能达到数据保护的目的。但是相应的,会失去静态页面的便利性和安全性。得不偿失。

为什么不定制 NextJS Server

通过替换 NextJS 标准 server,加入拦截逻辑,也可以做到判定登录,以及跳转,官网中的示例表明这是一种可行的做法。https://nextjs.org/docs/advanced-features/custom-server。但这是有代价的,对我来说非常重要的 static gen 功能会得到削弱(具体数值没测试过)

Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.

所以,思考再三,我决定跳出 NextJS ,寻找一种更加简便的办法:它应该足够解耦,不需要修改 NextJS 的 server,或者 page generation。这并不需要花费很多力气,现在毕竟不是互联网的蛮荒时期,啥都没有。我很快就找到了自己的目标: Nginx。

Nginx

首先,NextJS 这里准备一个 auth api,比如 /api/auth,用来判定 http 请求携带的 cookie 是否包含合法可用的 token。
然后,通过 Nginx 的 auth_request_module,拦截请求,转发给 /api/auth 处理,然后根据返回状态码,跳转到 sign_in 或者其他页面。
以下是 Nginx 的配置(忽略 server 的其他无关主题的配置)。 注:http://localhost:3030 是 NextJS App 的地址。

  # 静态内容请求直接转发给 NextJS,并且设定 1 year cache
  location ~* \.(gif|jpg|jpeg|svg|png|ico|woff|woff2|js|css|json|yaml)$ {
    expires 1y;
    add_header Cache-Control "public";
    proxy_pass              http://localhost:3030;
  }
  location ~* /(sign_in|sign_up) {
    proxy_pass              http://localhost:3030;
  }
  location /  {
    auth_request     /auth;
    auth_request_set $auth_status $upstream_status;
    error_page 401  =302 http://$http_host/signin?redirect=$request_uri;
    proxy_pass              http://localhost:3030;
  }
  location = /auth {
    internal;
    proxy_pass              http://localhost:3030/api/auth;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        X-Original-URI $request_uri;
  }

简单解释一下:

  1. sign_in 和 sign_up 页面直接返回 NextJS App 的页面,不做鉴权。
  2. 其他页面进入鉴权模块 auth_request,这里 /auth 表示负责鉴权的 uri。
  3. /auth 这里,会将请求转交给 NextJS 的 /api/auth 接口处理。
  4. 当前一步结束, error_page 会检测之前(/auth)处理的结果 http status 是否为 401,是的话,返回 302 指向 sign_in 页面。注意这里使用了很多内置参数
  5. 如果 http status 不是 401,则继续由 Nginx App 提供页面内容。

结论

NextJS 本身无法兼顾静态和服务端鉴权,此时,可以将鉴权交由 Nginx 负责,通过它的 auth request 模块来转发请求,处理鉴权结果。
参考资料:

ps. Nginx 的 tag 居然是存在的,这证明之前也写过 Nginx 相关的东西。感觉每次都是 Nginx 救场,哈哈。

@wangpin34 wangpin34 changed the title 2021/07/23: 如何为 NextJS 静态页面做鉴权保护? 2023/03/03: 如何为 NextJS 静态页面做鉴权保护? Mar 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant