# 客户端

客户端代表 web 认证机制。它执行登录流程并返回(如果成功)用户配置文件。大量客户端可用于:

虽然大多数客户端都是独立可用的,但 HTTP 客户端需要定义一个认证器(Authenticator) 来处理凭证校验。

客户端(如授权器和匹配器)通常在安全配置(security configuration)中定义。

每个客户端都有一个以类名作为默认的名称(如 FacebookClient),但可以使用 setName 方法将其显式设置为另一个值。

理解以下主要特性:

# 1)直接客户端 vs 间接客户端

客户端有两种:直接客户端(direct clients)用于 web 服务身份验证,间接客户端(indirect clients)用于 UI 身份验证。

以下是他们的行为和差异:

直接客户端=web 服务认证 间接客户端=UI 认证
认证流程 1)为每个 HTTP 请求传递凭据(传递给 “安全过滤器”) 1) 原始请求的 URL 保存在会话中(通过“安全过滤器”)
2) 用户被重定向到身份提供者(identity provider)(通过“安全过滤器”)
3) 认证发生在身份提供者(identity provider)(或本地的 FormClientIndirectBasicAuthClient
4) 用户被重定向回调端点/URL(“回调端点”)
5) 用户被重定向到原始请求的 URL(通过“回调端点”)
登录流程发生了几次? 通过定义的 AuthenticatorProfileCreator 对每个 HTTP 请求(在“安全过滤器”中)进行认证。
出于性能原因,可以通过将当前 Authenticator 包装在 LocalCachingAuthenticator 中来使用缓存,或者可以配置“安全过滤器”以在会话中保存配置配置文件(ProfileStorageDecision
认证仅发生一次(在“回调过滤器”中)
默认情况下,用户 profile 保存在哪? 在 HTTP request 中(无状态) 在 web session 中(有状态)
凭据在哪里? 为每个 HTTP request 传递(由“安全过滤器”处理) 在身份提供者(identity provider)返回的回调端点上(由“回调端点”获取)
什么是受保护的 URL? web 服务的 URL 受“安全过滤器”保护 web 应用程序的 URL 受“安全过滤器”保护,但回调 URL 不受保护,因为它在用户仍然匿名的登录过程中使用

# 2)计算角色和权限

要计算经过认证的用户配置文件合适的角色和权限,需要定义 AuthorizationGenerator (opens new window) 并将其附加到客户端。

示例

AuthorizationGenerator authGen = (ctx, session, profile) -> {
  String roles = profile.getAttribute("roles");
  for (String role: roles.split(",")) {
    profile.addRole(role);
  }
  return Optional.of(profile);
};
client.addAuthorizationGenerator(authGen);
1
2
3
4
5
6
7
8

你可以使用 addAuthorizationGenerator 方法添加任意数量的授权生成器,也可以使用 setAuthorizationGenerators 方法添加授权生成器列表。

# 3)回调 URL

对于间接客户端,必须定义将在登录流程中使用的回调 URL:成功登录后,身份提供者(identity provider)将用户重定向到回调 URL 的应用程序。

在此回调 URL 上,必须定义“回调端点”以完成登录流程。

由于回调 URL 可以在多个客户端之间共享,回调 URL 必须以查询参数或路径参数来保存客户端的信息(以便能够区分不同的客户端)。

示例

FacebookClient facebookClient = new FacebookClient(fbKey, fbSecret);
TwitterClient twitterClient = new TwitterClient(twKey, twSecret);
Config config = new Config("http://localhost:8080/callback", facebookClient, twitterClient);
1
2
3

在此例子中,FacebookClient 的回调 URL 是 http://localhost:8080/callback?client_name=FacebookClientTwitterClient 的回调 URL 是 http://localhost:8080/callback?client_name=TwitterClient

你必须在身份提供者一侧定义回调 URL。

这是由于客户端里的 CallbackUrlResolver (opens new window)QueryParameterCallbackUrlResolver (opens new window)

你可以通过 QueryParameterCallbackUrlResolversetClientNameParameter 来变更参数 client_name

但你也可以使用 PathParameterCallbackUrlResolver (opens new window),它可以添加客户端名称作为路径参数。

示例

OidcConfiguration configuration = new OidcConfiguration();
configuration.setClientId("788339d7-1c44-4732-97c9-134cb201f01f");
configuration.setSecret("we/31zi+JYa7zOugO4TbSw0hzn+hv2wmENO9AS3T84s=");
configuration.setDiscoveryURI("https://login.microsoftonline.com/38c46e5a-21f0-46e5-940d-3ca06fd1a330/.well-known/openid-configuration");
AzureAdClient azureAdClient = new AzureAdClient(configuration);
client.setCallbackUrlResolver(new PathParameterCallbackUrlResolver());
Clients clients = new Clients("http://localhost:8080/callback", azureAdClient);
Config config = new Config(clients);
1
2
3
4
5
6
7
8

在这个例子中,AzureAdClient 的回调 URL 是 http://localhost:8080/callback/AzureAdClient

你甚至可以使用 NoParameterCallbackUrlResolver,它使回调 URL 保持不变。在这种情况下,不会向回调 URL 添加任何参数,也不会在回调端点上获取客户端。你将被迫在 CallbackLogic 级别定义一个“默认客户端”。

示例

defaultCallbackLogic.setClient("FacebookClient");
1

CallbackUrlResolver 依赖 UrlResolver (opens new window),能根据当前 web 上下文补全 URL。可以通过客户端的 getUrlResolver() 方法检索 UrlResolver

你可以使用 DefaultUrlResolver (opens new window) 并通过使用:defaultUrlResolver.setCompleteRelativeUrl(true) 来处理相对 URL。或者通过 setUrlResolver 方法提供自己的 UrlResolver

# 4)配置文件选项

你可以通过 setSaveProfileInSession 方法控制配置文件是否保存在会话中。默认情况下,间接客户端配置为 true,直接客户端配置为 false

你可以通过 setMultiProfile 方法(默认情况下为 false)控制配置文件是保存在现存验证过的配置文件之外还是替换现存的 profile。

大多数客户端依赖 AuthenticatorProfileCreator 组件来验证凭据并创建用户配置文件。

在登录流程结束时,返回的用户配置文件由(内部)AuthenticatorProfileCreator 创建,后者保存 配置文件定义

可以使用 setProfileDefinition 方法重写 profile 定义。

# 5)AJAX 请求

对于间接客户端,如果用户尝试访问受保护的 URL,则会将其重定向到认证提供者(identity provider)进行登录。

但是,如果传入的 HTTP 请求是 AJAX 请求,则不会执行重定向,并返回 401 错误页面。

如果 X-Requested-With 头的值为 XMLHttpRequest,或者如果 is_AJAX_request 参数或头为 true,则 HTTP 请求被认为是 AJAX 请求。这是 DefaultAjaxRequestResolver (opens new window) 的行为。

如果 addRedirectionUrlAsHeader 属性设置为 trueDefaultAjaxRequestResolver 将仅计算重定向 URL 并将其添加为头。

但你可以通过 client.setAjaxRequestResolve(myAjaxRequestResolver) 提供自己的 AjaxRequestResolver

# 6)Client 方法

Client 接口有以下方法:

方法 用途
Optional<RedirectionAction> getRedirectionAction(WebContext context, SessionStore sessionStore) 它返回重定向操作,将用户重定向到身份提供者进行登录。这只对间接客户有意义。
通过 RedirectionActionBuilder (opens new window) 在内部计算用户到身份提供者的重定向
Optional<Credentials> getCredentials(WebContext context, SessionStore sessionStore) 它从 HTTP 请求中提取凭据并验证它们。
凭据的提取由 CredentialsExtractor (opens new window) 完成,而凭据验证由 Authenticator (opens new window) 确保
Optional<UserProfile> getUserProfile(Credentials credentials, WebContext context, SessionStore sessionStore) 它构建经过身份验证的用户配置文件。
已验证用户配置文件的创建由 ProfileCreator (opens new window) 执行
Optional<UserProfile> renewUserProfile(UserProfile profile, WebContext context, SessionStore sessionStore) 它返回更新的用户 配置文件
Optional<RedirectionAction> getLogoutAction(WebContext context, SessionStore sessionStore, UserProfile currentProfile, String targetUrl) 它返回重定向操作以调用身份提供者注销。
注销重定向操作计算由 LogoutActionBuilder (opens new window) 完成

客户端通常使用默认子组件填充:RedirectionActionBuilderCredentialExtractorProfileCreatorLogoutActionBuilderAuthenticator,但必须明确定义 Authenticator 的 HTTP 客户端除外。当然,子组件还可以针对各种定制进行更改。

# 7)原始请求的 URL

原始请求的 URL 是在经过认证的过程开始之前调用的 URL:它在登录流程完成后由“回调端点”恢复。

它由 SavedRequestHandler 组件在 DefaultSecurityLogicDefaultCallbackLogic 中处理。默认情况下,它是处理 GET 和 POST 请求的 DefaultSavedRequestHandler

# 8)静默登录

使用 IndirectClient 时,登录流程可能会失败或在外部身份提供者层级被取消。

因此,不会创建用户配置文件,并且不授权对安全资源访问(401 错误)。

但是,如果登录流程失败或被取消,你可能仍然希望访问 web 资源。

为此,可以使用客户端的 setProfileFactoryWhenNotAuthenticated 方法返回自定义配置文件,而不是无配置文件。

示例

myClient.setProfileFactoryWhenNotAuthenticated(p -> AnonymousProfile.INSTANCE);
1

注意

在这种情况下,除非定义了合适的授权者,否则将授予整个 web 会话对所有安全资源的访问权限。

原文链接 (opens new window)