配置您的身份认证策略
FW-NAC(飞网网络访问控制系统)支持灵活设置的身份认证方法。您可根据实际情况选择以下的方案:
- 使用FW-IAM(飞网身份认证和访问管理系统),该系统提供集中的身份管理、认证和访问控制,单点登录功能,并支持与各种类型的应用集成。
- 使用FW-NAC内置的账号系统注册、登录、重置密码,用户通过输入邮箱和自定义的密码访问FW-NAC系统;FW-NAC对外提供标准的GRPC API,方便您扩展身份认证功能。
- 使用您已有的身份认证系统与FW-NAC集成。您可以修改FW-NAC的配置文件,增加、扩展或使用您自己的身份认证策略(包括Oauth 2.0、OpenID Connect、SAML IDP、AD/LDAP、AuthProxy等)。具体的配置方法参见下文。
Oauth 2.0
飞网可与标准的 OAuth 2.0 接口集成,下面以微信开放平台为例介绍OAuth 2.0的集成方法。
connectors:
- type: oauth
# 使用OAuth 2.0认证机制程序的ID,wechat为示例配置
id: wechat
# 使用OAuth 2.0认证机制程序的名称
name: wechat
config:
# 飞网连接器配置中以“$”符号开头的值表示这些值将从环境变量中读取。
# clientID:身份验证程序的客户端ID
# clientSecret:身份验证程序的客户端的密钥
# redirectURI:重定向URI,用于在完成身份验证后将用户重定向回飞网。
clientID: $WEIXIN_CLIENT_ID
clientSecret: $WEIXIN_CLIENT_SECRET
redirectURI: https://connector.gmzta.com:44390/callback
# tokenURL:用于获取访问令牌的URL。
# authorizationURL: 用于进行用户授权的URL.
# userInfoURL:用于获取用户信息的URL
tokenURL: https://api.weixin.qq.com/sns/oauth2/access_token
authorizationURL: https://open.weixin.qq.com/connect/qrconnect
userInfoURL: https://api.weixin.qq.com/sns/userinfo
# insecureSkipVerify:指定是否在与身份验证提供者通信时验证 SSL 证书。
# 建议将 insecureSkipVerify 设置为 false,以确保与身份验证提供者之间的通信是安全和可信赖的。
# insecureSkipVerify: false
# rootCAs: 指定 SSL 证书文件的路径
# rootCAs: /etc/ssl/weixin.pem
# 可选参数: 为访问用户帐户请求身份验证提供程序的作用域列表。
# scopes:
# - identity
# 可选参数: 可配置的用户ID查找密钥。
# Default: id
# userIDKey:
# 身份验证提供程序返回非标准用户身份配置文件
# 使用 claimMapping 将这些用户信息映射到标准声明:
claimMapping:
# 可选参数: 可配置的用户名称查找密钥
# Default: user_name
# userNameKey:
# 可选参数: 可配置的首选用户名查找密钥
# Default: preferred_username
# preferredUsernameKey:
# 可选参数: 可配置的用户组查找密钥
# Default: groups
# groupsKey:
# 可选参数: 可配置的电子邮件查找密钥
# Default: email
# emailKey:
# 可选参数: 可配置的电子邮件验证查找密钥
# Default: email_verified
# emailVerifiedKey:
OpenID Connect
飞网可与标准的 OpenID Connect 接口集成,下面以google为例介绍OAuth 2.0的集成方法。
connectors:
- type: oidc
id: google
name: Google
config:
# issuer:身份验证系统的URL,它不仅用于与gooole建立连接,还用于配置发现。
# issuer的值必须与身份验证系统配置发现中返回的值完全匹配
#
# 详情: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
issuer: https://accounts.google.com
# 飞网连接器配置中以“$”符号开头的值表示这些值将从环境变量中读取。
clientID: $GOOGLE_CLIENT_ID
clientSecret: $GOOGLE_CLIENT_SECRET
# 重定向URI,用于在完成身份验证后将用户重定向回飞网
redirectURI: https://connector.gmzta.com:44390/callback
# 如果你遇到某些身份认证源要求通过POST参数传递client_secret而不是使用基本身份验证的情况,
# 你可能需要在代码中取消注释以下字段。
#
# basicAuthUnsupported: true
# 要在令牌响应中请求的附加作用域列表。
# 默认为profile和email
#
# scopes:
# - profile
# - email
# - groups
# 一些身份认证源在注册过程中没有使用电子邮件验证,或者它们充当另一个IDP(Identity Provider)的代理时,
# 会在未使用"email_verified"的情况下返回声明,例如AWS Cognito与 SAML IDP。可以使用以下选项覆盖此行为。
# insecureSkipEmailVerified: true
# 在飞网连接器中,群组声明(像其他oidc声明一样)只有在刷新id令牌时才会被更新,这意味着常规的刷新流程不会更新群组声明。
# 因此,默认情况下,oidc连接器不允许群组声明。如果您可以接受可能过期的群组声明,您可以使用此选项在每个连接器的基础上启用群组声明。
# 可以使用以下选项覆盖此行为。
# insecureEnableGroups: true
# 当启用时,OpenID连接器将查询UserInfo端点以获取附加声明。
# UserInfo声明优先于IDToken返回的声明。当IDToken不包含请求的所有声明时,应使用此选项。
# https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
# getUserInfo: true
# 将设置的声明用作用户ID。
# 声明列表在 https://openid.net/specs/openid-connect-core-1_0.html#Claims
# Default: sub
# userIDKey: nickname
# 将设置的声明用作用户名称。
# Default: name
# userNameKey: nickname
# "acr_values"变量指定了身份验证请求中要求授权服务器处理的身份验证上下文类别值,该请求来自此客户端。
# acrValues:
# - <value>
# - <value>
# 对于"offline_access",prompt参数默认设置为"prompt=consent"。
# 并非所有的OIDC提供程序都支持这一设置,其中一些支持不同的prompt值。
# 像 "prompt=login" 或者 "prompt=none"
# promptType: consent
# 某些身份认证源返回非标准的声明(例如,邮件地址)。
# 可以使用claimMapping来将这些声明映射到标准的声明:
# https://openid.net/specs/openid-connect-core-1_0.html#Claims
# claimMapping 只有在id_token中未返回非标准声明时,才可以将非标准声明映射到标准声明。
claimMapping:
# 设置的声明被用作首选用户名。
# Default: preferred_username
# preferred_username: other_user_name
# 设置的声明被用作电子邮件地址。
# Default: email
# email: mail
# 设置的声明被用作组。
# Default: groups
# groups: "cognito:groups"
SAML 2.0
飞网可与标准的 SAML 接口集成,下面以Okta为例介绍SAML 2.0的集成方法。
connectors:
- type: saml
# 身份验证程序的唯一标识符
id: okta
name: Okta
config:
# ssoURL:单点登录 URL,用于将用户重定向到 Okta 的认证页面。
ssoURL: https://dev-111102.oktapreview.com/app/foo/exk91cb99lKkKSYoy0h7/sso/saml
# 可选参数, PEM 格式证书文件路径,用于验证 Okta 的 SSL 证书。
ca: /etc/dex/saml-ca.pem
# 登录成功后重定向回飞网的 URI。
redirectURI: https://connector.gmzta.com:44390/callback
usernameAttr: name
emailAttr: email
groupsAttr: groups
LDAP
用户条目应具有一个电子邮件属性(可通过emailAttr进行配置)和一个显示名称属性(可通过nameAttr进行配置)。 *Attr属性可以设置为“DN”,以便在需要但其他地方不可用的情况下使用,如果记录中不存在“DN”属性。
为了配置此连接器,“DN”区分大小写,应始终大写。
下面是一个示例配置文件,飞网连接LDAP服务器的相关配置。
connectors:
- type: ldap
# 连接器的唯一标识符。
id: ldap
# 连接器的名称。
name: LDAP
config:
# LDAP服务器的主机和可选端口,格式为“host:port”。
# 如果未提供端口,则将基于“insecureNoSSL”和“startTLS”标志猜测端口,
# 对于不安全或StartTLS连接,端口为389,否则为636。
host: ldap.example.com:636
# 如果LDAP服务器不使用TLS(端口389),可以设置此选项为true。
# 但此选项本质上会将密码泄露给同一网络上的任何人,因此只应在必要时使用。
# insecureNoSSL: true
# 如果未提供自定义证书,可以使用此选项开启TLS证书检查。需要注意的是,这样做是不安全的。
#
# insecureSkipVerify: true
# 在连接到服务器时,使用ldap://协议进行连接,然后发出StartTLS命令。如果未指定,连接将使用ldaps://协议。
# startTLS: true
# 受信任的根证书文件的路径。默认情况下,使用主机的根CA证书。
rootCA: /etc/dex/ldap.ca
# 可以提供内联的原始证书文件。
# rootCAData: ( base64 encoded PEM file )
# 应用程序服务帐户的DN和密码。连接器使用这些凭据来搜索用户和组。
# 如果LDAP服务器提供匿名身份验证访问,则不需要此选项。
# 请注意,如果绑定密码包含$符号,则必须将其保存在环境变量中,并将其作为bindPW的值。
bindDN: uid=serviceaccount,cn=users,dc=example,dc=com
bindPW: password
# 要在提供的密码提示符中显示的属性。如果未设置,将显示“用户名”,提示用户输入用户名或电子邮件地址
usernamePrompt:Username
# userSearch将用户输入的用户名和密码映射到LDAP条目。
userSearch:
# 搜索的起始BaseDN。它将转换为查询。
# "(&(objectClass=person)(uid=<username>))".
baseDN: cn=users,dc=example,dc=com
# 搜索目录时要应用的可选筛选器。
# filter: "(objectClass=person)"
filter: "(objectClass=inetOrgPerson)"
# 修改此行,使用LDAP中用户的用户名属性 使用属性改为有登录mail
username: uid
# 用于比较用户条目的用户名属性。这将被转换并与其他过滤器结合为"(<attr>=<username>)"。
username: uid
# 以下三个字段直接映射到用户条目上的属性。
# 用户的字符串表示形式。
idAttr: uid
# 必填项。映射到电子邮件的属性。
emailAttr: mail
# 映射到用户显示名称的属性。没有默认值。
nameAttr: name
# 映射到用户的首选用户名。无默认值。
preferredUsernameAttr: uid
# 给定用户条目,用于搜索组的组搜索查询。
groupSearch:
# 搜索的起始BaseDN。它将转换为查询。
# "(&(objectClass=group)(member=<user uid>))".
baseDN: cn=groups,dc=freeipa,dc=example,dc=com
# 在搜索目录时要应用的可选过滤器。
# filter: "(objectClass=group)"
filter: "(objectClass=groupOfNames)"
# 以下列表包含用于将用户与组匹配的字段对。
# 它向过滤器添加了一个额外的要求,即组中的属性必须与用户的属性值匹配。
userMatchers:
# - userAttr: uid
userAttr: DN
groupAttr: member
# 表示组名的属性。
# nameAttr: name
nameAttr: cn
LDAP 连接器首先使用 bindDN 和 bindPW 初始化与 LDAP 目录的连接。然后,它会尝试搜索给定 username 的用户并绑定为该用户以验证其密码。返回多个条目的搜索被视为不明确,并将返回错误。
AuthProxy
身份认证代理服务,以下是一个飞网连接身份认证代理服务的配置示例:
connectors:
- type: authproxy
id: myBasicAuth
name: HTTP Basic Auth
config:
userHeader: X-Forwarded-User # default is X-Remote-User
groupHeader: X-Forwarded-Group # default is X-Remote-Group
staticGroups:
- default
authproxy 连接器假定您已配置前端 Web 服务器,以便它对位置执行身份验证/callback/myBasicAuth 并在 HTTP 标头中提供结果。
在此示例中,配置的标头适用X-Forwarded-User于用户的邮件和X-Forwarded-Group用户的组。Dex authproxy 连接器将返回包含配置的组列表staticGroups并返回组标头。
以下配置适用于 Apache 2.4.10+:
<Location /dex/>
ProxyPass "https://connector.gmzta.com:44390/"
ProxyPassReverse "https://connector.gmzta.com:44390/"
# 除了我们需要覆盖X-Remote-User头部的请求,其他所有请求都要删除X-Remote-User头部。
RequestHeader unset X-Remote-User
</Location>
<Location /dex/callback/myBasicAuth>
AuthType Basic
AuthName "db.debian.org webPassword"
AuthBasicProvider file
AuthUserFile "/etc/apache2/debian-web-pw.htpasswd"
Require valid-user
# 深度防御:清除授权头部,以便Debian Web密码甚至不会传达到飞网连接器。
RequestHeader unset Authorization
# 需要Apache 2.4.10或更高版本。
RequestHeader set X-Remote-User expr=%{REMOTE_USER}@debian.org
ProxyPass "https://connector.gmzta.com:44390/callback/myBasicAuth"
ProxyPassReverse "https://connector.gmzta.com:44390/callback/myBasicAuth"
</Location>
GRPC API
您可以参考下面的”api.proto“接口描述文件,扩展身份认证功能。您可以开发并实现如下功能:
- 注册、更新、删除、查看Oauth客户端信息;
- 注册密码(包括用户名、邮箱、密码哈希)、更新密码、删除密码、查看密码列表;
- 根据邮箱和密码哈希进行身份验证
- 列举和解除刷新令牌(每一个user-client组合只能同时具有一个刷新令牌)。
syntax = "proto3";
package api;
option java_package = "";
option go_package = "";
// Client表示OAuth2客户端
message Client {
string id = 1;
string secret = 2;
repeated string redirect_uris = 3;
repeated string trusted_peers = 4;
bool public = 5;
string name = 6;
string logo_url = 7;
}
// GetClientReq 请求客户端详细信息
message GetClientReq {
// 客户端ID
string id = 1;
}
// GetClientResp 返回客户端详细信息
message GetClientResp {
Client client = 1;
}
// CreateClientReq 请求创建客户端
message CreateClientReq {
Client client = 1;
}
// CreateClientResp 响应创建客户端
message CreateClientResp {
bool already_exists = 1;
Client client = 2;
}
// DeleteClientReq 请求删除客户端
message DeleteClientReq {
// The ID of the client.
string id = 1;
}
// DeleteClientResp 响应确定客户端是否已成功删除。
message DeleteClientResp {
bool not_found = 1;
}
// UpdateClientReq 请求更新客户端
message UpdateClientReq {
string id = 1;
repeated string redirect_uris = 2;
repeated string trusted_peers = 3;
string name = 4;
string logo_url = 5;
}
// UpdateClientResp 响应更新客户端
message UpdateClientResp {
bool not_found = 1;
}
// TODO
// Password 由存储管理的用于密码映射的电子邮件。
message Password {
string email = 1;
// 目前我们不接受纯文本密码。
bytes hash = 2;
string username = 3;
string user_id = 4;
}
// CreatePasswordReq 请求创建密码
message CreatePasswordReq {
Password password = 1;
}
// CreatePasswordResp 响应创建密码
message CreatePasswordResp {
bool already_exists = 1;
}
// UpdatePasswordReq 请求修改已经存在的密码
message UpdatePasswordReq {
//email 用于查找密码的电子邮件。此字段无法修改
string email = 1;
bytes new_hash = 2;
string new_username = 3;
}
// UpdatePasswordResp 响应修改密码
message UpdatePasswordResp {
bool not_found = 1;
}
// DeletePasswordReq 请求删除密码
message DeletePasswordReq {
string email = 1;
}
// DeletePasswordResp 响应删除密码
message DeletePasswordResp {
bool not_found = 1;
}
// ListPasswordReq is 请求枚举passwords.
message ListPasswordReq {}
// ListPasswordResp 响应passwords列表
message ListPasswordResp {
repeated Password passwords = 1;
}
// VersionReq 请求获取版本信息
message VersionReq {}
// VersionResp 响应组件的版本信息
message VersionResp {
// 服务器版本
string server = 1;
// API的数字版本。每次向API添加新的调用时,它都会增加。
// 客户端应使用此信息来确定服务器是否支持某个特定功能。
int32 api = 2;
}
// RefreshTokenRef 包含由存储管理的刷新令牌refresh token的元数据。
message RefreshTokenRef {
// refresh token的ID
string id = 1;
string client_id = 2;
int64 created_at = 5;
int64 last_used = 6;
}
// ListRefreshReq 请求列举某个用户的刷新令牌refresh tokens
message ListRefreshReq {
// The "sub" claim returned in the ID Token.
string user_id = 1;
}
// ListRefreshResp 响应某个用户的刷新令牌refresh tokens
message ListRefreshResp {
repeated RefreshTokenRef refresh_tokens = 1;
}
// RevokeRefreshReq 请求解除某个user-client组合的刷新令牌
message RevokeRefreshReq {
// "sub"属性包含在ID Token中
string user_id = 1;
string client_id = 2;
}
// RevokeRefreshResp 响应刷新令牌是否解除成功
message RevokeRefreshResp {
// true表示刷新令牌没有找到,且令牌无法被解除
bool not_found = 1;
}
//请求校验邮箱密码
message VerifyPasswordReq {
string email = 1;
string password = 2;
}
//响应校验邮箱密码结果
message VerifyPasswordResp {
bool verified = 1;
bool not_found = 2;
}
// gRPC服务清单
service Dex {
rpc GetClient(GetClientReq) returns (GetClientResp) {};
rpc CreateClient(CreateClientReq) returns (CreateClientResp) {};
rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {};
rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {};
rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {};
rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {};
rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {};
rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {};
rpc GetVersion(VersionReq) returns (VersionResp) {};
rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {};
// 注意:每一个user-client组合只能同时具有一个刷新令牌
rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};
rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {};
}