跳到主要内容
给我们的新朋友:

Logto 是一个为现代应用和 SaaS 产品设计的 Auth0 替代方案。它提供 Cloud开源 服务,帮助你快速启动身份和管理 (IAM) 系统。享受认证 (Authentication)、授权 (Authorization) 和多租户管理 一体化

我们建议从 Logto Cloud 上的免费开发租户开始。这可以让你轻松探索所有功能。

在本文中,我们将介绍使用 GoLogto 快速构建 OIDC enterprise SSO 登录体验(用户认证 (Authentication))的步骤。

先决条件

  • 一个正在运行的 Logto 实例。查看 介绍页面 以开始。
  • Go 的基本知识。
  • 一个可用的 OIDC enterprise SSO 账户。

Create an application in Logto

Logto 基于 OpenID Connect (OIDC) 认证 (Authentication) 和 OAuth 2.0 授权 (Authorization)。它支持跨多个应用程序的联合身份管理,通常称为单点登录 (SSO)。

要创建你的 传统 Web 应用程序,只需按照以下步骤操作:

  1. 打开 Logto Console。在“开始使用”部分,点击“查看全部”链接以打开应用程序框架列表。或者,你可以导航到 Logto Console > Applications,然后点击“创建应用程序”按钮。 开始使用
  2. 在打开的模态窗口中,点击“传统 Web”部分,或使用左侧的快速过滤复选框过滤所有可用的“传统 Web”框架。点击 "Go" 框架卡片以开始创建你的应用程序。 框架
  3. 输入应用程序名称,例如“Bookstore”,然后点击“创建应用程序”。

🎉 太棒了!你刚刚在 Logto 中创建了你的第一个应用程序。你将看到一个祝贺页面,其中包含详细的集成指南。按照指南查看你的应用程序中的体验将会是什么样的。

Integrate Go SDK

提示:
  • 以下演示基于 Gin Web Framework 构建。你也可以通过相同的步骤将 Logto 集成到其他框架中。
  • Go 示例项目可在我们的 Go SDK 仓库 中找到。

安装

在项目根目录执行:

go get github.com/logto-io/go

github.com/logto-io/go/client 包添加到你的应用代码中:

main.go
// main.go
package main

import (
"github.com/gin-gonic/gin"
// 添加依赖
"github.com/logto-io/go/client"
)

func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello Logto!")
})
router.Run(":3000")
}

创建会话存储

在传统的 Web 应用程序中,用户认证信息会存储在用户会话中。

Logto SDK 提供了一个 Storage 接口,你可以根据你的 Web 框架实现一个 Storage 适配器,以便 Logto SDK 可以将用户认证信息存储在会话中。

备注:

我们不推荐使用基于 Cookie 的会话,因为 Logto 存储的用户认证信息可能会超过 Cookie 的大小限制。在这个例子中,我们使用基于内存的会话。你可以在生产环境中使用 Redis、MongoDB 和其他技术根据需要存储会话。

Logto SDK 中的 Storage 类型如下:

github.com/logto-io/client/storage.go
package client

type Storage interface {
GetItem(key string) string
SetItem(key, value string)
}

我们使用 github.com/gin-contrib/sessions 中间件作为示例来演示这个过程。

将中间件应用到应用程序中,以便我们可以在路由处理程序中通过用户请求上下文获取用户会话:

main.go
package main

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-gonic/gin"
"github.com/logto-io/go/client"
)

func main() {
router := gin.Default()

// 在这个例子中我们使用基于内存的会话
store := memstore.NewStore([]byte("your session secret"))
router.Use(sessions.Sessions("logto-session", store))

router.GET("/", func(ctx *gin.Context) {
// 获取用户会话
session := sessions.Default(ctx)
// ...
ctx.String(200, "Hello Logto!")
})
router.Run(":3000")
}

创建一个 session_storage.go 文件,定义一个 SessionStorage 并实现 Logto SDK 的 Storage 接口:

session_storage.go
package main

import (
"github.com/gin-contrib/sessions"
)

type SessionStorage struct {
session sessions.Session
}

func (storage *SessionStorage) GetItem(key string) string {
value := storage.session.Get(key)
if value == nil {
return ""
}
return value.(string)
}

func (storage *SessionStorage) SetItem(key, value string) {
storage.session.Set(key, value)
storage.session.Save()
}

现在,在路由处理程序中,你可以为 Logto 创建一个会话存储:

session := sessions.Default(ctx)
sessionStorage := &SessionStorage{session: session}

初始化 LogtoClient

首先,创建一个 Logto 配置:

main.go
func main() {
// ...
logtoConfig := &client.LogtoConfig{
Endpoint: "<your-logto-endpoint>", // 例如 http://localhost:3001
AppId: "<your-application-id>",
AppSecret: "<your-application-secret>",
}
// ...
}
提示:

你可以在管理控制台的应用详情页面找到并复制“应用密钥”:

App Secret

然后,你可以为每个用户请求使用上述 Logto 配置创建一个 LogtoClient

main.go
func main() {
// ...

router.GET("/", func(ctx *gin.Context) {
// 创建 LogtoClient
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)

// 使用 Logto 控制主页内容
authState := "你尚未登录此网站。:("

if logtoClient.IsAuthenticated() {
authState = "你已登录此网站!:)"
}

homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>"

ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})

// ...
}

实现登录路由

在配置好重定向 URI 后,我们添加一个 sign-in 路由来处理登录请求,并在主页上添加一个登录链接:

main.go
func main() {
// ...

// 在主页上添加一个链接以执行登录请求
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
// 添加链接
`<div><a href="/sign-in">Sign In</a></div>`

ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})

// 添加一个路由来处理登录请求
router.GET("/sign-in", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)

// 登录请求由 Logto 处理。
// 用户登录后将被重定向到重定向 URI。
signInUri, err := logtoClient.SignIn("http://localhost:3000/callback")
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}

// 将用户重定向到 Logto 登录页面。
ctx.Redirect(http.StatusTemporaryRedirect, signInUri)
})

// ...
}

现在,当用户访问 http://localhost:3000/sign-in 时,用户将被重定向到 Logto 登录页面。

实现回调路由

当用户在 Logto 登录页面成功登录后,Logto 将把用户重定向到重定向 URI。

由于重定向 URI 是 http://localhost:3000/callback,我们添加 /callback 路由来处理登录后的回调。

main.go
func main() {
// ...

// 添加一个路由来处理登录回调请求
router.GET("/callback", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)

// 登录回调请求由 Logto 处理
err := logtoClient.HandleSignInCallback(ctx.Request)
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}

// 跳转到开发者指定的页面。
// 本例将用户带回主页。
ctx.Redirect(http.StatusTemporaryRedirect, "/")
})

// ...
}

实现登出路由

与登录流程类似,当用户登出时,Logto 将会重定向用户到登出后的重定向 URI。

现在,让我们添加 sign-out 路由来处理登出请求,并在主页上添加一个登出链接:

main.go
func main() {
// ...

// 在主页上添加一个链接以执行登出请求
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
`<div><a href="/sign-in">Sign In</a></div>` +
// 添加链接
`<div><a href="/sign-out">Sign Out</a></div>`

ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})

// 添加一个路由来处理登出请求
router.GET("/sign-out", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)

// 登出请求由 Logto 处理。
// 用户登出后将被重定向到登出后的重定向 URI。
signOutUri, signOutErr := logtoClient.SignOut("http://localhost:3000")

if signOutErr != nil {
ctx.String(http.StatusOK, signOutErr.Error())
return
}

ctx.Redirect(http.StatusTemporaryRedirect, signOutUri)
})

// ...
}

用户发起登出请求后,Logto 将清除会话中的所有用户认证信息。

检查点:测试你的应用程序

现在,你可以测试你的应用程序:

  1. 运行你的应用程序,你将看到登录按钮。
  2. 点击登录按钮,SDK 将初始化登录过程并将你重定向到 Logto 登录页面。
  3. 登录后,你将被重定向回你的应用程序,并看到登出按钮。
  4. 点击登出按钮以清除令牌存储并登出。

Add OIDC enterprise SSO connector

To simplify access management and gain enterprise-level safeguards for your big clients, connect with Go as a federated identity provider. The Logto enterprise SSO connector helps you establish this connection in minutes by allowing several parameter inputs.

To add an enterprise SSO connector, simply follow these steps:

  1. Navigate to Logto console > Enterprise SSO.

SSO page

  1. Click "Add enterprise connector" button and choose your SSO provider type. Choose from prebuilt connectors for Microsoft Entra ID (Azure AD), Google Workspace, and Okta, or create a custom SSO connection using the standard OpenID Connect (OIDC) or SAML protocol.
  2. Provide a unique name (e.g., SSO sign-in for Acme Company).

Select your SSO provider

  1. Configure the connection with your IdP in the "Connection" tab. Check the guides above for each connector types.

SSO connection

  1. Customize the SSO experience and enterprise’s email domain in the "Experience" tab. Users sign in with the SSO-enabled email domain will be redirected to SSO authentication.

SSO experience

  1. Save changes.

Set up 在你的身份提供商 (IdP) 上配置 OIDC 应用

步骤 1:在你的身份提供商 (IdP) 上创建一个 OIDC 应用程序

通过在身份提供商 (IdP) 端创建一个应用程序来启动 OIDC 单点登录 (SSO) 集成。你需要提供来自 Logto 服务器的以下配置。

  • 回调 URI:Logto 回调 URI,也称为重定向 URI 或回复 URL,是 IdP 用于在成功认证 (Authentication) 后重定向用户浏览器的特定端点或 URL。当用户成功通过 IdP 认证 (Authentication) 后,IdP 会将用户的浏览器重定向回这个指定的 URI,并附带一个授权 (Authorization) 代码。Logto 将根据从此 URI 接收到的授权 (Authorization) 代码完成认证 (Authentication) 过程。

在你的 IdP OIDC 应用程序设置表单中填写 Logto 回调 URI,并继续创建应用程序。(大多数 OIDC IdP 提供多种应用程序类型可供选择。要在 Logto 上创建基于 Web 的 SSO 连接器,请选择 Web Application 类型。)

步骤 2:在 Logto 上配置 OIDC 单点登录 (SSO)

在 IdP 端成功创建 OIDC 应用程序后,你需要将 IdP 配置反馈给 Logto。导航到 Connection 选项卡,并填写以下配置:

  • Client ID:由 IdP 分配给你的 OIDC 应用程序的唯一标识符。Logto 使用此标识符在 OIDC 流程中识别和认证应用程序。
  • Client Secret:在 Logto 和 IdP 之间共享的机密密钥。此密钥用于认证 OIDC 应用程序并保护 Logto 和 IdP 之间的通信。
  • 发行者 (Issuer):发行者 URL,是 IdP 的唯一标识符,指定可以找到 OIDC 身份提供商的位置。它是 OIDC 配置的重要组成部分,因为它帮助 Logto 发现必要的端点。 为了简化配置过程,Logto 将自动获取所需的 OIDC 端点和配置。这是通过利用你提供的发行者并调用 IdP 的 OIDC 发现端点来完成的。确保发行者端点有效且配置准确,以便 Logto 能够正确检索所需信息。 在成功获取请求后,你应该能够在发行者部分看到解析后的 IdP 配置。
  • 权限 (Scope):一个以空格分隔的字符串列表,定义 Logto 在 OIDC 认证 (Authentication) 过程中请求的所需权限或访问级别。权限 (Scope) 参数允许你指定 Logto 从 IdP 请求哪些信息和访问权限。 权限 (Scope) 参数是可选的。无论自定义权限设置如何,Logto 总是会向 IdP 发送 openidprofileemail 权限 (Scopes)。 这是为了确保 Logto 能够正确从 IdP 检索用户的身份信息和电子邮件地址。你可以在权限 (Scope) 参数中添加其他权限,以请求更多来自 IdP 的信息。

步骤 3:设置电子邮件域并启用 SSO 连接器

在 Logto 的连接器 SSO 体验 标签上提供你组织的 电子邮件域。这将启用 SSO 连接器作为这些用户的认证 (Authentication) 方法。

具有指定域的电子邮件地址的用户将被重定向,以使用你的 SSO 连接器作为他们唯一的认证 (Authentication) 方法。

Save your configuration

仔细检查你是否在 Logto 连接器配置区域填写了必要的值。点击“保存并完成”(或“保存更改”),OIDC enterprise SSO 连接器现在应该可用了。

Enable OIDC enterprise SSO connector in Sign-in Experience

You don’t need to configure enterprise connectors individually, Logto simplifies SSO integration into your applications with just one click.

  1. Navigate to: Console > Sign-in experience > Sign-up and sign-in.
  2. Enable the "Enterprise SSO" toggle.
  3. Save changes.

Once enabled, a "Single Sign-On" button will appear on your sign-in page. Enterprise users with SSO-enabled email domains can access your services using their enterprise identity providers (IdPs).

Auto detect SSO sign-in via email domain Navigate to SSO sign-in via manually click link button

To learn more about the SSO user experience, including SP-initiated SSO and IdP-initiated SSO, refer to User flows: Enterprise SSO.

Testing and Validation

返回到你的 Go 应用。你现在应该可以使用 OIDC enterprise SSO 登录了。享受吧!

Further readings

终端用户流程:Logto 提供开箱即用的认证 (Authentication) 流程,包括多因素认证 (MFA) 和企业单点登录 (SSO),以及强大的 API,用于灵活实现账户设置、安全验证和多租户体验。

授权 (Authorization):授权 (Authorization) 定义了用户在被认证 (Authentication) 后可以执行的操作或访问的资源。探索如何保护你的 API 以用于原生和单页应用程序,并实现基于角色的访问控制 (RBAC)。

组织 (Organizations):在多租户 SaaS 和 B2B 应用中特别有效,组织功能支持租户创建、成员管理、组织级 RBAC 和即时供应。

客户 IAM 系列:我们关于客户(或消费者)身份和访问管理的系列博客文章,从 101 到高级主题及更深入的内容。