Revel 不能像 Laravel Passport 那样“开箱即用”做 SSO,因其是无状态轻量级 Go 框架,缺乏内置认证抽象层(如 Passport::routes() 或 AuthServiceProvider),且默认 session 基于 cookie + host 绑定,无法跨域共享,必须依赖 OAuth 2.0/ OIDC 对接独立 IDP 才能实现安全 SSO。

Revel 框架本身不内置 SSO 支持,直接复用 session 或硬编码跳转无法跨域生效,必须依赖标准协议(OAuth 2.0 / OpenID Connect)对接独立 IDP。
为什么 Revel 不能像 Laravel Passport 那样“开箱即用”做 SSO
Revel 是无状态、轻量级的 Go Web 框架,它没有类似 Passport::routes() 或 AuthServiceProvider 这类认证抽象层。它的 session 默认基于 cookie + 内存/redis 存储,且签名强绑定 app.secret 和请求 host —— 换句话说,a.example.com 写的 session cookie 在 b.example.com 根本读不到,更别说跨协议透传了。
常见错误现象包括:
- 用户在 IDP 登录后跳回 Revel 应用,
session.Get("user_id")始终为空 - 手动 set-cookie 到根域(如
.example.com),但因 TLS/Secure 标志或 SameSite 策略被浏览器拦截 - 试图用
http.SetCookie()复制 IDP 返回的 JWT,结果签名校验失败或过期时间错乱
推荐路径:用 OAuth 2.0 授权码模式对接 Keycloak / Auth0 / 自建 OIDC Provider
这是目前最可控、可审计、且 Revel 能干净集成的方式。核心是让 Revel 充当 OAuth 客户端(RP),不碰用户凭证,只处理回调和令牌交换。
实操要点:
- 在 IDP 中注册 Revel 应用,获取
client_id、client_secret、redirect_uri(必须精确匹配,如https://app.example.com/auth/callback) - 使用
golang.org/x/oauth2构建配置,Endpoint必须设为 IDP 的authURL和tokenURL(例如 Keycloak 是/realms/{realm}/protocol/openid-connect/auth) - 回调 handler 中调用
oauth2.Config.Exchange()换取*oauth2.Token,再用它请求 IDP 的userinfo端点解出用户身份 - 不要把原始
access_token存 session;建议解析 JWT claims 后提取sub、email、groups,存入 Revel 的session.Set("user_email", email)
Revel 中必须改写的两个关键 handler
你得自己实现登录入口和 OAuth 回调逻辑,不能依赖 Revel 的 AuthFilter 自动拦截 —— 它只认本地 session,不认识外部 token。
示例片段(简化版):
func (c App) Login() revel.Result {
// 生成 state 随机值防 CSRF,存入 session
state := generateState()
c.Session.Set("oauth_state", state)
url := oauthConfig.AuthCodeURL(state, oauth2.AccessTypeOnline)
return c.Redirect(url)
}
func (c App) Callback() revel.Result {
// 校验 state
if c.Params.Get("state") != c.Session.Get("oauth_state") {
return c.RenderError("invalid state")
}
// 换 token
token, err := oauthConfig.Exchange(c.Request.Context(), c.Params.Get("code"))
if err != nil {
return c.RenderError("exchange failed: " + err.Error())
}
// 解 userinfo
client := oauthConfig.Client(c.Request.Context(), token)
resp, _ := client.Get("https://idp.example.com/realms/myrealm/protocol/openid-connect/userinfo")
defer resp.Body.Close()
var userInfo map[string]interface{}
json.NewDecoder(resp.Body).Decode(&userInfo)
c.Session.Set("user_id", userInfo["sub"])
c.Session.Set("user_email", userInfo["email"])
return c.Redirect(App.Index)
}
容易被忽略的三个细节
很多团队卡在最后一步不是逻辑问题,而是部署和协议细节:
-
redirect_uri必须带 trailing slash 或完全一致 —— Revel 默认路由不自动补斜杠,而 Keycloak/Auth0 对此敏感 - Revel 的
app.mode = PROD下,session默认启用Securecookie,若没配 HTTPS,浏览器会拒绝写入,导致回调后 session 丢失 - IDP 返回的
userinfo字段名不统一:Auth0 返回email,Keycloak 可能是email或需开启emailmapper,否则userInfo["email"]为nil











