前言:需求是判断用户是否购买了资源文件,如果没有购买则不允许下载
最传统的解决方案是通过SpringBoot后端直接鉴权并传输文件到浏览器,但是Tomcat对于大文件大量静态资源请求的处理效率确实堪忧。
我的想法是通过Nginx传输静态文件,但在nginx接受到请求时先将参数转发到后端接口,由后端接口判断权限,通过之后nginx再传输文件给浏览器
实现目标:
- 浏览器访问http://192.168.78.133/file/abcd.7z?token=tokenvalue
- Nginx截取文件名 abcd.7z和token值tokenvalue转发到后端接口http://192.168.78.133:8080/auth/file?file=abcd.7z&token=tokenvalue
- 后端判断权限和文件,没有权限返回到浏览器401,没有文件返回到浏览器404
sequenceDiagram
浏览器->>Nginx: http://192.168.78.133/file/abcd.7z?token=tokenvalue
Nginx--)后端接口: http://192.168.78.133:8080/auth/file?file=abcd.7z&token=tokenvalue
后端接口--)Nginx: 响应状态码 200 401 404
Nginx-) 浏览器: 200时正常传输文件,其它状态码则直接转给浏览器
准备工作:Nginx(版本高于1.5.4),这里我使用的是宝塔一键环境配置Nginx和auth_request模块
Nginx配置
server
{
listen 80;
server_name 192.168.78.133;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/ftp;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
#error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-00.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/192.168.78.133.conf;
#REWRITE-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
#对指定路径下的访问进行鉴权配置
location /file
{
set $token $arg_token; #获取url中param参数的token赋值给变量
#没有token时返回401,不必要再请求后端接口
if ($token = "")
{
return 401;
}
if ($request_uri ~* ^/(.*)/([^/]+)$) {
set $file $2;
}
#没有获取到文件名时直接返回404
if ($file = "") {
return 404;
}
#将请求转发给鉴权接口
auth_request /auth;
}
# 鉴权接口路径代理
location /auth
{
#仅允许nginx内部访问,外部访问404
internal;
#这个配置是不需要转发请求体相关的内容,参考的其它博主的配置
proxy_pass_request_body off;
proxy_set_header Content-Length "";
#比较常规的反向代理配置,如果有其它需求可以自己再添加
proxy_set_header host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#重点是这里,将token和file变量的内容转发给后端接口
proxy_pass "http://192.168.78.1:8081/user/resource/auth?token=$token&file=$file";
}
access_log /www/wwwlogs/192.168.78.133.log;
error_log /www/wwwlogs/192.168.78.133.error.log;
}
SpringBoot接口
@Operation(summary = "资源文件鉴权",description = "资源文件鉴权,用于nginx鉴权认证")
@GetMapping("/resource/auth")
public void checkResourceAuth(@RequestParam(required = false) String token,@RequestParam(required = false) String file,HttpServletResponse response){
//判断token是否存在
if(!StringUtils.hasText(token)){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
//判断token是否有效,并根据token获取用户主键
/**
* 我使用的是Sa-token框架,没用SpringSecurity的原因是SpringSecurity太重,项目体量小用不上,还有一个原因是SpringSecurity没学透彻,大佬们有什么比较好的学习资料请分享一下,谢谢
*/
Object loginId = StpUtil.getStpLogic().getLoginIdByToken(token);
if(loginId==null){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
//校验文件名是否未空
if(!StringUtils.hasText(file)){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//TODO 校验文件是否存在
//校验用户是否拥有该文件
if(checkUserHaveFile(Long.valueOf(loginId.toString()),file)){
response.setStatus(HttpServletResponse.SC_OK);
}else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}