feat(auth): 实现首页路由的认证逻辑并优化静态资源服务

- 在客户端添加首页路由的认证逻辑,提高用户体验
- 优化静态资源服务配置,增强安全性
- 添加 URL 解析和保存功能,为后续的路由认证做准备
- 调整中间件顺序和配置,提升应用性能和安全性
This commit is contained in:
高手 2025-02-16 21:46:23 +08:00
parent 43a2f31eda
commit b24dee2ef9
7 changed files with 311 additions and 11 deletions

View File

@ -4,6 +4,31 @@ import UserInfo from "./components/UserInfo.vue";
export default defineClientConfig({
enhance({ app, router }) {
app.component("UserInfo", UserInfo);
// 仅在客户端执行
if (typeof window !== 'undefined') {
router.beforeEach(async (to, from, next) => {
// 检查是否是首页
if (from.fullPath === '/' && to.fullPath !== '/') {
try {
// 复用 UserInfo 组件中相同的接口检查登录状态
const response = await fetch('/api/user/info')
if (!response.ok) {
// 未登录状态使用非SPA的路由跳转方式
window.location.href = to.fullPath
return
}
} catch (error) {
console.error('检查登录状态失败:', error)
// 发生错误时同上
window.location.href = to.fullPath
return
}
}
next()
})
}
},
setup() {
// 这里可以添加全局的设置

42
gateway/config/urls.go Normal file
View File

@ -0,0 +1,42 @@
package config
import (
"encoding/json"
"os"
"path/filepath"
)
// URLConfig 存储所有URL配置
type URLConfig struct {
URLs []string `json:"urls"`
}
// SaveURLsToFile 将URLs保存到文件
func SaveURLsToFile(urls []string, filename string) error {
config := URLConfig{
URLs: urls,
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
}
// GetProtectedURLs 从文件中读取需要保护的URLs
func GetProtectedURLs() ([]string, error) {
configPath := filepath.Join("config", "urls.json")
data, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
var config URLConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return config.URLs, nil
}

50
gateway/config/urls.json Normal file
View File

@ -0,0 +1,50 @@
{
"urls": [
"/assets/index.html-6M4WaJGn.js",
"/assets/10.html-Cox3wLdF.js",
"/assets/index.html-iJcgfwSj.js",
"/assets/intro.html-QyfAbIVc.js",
"/assets/relation.html-Dqaw_w5f.js",
"/assets/simple.html-gWbsxbST.js",
"/assets/tomb.html-PQqy8bGV.js",
"/assets/address.html-CNNc9o0U.js",
"/assets/branch_list.html-vb7nrwTF.js",
"/assets/build_draft.html-AXYMqBRq.js",
"/assets/catalogue.html-FX5AkOsY.js",
"/assets/ink_presequence.html-BYSWwNUl.js",
"/assets/intro.html-BmWXutnI.js",
"/assets/origin_narrate.html-qihX0gD2.js",
"/assets/index.html-DTsg6_Mb.js",
"/assets/thank_support.html-Bec3hAfD.js",
"/assets/third_intro.html-DCYwR1dS.js",
"/assets/third_renew_record.html-Bw0dPE0f.js",
"/assets/whole_intro.html-Dxm36Rz4.js",
"/assets/yu_ink_intro.html-8081fdp9.js",
"/assets/余氏创修名目.html-UBd1EHMQ.js",
"/assets/续修宗谱序.html-CC03qlYI.js",
"/assets/西分祖一公总序.html-IqyDmuhg.js",
"/assets/长塘湾支祖序.html-D138eKRX.js",
"/assets/10.html-9er2Kamf.js",
"/assets/address.html-DGl_p7BY.js",
"/assets/banqiao_tomb_record.html-ZndFRXyq.js",
"/assets/catalogue.html-C390bTAm.js",
"/assets/hongshun_record.html-DPlvV_FX.js",
"/assets/intro.html-D09epTqh.js",
"/assets/jiangxi-find-origin.html-DYa2pI1G.js",
"/assets/meeting-record.html-DwOgR3Y5.js",
"/assets/proposal.html-B0yl1179.js",
"/assets/qitou_build_tomb_record.html-w1QQPg58.js",
"/assets/index.html-B8MyB7Nx.js",
"/assets/rebuild-origin-tomb.html-D6GwIgqw.js",
"/assets/rebuild-temple.html-yg6_cE67.js",
"/assets/rebuild.html-Bksfc_AQ.js",
"/assets/renew_family_disciplines.html-C6HeQ_LO.js",
"/assets/rule.html-C6rqyb74.js",
"/assets/simple.html-Co37wF1v.js",
"/assets/thank_support.html-Cb7EtcXi.js",
"/assets/the_fourth_postscript.html-S4JLTXyo.js",
"/assets/the_fourth_public.html-CNkGkbSJ.js",
"/assets/tianjue-intro.html-D1lSLpRz.js",
"/assets/404.html-CDvZn73T.js"
]
}

View File

@ -6,6 +6,7 @@ import (
"gateway/middleware"
"gateway/models"
"gateway/utils"
"path/filepath"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
@ -14,10 +15,23 @@ import (
)
func main() {
// 初始化日志
utils.InitLogger()
// 解析static/index.html中的URLs并立即写入
hrefs, err := utils.ExtractHrefs("static/index.html")
if err != nil {
utils.Logger.Fatalf("解析HTML文件失败: %v", err)
}
// 保存URLs到config目录
configPath := filepath.Join("config", "urls.json")
if err := config.SaveURLsToFile(hrefs, configPath); err != nil {
utils.Logger.Fatalf("保存URLs失败: %v", err)
}
utils.Logger.Info("成功提取并保存URLs")
// 初始化数据库
db, err := gorm.Open("sqlite3", "family.db")
if err != nil {
@ -29,14 +43,15 @@ func main() {
// 初始化 Gin 引擎
r := gin.Default()
// 配置静态文件服务
r.Static("/assets", "./static/assets")
r.LoadHTMLGlob("templates/*")
// 配置 Session 中间件
// 配置 Session 中间件(移到最前面)
store := config.InitSessionStore()
r.Use(sessions.Sessions("mysession", store))
// 配置静态文件服务,添加认证中间件
r.Use(middleware.StaticAuthMiddleware())
r.Static("/static", "./static")
r.LoadHTMLGlob("templates/*")
// 路由配置
r.GET("/login", handlers.GetLogin)
r.POST("/login", handlers.PostLogin(db))
@ -44,14 +59,15 @@ func main() {
r.POST("/register", handlers.PostRegister(db))
r.GET("/api/user/info", handlers.GetUserInfo(db))
// 权限校验中间件
r.Use(middleware.AuthRequired())
// 添加用户信息API
// 文档页面路由
r.GET("/", handlers.ServeIndex(db))
// 权限校验中间件
//r.Use(middleware.AuthRequired())
// 添加路由认证中间件
r.Use(middleware.RouteAuthMiddleware())
// 在权限校验中间件后添加退出路由
r.GET("/logout", handlers.Logout)

View File

@ -0,0 +1,31 @@
package middleware
import (
"net/url"
"path/filepath"
"strings"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func RouteAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestPath := c.Request.URL.Path
// 如果路径没有后缀(不是静态资源)或以斜杠结尾,且不在白名单中,需要认证
if filepath.Ext(requestPath) == "" || strings.HasSuffix(requestPath, "/") {
session := sessions.Default(c)
user := session.Get("user")
if user == nil {
returnURL := c.Request.URL.String()
encodedURL := url.QueryEscape(returnURL)
c.Redirect(302, "/login?return_url="+encodedURL)
c.Abort()
return
}
}
c.Next()
}
}

View File

@ -0,0 +1,71 @@
package middleware
import (
"gateway/config"
"net/url"
"path/filepath"
"strings"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func StaticAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取受保护的URL列表
protectedURLs, err := config.GetProtectedURLs()
if err != nil {
c.AbortWithStatus(500)
return
}
// 检查当前请求路径是否在受保护列表中
requestPath := c.Request.URL.Path
// 如果是登录相关的资源,直接放行
if strings.Contains(c.Request.Referer(), "/login") ||
strings.Contains(c.Request.Referer(), "/register") {
c.Next()
return
}
// 检查是否是静态资源
if strings.HasPrefix(requestPath, "/") {
needAuth := false
// 检查是否是HTML文件
if filepath.Ext(requestPath) == ".html" {
needAuth = true
} else {
// 检查是否在受保护列表中
for _, protectedURL := range protectedURLs {
if requestPath == protectedURL {
needAuth = true
break
}
}
}
// 如果需要认证,检查用户是否已登录
if needAuth {
session := sessions.Default(c)
user := session.Get("user")
if user == nil {
if filepath.Ext(requestPath) == ".html" {
// HTML 文件重定向到登录页面
returnURL := c.Request.URL.String()
encodedURL := url.QueryEscape(returnURL)
c.Redirect(302, "/login?return_url="+encodedURL)
} else {
// 非 HTML 文件返回 401 未授权状态码
c.AbortWithStatus(401)
}
c.Abort()
return
}
}
}
c.Next()
}
}

View File

@ -0,0 +1,65 @@
package utils
import (
"os"
"strings"
"golang.org/x/net/html"
)
// ExtractHrefs 从HTML文件中提取所有rel="prefetch"的link标签的href属性
func ExtractHrefs(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
doc, err := html.Parse(file)
if err != nil {
return nil, err
}
var hrefs []string
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "link" {
var href, rel string
// 获取href和rel属性
for _, a := range n.Attr {
switch a.Key {
case "href":
href = a.Val
case "rel":
rel = a.Val
}
}
// 如果是prefetch链接且href包含.html添加到结果中
if rel == "prefetch" && href != "" && strings.Contains(href, ".html") {
hrefs = append(hrefs, href)
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
// 去重
return removeDuplicates(hrefs), nil
}
// removeDuplicates 移除重复的URL
func removeDuplicates(urls []string) []string {
seen := make(map[string]bool)
result := []string{}
for _, url := range urls {
if !seen[url] {
seen[url] = true
result = append(result, url)
}
}
return result
}