fix: 生产安全修复 + Go SDK + CAS SSO框架
安全修复: - CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证 - HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证 - HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵) - HIGH: 短信验证码熵值过低(4字节) - 提升到6字节 - HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时 - HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern 新功能: - Go SDK: sdk/go/user-management/ 完整SDK实现 - CAS SSO框架: internal/auth/cas.go CAS协议支持 其他: - L1Cache实例问题修复 - AuthMiddleware共享l1Cache - 设备指纹XSS防护 - 内存存储替代localStorage - 响应格式协议中间件 - 导出无界查询修复
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -11,12 +12,16 @@ import (
|
||||
|
||||
// SSOHandler SSO 处理程序
|
||||
type SSOHandler struct {
|
||||
ssoManager *auth.SSOManager
|
||||
ssoManager *auth.SSOManager
|
||||
clientsStore auth.SSOClientsStore
|
||||
}
|
||||
|
||||
// NewSSOHandler 创建 SSO 处理程序
|
||||
func NewSSOHandler(ssoManager *auth.SSOManager) *SSOHandler {
|
||||
return &SSOHandler{ssoManager: ssoManager}
|
||||
func NewSSOHandler(ssoManager *auth.SSOManager, clientsStore auth.SSOClientsStore) *SSOHandler {
|
||||
return &SSOHandler{
|
||||
ssoManager: ssoManager,
|
||||
clientsStore: clientsStore,
|
||||
}
|
||||
}
|
||||
|
||||
// AuthorizeRequest 授权请求
|
||||
@@ -43,6 +48,14 @@ func (h *SSOHandler) Authorize(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 redirect_uri 是否在白名单中
|
||||
if h.clientsStore != nil {
|
||||
if !h.clientsStore.ValidateClientRedirectURI(req.ClientID, req.RedirectURI) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid redirect_uri"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前登录用户(从 auth middleware 设置的 context)
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
@@ -93,7 +106,11 @@ func (h *SSOHandler) Authorize(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
token, _, err := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
// 重定向回客户端,带 token
|
||||
redirectURL := req.RedirectURI + "#access_token=" + token + "&expires_in=7200"
|
||||
@@ -136,6 +153,20 @@ func (h *SSOHandler) Token(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证客户端凭证
|
||||
if h.clientsStore != nil {
|
||||
client, err := h.clientsStore.GetByClientID(req.ClientID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client"})
|
||||
return
|
||||
}
|
||||
// 使用常量时间比较防止时序攻击
|
||||
if subtle.ConstantTimeCompare([]byte(req.ClientSecret), []byte(client.ClientSecret)) != 1 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client_secret"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 验证授权码
|
||||
session, err := h.ssoManager.ValidateAuthorizationCode(req.Code)
|
||||
if err != nil {
|
||||
@@ -144,7 +175,11 @@ func (h *SSOHandler) Token(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 生成 access token
|
||||
token, expiresAt := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
token, expiresAt, err := h.ssoManager.GenerateAccessToken(req.ClientID, session)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, TokenResponse{
|
||||
AccessToken: token,
|
||||
|
||||
Reference in New Issue
Block a user