feat(auth): 实现首页路由的认证逻辑并优化静态资源服务
- 在客户端添加首页路由的认证逻辑,提高用户体验 - 优化静态资源服务配置,增强安全性 - 添加 URL 解析和保存功能,为后续的路由认证做准备 - 调整中间件顺序和配置,提升应用性能和安全性
This commit is contained in:
parent
43a2f31eda
commit
b24dee2ef9
@ -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
42
gateway/config/urls.go
Normal 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
50
gateway/config/urls.json
Normal 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"
|
||||
]
|
||||
}
|
||||
@ -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)
|
||||
|
||||
|
||||
31
gateway/middleware/route_auth.go
Normal file
31
gateway/middleware/route_auth.go
Normal 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()
|
||||
}
|
||||
}
|
||||
71
gateway/middleware/static_auth.go
Normal file
71
gateway/middleware/static_auth.go
Normal 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()
|
||||
}
|
||||
}
|
||||
65
gateway/utils/html_parser.go
Normal file
65
gateway/utils/html_parser.go
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user