feat: update openapi page logic
This commit is contained in:
+28
-12
@@ -182,23 +182,39 @@ func SwaggerAuth() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取请求中携带的凭证
|
||||||
|
// 1. URL 参数 token
|
||||||
|
// 2. Cookie 中的 openapi_token
|
||||||
|
// 3. HTTP Basic Auth
|
||||||
|
tokenQuery := c.Query("token")
|
||||||
|
tokenCookie, _ := c.Cookie("openapi_token")
|
||||||
_, password, hasAuth := c.Request.BasicAuth()
|
_, password, hasAuth := c.Request.BasicAuth()
|
||||||
// 允许使用任意用户名,但密码必须匹配 OpenAPI Token
|
|
||||||
if hasAuth && password == tokenConfig.Token && tokenConfig.Token != "" {
|
var providedToken string
|
||||||
|
if tokenQuery != "" {
|
||||||
|
providedToken = tokenQuery
|
||||||
|
} else if tokenCookie != "" {
|
||||||
|
providedToken = tokenCookie
|
||||||
|
} else if hasAuth {
|
||||||
|
providedToken = password
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查提供的 token 是否匹配
|
||||||
|
if providedToken != "" && providedToken == tokenConfig.Token {
|
||||||
|
// 如果是通过 url 参数进来的,自动将其种入 Cookie,便于后续加载静态资源 (如 json)
|
||||||
|
if tokenQuery != "" {
|
||||||
|
c.SetCookie("openapi_token", providedToken, 86400, "/openapi", "", false, false)
|
||||||
|
}
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未提供认证,触发浏览器登录弹窗
|
// 验证失败,不再返回 WWW-Authenticate 头触发浏览器反人类原生弹窗
|
||||||
if !hasAuth {
|
// 我们返回 401 的 JSON 或纯文本结构,以便由调用方自行接管鉴权逻辑
|
||||||
c.Header("WWW-Authenticate", `Basic realm="OpenAPI Access Token (Any username)"`)
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
c.Status(http.StatusUnauthorized)
|
"code": 401,
|
||||||
c.Abort()
|
"msg": "OpenAPI 访问未授权或 Token 错误",
|
||||||
return
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 认证失败 (密码错误),返回 404 隐藏路由
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,9 @@ func initOpenAPIRoutes(root *gin.RouterGroup, urlPrefix string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行认证
|
// 执行主站 Cookie 认证
|
||||||
middleware.SwaggerAuth()(c)
|
middleware.AuthRequired()(c)
|
||||||
if c.IsAborted() {
|
if c.IsAborted() {
|
||||||
// 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它)
|
|
||||||
if c.Writer.Status() == http.StatusNotFound {
|
|
||||||
serveSPA(c, urlPrefix, 404)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,14 @@ async function copyOpenapiToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSwaggerDocs() {
|
||||||
|
if (!form.value.openapi_enabled) {
|
||||||
|
toast.error('必须先开启 OpenAPI 才能查看接口文档页面')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.open(`${baseUrl}/openapi/index.html`, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(loadSettings)
|
onMounted(loadSettings)
|
||||||
</script>
|
</script>
|
||||||
@@ -240,20 +248,20 @@ onMounted(loadSettings)
|
|||||||
class="font-normal text-xs bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20 whitespace-nowrap">推荐方式</Badge>
|
class="font-normal text-xs bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20 whitespace-nowrap">推荐方式</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between sm:justify-end w-full sm:w-auto gap-4">
|
<div class="flex items-center justify-between sm:justify-end w-full sm:w-auto gap-4">
|
||||||
<a :href="baseUrl + '/openapi/index.html'" target="_blank"
|
<a href="#" @click.prevent="openSwaggerDocs"
|
||||||
class="flex items-center gap-1 text-xs text-blue-600 hover:underline shrink-0">
|
class="flex items-center gap-1 text-xs text-blue-600 hover:underline shrink-0">
|
||||||
查看接口文档
|
查看接口文档
|
||||||
<ExternalLink class="w-3 h-3" />
|
<ExternalLink class="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
<Switch v-model="form.openapi_enabled" id="openapi-enabled" />
|
<Switch v-model="form.openapi_enabled" id="openapi-enabled" />
|
||||||
<Label for="openapi-enabled" class="text-xs cursor-pointer">开启鉴权</Label>
|
<Label for="openapi-enabled" class="text-xs cursor-pointer">开启 OpenAPI</Label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-muted-foreground mb-4">开启全局 OpenAPI 直接访问能力,配置后可通过请求头 <code
|
<p class="text-sm text-muted-foreground mb-4">开启全局 OpenAPI 直接访问能力,配置后可通过请求头 <code
|
||||||
class="bg-muted px-1.5 py-0.5 rounded text-xs select-all font-sans">Authorization: Bearer <在此生成的Token></code>
|
class="bg-muted px-1.5 py-0.5 rounded text-xs select-all font-sans">Authorization: Bearer <在此生成的Token></code>
|
||||||
无需登录直接调用系统的所有接口,请妥善保管并设置合理的有效期。<span class="text-amber-600 dark:text-amber-500 font-medium ml-1">注意:必须先开启鉴权开关才能查看接口文档页面和进行调用。</span></p>
|
以第三方身份调用系统的所有接口,请妥善保管 Token 并设置合理的有效期。<span class="text-amber-600 dark:text-amber-500 font-medium ml-1">注意:必须先开启本功能才能查看接口文档页面和对接调用。</span></p>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4 mb-4">
|
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4 mb-4">
|
||||||
<Label class="sm:text-right text-muted-foreground">Token 密钥</Label>
|
<Label class="sm:text-right text-muted-foreground">Token 密钥</Label>
|
||||||
@@ -303,5 +311,6 @@ onMounted(loadSettings)
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user