本文将介绍如何创建一个 Java WebSphere 应用,该应用使用 适用于 Java 的 Microsoft 身份验证库 (MSAL) 登录用户。 该应用还可根据 Microsoft Entra ID 安全组的成员身份来限制对页面的访问。
下图显示了应用的拓扑结构:
客户端应用使用 MSAL for Java (MSAL4J) 将用户登录到 Microsoft Entra ID 租户,并从 Microsoft Entra ID 获取 ID 令牌。 ID 令牌证明用户已通过此租户的身份验证。 该应用会根据用户的身份验证状态和群组成员身份来保护其路由。
有关涵盖此场景的视频,请观看使用应用角色、安全组、范围和目录角色在应用程序中实现授权。
先决条件
- JDK 版本 8 或更高版本
- Maven 3
- Microsoft Entra ID 租户。 有关详细信息,请参阅 如何获取 Microsoft Entra ID 租户。
- 你自己的 Microsoft Entra ID 租户中的一个用户帐户。
- 两个安全组
GroupAdmin和GroupMember,其中包含要测试的用户。
建议
- 熟悉一下 Java / Jakarta Servlets。
- 熟悉一下 Linux/OSX 终端。
- 用于查看令牌的 jwt.ms。
- Fiddler 用于监控您的网络活动和进行故障排除。
- 关注 Microsoft Entra Blog,及时了解最新动态。
设置示例
以下各部分将介绍如何设置示例应用程序。
克隆或下载示例存储库
要克隆示例,请打开 Bash 窗口并使用以下命令:
git clone https://github.com/Azure-Samples/ms-identity-msal-java-samples.git
cd 3-java-servlet-web-app/3-Authorization-II/groups
或者,导航到 ms-identity-msal-java-samples 存储库,然后将其作为 .zip 文件下载并提取到硬盘驱动器。
重要
为避免 Windows 上的文件路径长度限制,请将存储库克隆到或解压到靠近硬盘根目录的目录中。
将示例应用程序注册到 Microsoft Entra ID 租户中
此示例中有一个项目。 以下各部分将介绍如何使用 Azure 门户注册应用。
选择要在其中创建应用程序的 Microsoft Entra ID 租户
要选择租户,请按以下步骤操作:
登录到 Azure 门户。
如果你的帐户属于多个 Microsoft Entra ID 租户,请在 Azure 门户的角落选择你的个人资料,然后选择 切换目录,将当前会话切换到所需的 Microsoft Entra ID 租户。
注册应用 (java-servlet-webapp-groups)
首先,按照快速入门:将应用程序注册到 Microsoft 标识平台中的说明在 Azure 门户中注册新应用。
然后,按照以下步骤完成注册:
导航到面向开发人员的 Microsoft 标识平台应用注册页。
选择新注册。
在出现的“注册应用程序”页面中,输入以下应用的注册信息:
- 在“名称”部分中,输入一个有意义的应用名称,以便向应用用户显示,例如 。
java-servlet-webapp-groups - 在支持的账户类型下,选择仅此组织目录中的账户。
- 在“重定向 URI”部分中,在组合框中选择“Web”并输入以下重定向 URI:。
http://localhost:8080/msal4j-servlet-groups/auth/redirect
- 在“名称”部分中,输入一个有意义的应用名称,以便向应用用户显示,例如 。
选择“注册”以创建应用程序。
在应用的注册页面上,找到并复制“应用程序(客户端) ID”值,以供稍后使用。 可以在应用程序的一个或多个配置文件中使用此值。
选择“保存”以保存更改。
在应用的注册页面上,选择导航窗格中的“证书和机密”,打开可生成机密和上传证书的页面。
在“客户端密码”部分中,选择“新建客户端密码”。
键入描述,例如“应用机密”。
选择机密的过期时间或指定自定义生存期。 客户端机密的最长生存期限制为 24 个月,Microsoft建议过期时间少于 12 个月。 对于生产应用,首选证书或联合标识凭据而不是客户端密码。
选择 添加 。 此时将显示生成的值。
复制并保存生成的值,以供在之后的步骤中使用。 你需要在代码的配置文件中使用此值。 此值不会再显示,也无法通过任何其他方式获取。 因此,在转到任何其他屏幕或窗格之前,请务必先在 Azure 门户中将其保存。
在应用程序的注册页面上,从导航窗格中选择“API 权限”,以便打开页面并添加对应用程序所需的 API 的访问权限。
选择“添加权限”。
确保已选择“Microsoft API”选项卡。
在“常用 Microsoft API”部分,选择“Microsoft Graph”。
在“委托的权限”部分中,从列表中选择 User.Read 和 GroupMember.Read.All。 如有必要,请使用搜索框。
选择“添加权限”。
GroupMember.Read.All需要管理员同意,因此请选择“为 {租户} 授予/撤销管理员同意”,然后在询问你是否要为租户中的所有账户所请求的权限授予同意时,选择“是”。 需要成为 Microsoft Entra ID 租户管理员才能执行此操作。
配置应用 (java-servlet-webapp-groups) 以使用应用注册
按照以下步骤来配置应用:
注意
在以下步骤中,ClientID 与 Application ID 或 AppId 相同。
在 IDE 中打开项目。
打开 ./src/main/resources/authentication.properties 文件。
找到字符串
{enter-your-tenant-id-here}。 如果使用“仅此组织目录中的帐户”选项来注册应用程序,请使用 Microsoft Entra 租户 ID 来替换现有值。找到字符串
{enter-your-client-id-here},然后用从 Azure 门户复制的应用程序 ID 或clientId应用程序的java-servlet-webapp-groups来替换现有值。在 Azure 门户中查找字符串
{enter-your-client-secret-here},并将现有值替换为在创建java-servlet-webapp-groups应用时保存的值。
配置安全组
你可以使用以下选项来进一步配置应用程序,以接收组声明:
接收已登录用户在 Microsoft Entra ID 租户中分配的所有组,包括嵌套组。 有关详细信息,请参阅配置应用程序以接收已登录用户分配给的所有组(包括嵌套组)部分。
从经过筛选且你的应用程序被配置为可与之配合使用的组中接收组声明值。 有关详细信息,请参阅配置应用程序以便从用户可能分配给的已筛选组中接收组声明值部分。 Microsoft Entra ID 免费版中没有此选项。
注意
要获取内部部署组的 samAccountName 或 On Premises Group Security Identifier 而不是组 ID,请参阅使用 Microsoft Entra ID 为应用程序配置组声明中的使用从 Active Directory 同步的组特性的先决条件部分。
配置应用程序以接收已登录用户分配给的所有组(包括嵌套组)
要配置应用程序,请按照以下步骤操作:
在应用的注册页面上,在导航窗格中选择令牌配置,即可打开可在其中配置颁发给应用程序的令牌中所提供声明的页面。
选择“添加组声明”,以便打开“编辑组声明”屏幕。
选择“安全组”或“所有组(包括分发列表,但不包括分配给应用程序的组)”选项。 选择这两个选项会抵消“安全组”选项的效果。
在ID部分,选择组 ID。 选择此选项后,Microsoft Entra ID 将在用户登录后应用程序收到的 ID 令牌的组声明中发送用户分配给的组的对象 ID。
配置应用程序以便从用户可能分配给的已筛选组中接收组声明值
在下列情况时,此选项非常有用:
- 应用程序对登录用户可能被分配到的一组选定组感兴趣。
- 应用程序不会对该用户在租户中分配到的每个安全组都感兴趣。
此选项可帮助应用程序避免超额问题。
注意
Microsoft Entra ID 免费版中没有此功能。
使用此选项时,将无法使用嵌套组分配功能。
要在应用中启用此选项,请执行以下步骤:
在应用的注册页面上,在导航窗格中选择令牌配置,即可打开可在其中配置颁发给应用程序的令牌中所提供声明的页面。
选择“添加组声明”,以便打开“编辑组声明”屏幕。
选择已分配给应用程序的组。
选择其他选项,如“安全组”或“所有组(包括分发列表,但不包括分配给应用程序的组)”,会抵消应用程序选择使用此选项所带来的好处。
在ID部分,选择组 ID。 选择此选项后,Microsoft Entra ID 将在 ID 令牌的组声明中发送用户分配给的组的对象 ID。
如果您使用“公开 API”选项公开 Web API,那么还可以在“访问”部分中选择“组 ID”选项。 此选项会导致 Microsoft Entra ID 在访问令牌的组声明中发送用户分配给的组的对象 ID。
在应用的注册页面上,选择导航窗格中的“概述”,以便打开应用程序概览屏幕。
在本地目录中的受管应用程序中,选择名为您的应用程序的超链接。 此字段标题可能会被截断,例如
Managed application in ...。 选择此链接后,你将转到在你创建该应用程序的租户中与该应用程序的服务主体相关联的 企业应用程序概述 页面。 可以使用浏览器的后退按钮返回应用程序注册页面。在导航窗格中选择“用户和组”,以便打开为应用程序分配用户和组的页面。
选择“添加用户”。
从结果屏幕上选择“用户和组”。
选择要分配给此应用程序的组。
选择“选择”以完成组选择。
选择“分配”以完成组分配过程。
现在,当登录到你的应用的用户属于一个或多个已分配组时,你的应用会在组声明中收到这些选定的组。
在导航窗格中选择“属性”,打开列出应用程序基本属性的页面。将“需要用户分配?”标志设置为“是”。
重要
在将“需要进行用户分配?”设置为“是”时,Microsoft Entra ID 将检查只有在“用户和组”窗格中分配给应用程序的用户才能登录应用。 可以直接分配用户,也可以通过分配用户所属的安全组来分配。
配置应用程序 (java-servlet-webapp-groups) 以识别组 ID
按照以下步骤来配置应用:
重要
在“令牌配置”页面上,如果选择了“groupID”之外的任何选项,例如“DNSDomain\sAMAccountName”,则应在以下步骤中输入组名,例如 - 而不是对象 ID:contoso.com\Test Group
打开 ./src/main/resources/authentication.properties 文件。
找到字符串
{enter-your-admins-group-id-here},然后用从 Azure 门户复制的GroupAdmin组的对象 ID 替换现有值。 将占位符值中的大括号也删掉。找到字符串
{enter-your-users-group-id-here},然后用从 Azure 门户复制的GroupMember组的对象 ID 替换现有值。 将占位符值中的大括号也删掉。
生成示例
要使用 Maven 生成示例,请导航到包含示例的 pom.xml 文件的目录,然后运行以下命令:
mvn clean package
此命令会生成一个 .war 文件,它可以在各种应用服务器上运行。
运行示例
这些说明假定安装了 WebSphere 并设置了服务器。 可以按照在 Azure 虚拟机上部署 WebSphere 应用服务器(传统)集群中的指导进行基本服务器设置。
在部署到 WebSphere 之前,请按照以下步骤对示例本身进行一些配置更改,然后生成或重新生成包:
导航到应用的 authentication.properties 文件,并将
app.homePage的值更改为计划使用的服务器 URL 和端口号,如下例所示:# app.homePage is by default set to dev server address and app context path on the server # for apps deployed to azure, use https://your-sub-domain.azurewebsites.net app.homePage=https://<server-url>:<port-number>/msal4j-servlet-auth/保存此文件后,使用以下命令重新生成应用:
mvn clean package在代码完成生成后,将 .war 文件复制到目标服务器的文件系统中。
还需要在 Azure 应用注册中进行相同的更改,即在 Azure 门户中将其设置为“身份验证”选项卡上的“重定向 URI”值。
导航到面向开发人员的 Microsoft 标识平台应用注册页。
使用搜索框搜索应用注册,例如
java-servlet-webapp-authentication。选择应用名称,打开应用注册。
从菜单中选择“身份验证”。
在 Web - 重定向 URI 部分中,选择“添加 URI”。
填写应用程序的 URI,附加 /auth/redirect - 例如
https://<server-url>:<port-number>/auth/redirect。选择“保存”。
按照以下步骤使用 WebSphere 的集成解决方案控制台部署示例:
在“应用程序”选项卡上,选择“新建应用程序”,然后选择“新建企业应用程序”。
选择生成的 .war 文件,然后选择“下一步”,直至到达“映射 Web 模块的上下文根”安装步骤。 其他设置保持默认即可。
对于上下文根,将其设置为与在示例配置/Azure 应用注册中设置的“重定向 URI”中端口号之后的值相同。 也就是说,如果重定向 URI 是
http://<server-url>:9080/msal4j-servlet-auth/,那么上下文根应该是msal4j-servlet-auth。选择“完成”。
应用程序安装完成后,转到“应用程序”选项卡的“WebSphere 企业应用程序”部分。
从应用程序列表中选择已安装的 .war 文件,然后选择“开始”进行部署。
完成部署后,导航到
http://<server-url>:9080/{whatever you set as the context root},此时就可以看到应用程序。
查看示例
按照以下步骤来探索示例:
- 注意屏幕中央显示的登录或退出状态。
- 选择角落里的上下文相关按钮。 首次运行应用时,此按钮显示“登录”。
- 在下一页上,按照说明使用 Microsoft Entra ID 租户中的帐户登录。
- 在同意页面上,请注意所请求的权限范围。
- 请注意,上下文相关按钮现在显示“注销”并显示你的用户名。
- 选择“ID 令牌详细信息”以查看一些 ID 令牌的解码声明。
- 选择“组”以查看有关已登录用户的安全组成员身份的信息。
- 选择仅限管理员或普通用户以访问受群组声明保护的端点。
- 如果登录的用户属于
GroupAdmin组,则用户可以同时进入两个页面。 - 如果登录用户属于
GroupMember组,则该用户只能进入“常规用户”页面。 - 如果已登录的用户不在这两个组中,则无法访问这两个页面中的任何一个。
- 如果登录的用户属于
- 使用角落里的按钮注销。
- 退出登录后,选择 ID Token Details,观察当用户未获授权时,应用程序显示的是
401: unauthorized错误,而不是 ID 令牌声明。
关于代码
此示例使用 MSAL for Java (MSAL4J) 登录用户并获取可能包含组声明的 ID 令牌。 如果 ID 令牌中需要排放的组过多,示例会使用 适用于 Java 的 Microsoft Graph SDK 从 Microsoft Graph 获取组成员身份数据。 根据用户所属的组,登录的用户可以不访问、访问一个或同时访问受保护页面 Admins Only 和 Regular Users。
如果要复制此示例的行为,则必须使用 Maven 将 MSAL4J 和 Microsoft Graph SDK 添加到项目中。 可以复制 pom.xml 文件以及 src/main/java/com/microsoft/azuresamples/msal4j 文件夹中的 helpers 和 authservlets 文件夹里的内容。 还需要使用 authentication.properties 文件。 这些类和文件包含通用代码,可用于各种应用程序。 也可以复制示例的其他部分,但其他类和文件是专门为实现本示例的目标而生成的。
目录
下表列出了示例项目文件夹的内容:
| 文件/文件夹 | 说明 |
|---|---|
| src/main/java/com/microsoft/azuresamples/msal4j/groupswebapp/ | 此目录包含定义应用的后端业务逻辑的类。 |
| src/main/java/com/microsoft/azuresamples/msal4j/authservlets/ | 此目录包含供登录和退出登录端点使用的类。 |
| *Servlet.java | 所有可用的终结点都在 Java 类中定义,名称以 Servlet结尾。 |
| src/main/java/com/microsoft/azuresamples/msal4j/helpers/ | 用于身份验证的辅助类。 |
| AuthenticationFilter.java | 将对受保护终结点未经身份验证的请求重定向到 401 页面。 |
| src/main/resources/authentication.properties | Microsoft Entra ID 和程序配置。 |
| src/main/webapp/ | 此目录包含 UI - JSP 模板 |
| CHANGELOG.md | 示例变更列表。 |
| CONTRIBUTING.md | 参与示例的指南。 |
| 许可证 | 示例许可证。 |
处理令牌中的组声明,包括处理超额
以下各部分将介绍应用如何处理组声明。
这些团体声称
登录用户所属安全组的对象 ID 会在令牌的组声明中返回,如下例所示:
{
...
"groups": [
"0bbe91cc-b69e-414d-85a6-a043d6752215",
"48931dac-3736-45e7-83e8-015e6dfd6f7c",]
...
}
组超额声明
为确保令牌大小不超过 HTTP 标头大小限制,Microsoft 身份识别平台限制了组声明中包含的对象 ID 数量。
SAML 令牌的超额限制为 150 个,JWT 令牌为 200 个,单页应用程序为 6 个。 如果用户加入的组超过了超额限制,则 Microsoft 身份识别平台不会在令牌中的组声明中发出组 ID。 相反,它会在令牌中包含一个超限声明,指示应用程序查询 Microsoft 图形 API 以检索用户的组成员身份信息,如下例所示:
{
...
"_claim_names": {
"groups": "src1"
},
{
"_claim_sources": {
"src1": {
"endpoint":"[Graph Url to get this user's group membership from]"
}
}
...
}
在此示例中创建超额情形用于测试
要创建超额情形,可以执行以下步骤:
可以使用 AppCreationScripts 文件夹中提供的 BulkCreateGroups.ps1 文件来创建大量组并为其分配用户。 此文件有助于在开发过程中测试超额情况。 记得更改 BulkCreateGroups.ps1 脚本中提供的用户
objectId。运行此示例并发生超额时,你会在用户登录后在主页上看到 _claim_names。
我们强烈建议您在可能的情况下使用组筛选功能,以避免超出组数量限制。 有关详细信息,请参阅配置应用程序以便从用户可能分配给的已筛选组中接收组声明值部分。
如果无法避免遇到组超额的情况,则建议按照以下步骤来处理令牌中的组声明:
- 检查声明 _claim_names,其中一个值是 groups。 此项索赔表明已超额。
- 如已找到,则调用 _claim_sources 中指定的终结点来获取用户的组。
- 如果未找到,则查看 groups 声明以获取用户所属的组。
注意
处理超额需要调用 Microsoft Graph 来读取已登录用户的组成员身份,因此你的应用需要拥有 GroupMember.Read.All 权限才能成功执行 getMemberObjects 函数。
有关 Microsoft Graph 编程的详细信息,请观看视频面向开发人员的 Microsoft Graph 简介。
ConfidentialClientApplication
如下例所示,在 AuthHelper.java 文件中创建了一个 ConfidentialClientApplication 实例。 此对象有助于创建 Microsoft Entra 授权 URL,还有助于将身份验证令牌交换为访问令牌。
// getConfidentialClientInstance method
IClientSecret secret = ClientCredentialFactory.createFromSecret(SECRET);
confClientInstance = ConfidentialClientApplication
.builder(CLIENT_ID, secret)
.authority(AUTHORITY)
.build();
以下参数用于实例化:
- 应用的客户端 ID。
- 客户端机密,这是机密客户端应用程序的要求。
- 包含你的 Microsoft Entra 租户 ID 的 Microsoft Entra ID 颁发机构。
在本示例中,这些值是使用 Config.java 文件中的属性阅读器从 authentication.properties 文件读取的。
分步指南
以下步骤介绍了该应用的功能:
登录过程的第一步是向 Microsoft Entra ID 租户的
/authorize终结点发送请求。 MSAL4JConfidentialClientApplication实例用于构建授权请求 URL。 应用会将浏览器重定向到此 URL,即用户登录的 URL。final ConfidentialClientApplication client = getConfidentialClientInstance(); AuthorizationRequestUrlParameters parameters = AuthorizationRequestUrlParameters.builder(Config.REDIRECT_URI, Collections.singleton(Config.SCOPES)) .responseMode(ResponseMode.QUERY).prompt(Prompt.SELECT_ACCOUNT).state(state).nonce(nonce).build(); final String authorizeUrl = client.getAuthorizationRequestUrl(parameters).toString(); contextAdapter.redirectUser(authorizeUrl);下面列出了该代码的功能:
-
AuthorizationRequestUrlParameters:生成 AuthorizationRequestUrl 时必须设置的参数。 -
REDIRECT_URI:Microsoft Entra 在收集用户凭据后将浏览器连同授权代码重定向到的位置。 它必须与 Azure 门户中 Microsoft Entra ID 应用注册的重定向 URI 相匹配。 -
SCOPES:作用域是应用程序请求的权限。- 通常,三个范围
openid profile offline_access就足以接收 ID 令牌响应。 - 应用程序请求的全部范围列表可在 authentication.properties 文件中找到。 可以添加更多范围,如
User.Read。
- 通常,三个范围
-
Microsoft Entra ID 将会向用户显示登录提示。 如果登录尝试成功,用户的浏览器就会重定向到应用的重定向终结点。 向此终结点发出的有效请求包含授权代码。
然后,
ConfidentialClientApplication实例会将此授权代码与 Microsoft Entra ID 的 ID 令牌和访问令牌进行交换。// First, validate the state, then parse any error codes in response, then extract the authCode. Then: // build the auth code params: final AuthorizationCodeParameters authParams = AuthorizationCodeParameters .builder(authCode, new URI(Config.REDIRECT_URI)).scopes(Collections.singleton(Config.SCOPES)).build(); // Get a client instance and leverage it to acquire the token: final ConfidentialClientApplication client = AuthHelper.getConfidentialClientInstance(); final IAuthenticationResult result = client.acquireToken(authParams).get();下面列出了该代码的功能:
-
AuthorizationCodeParameters:为交换 ID 和/或访问令牌的授权代码而必须设置的参数。 -
authCode:在重定向终结点收到的授权代码。 -
REDIRECT_URI:必须再次传递上一步中使用的重定向 URI。 -
SCOPES:必须再次传递上一步中使用的范围。
-
如果
acquireToken成功,则提取令牌声明。 如果 nonce 检查通过,结果将被存入context(其是IdentityContextData的一个实例)中,并保存到会话中。 然后,应用程序在需要访问IdentityContextData时,可以像下面的代码所示,通过IdentityContextAdapterServlet的实例从会话中实例化IdentityContextData。// parse IdToken claims from the IAuthenticationResult: // (the next step - validateNonce - requires parsed claims) context.setIdTokenClaims(result.idToken()); // if nonce is invalid, stop immediately! this could be a token replay! // if validation fails, throws exception and cancels auth: validateNonce(context); // set user to authenticated: context.setAuthResult(result, client.tokenCache().serialize()); // handle groups overage if it has occurred. handleGroupsOverage(contextAdapter);完成上一步后,可以通过使用
context.getGroups()的实例调用IdentityContextData来提取组的成员身份。如果用户加入的组过多(超过 200 个),如果没有调用
context.getGroups(),则对handleGroupsOverage()的调用可能为空。 同时,context.getGroupsOverage()会返回true,表明发生了超额,需要调用 Microsoft Graph 才能获得完整的组列表。 请参阅 AuthHelper.java 中的handleGroupsOverage()方法,了解该应用程序在出现超额情况时如何使用context.setGroups()。
保护路由
请参阅 AuthenticationFilter.java,了解示例应用程序如何筛选对路由的访问。 在 authentication.properties 文件中,app.protect.authenticated 属性包含只有通过身份验证的用户才能访问的以逗号分隔的路由,如下例所示:
# for example, /token_details requires any user to be signed in and does not require special groups claim
app.protect.authenticated=/token_details
如以下示例所示,app.protect.groups 下以逗号分隔的规则集中列出的任何路由也禁止非身份验证用户使用。 但是,这些路由还包含一个以空格分隔的组成员身份列表。 只有属于至少一个相应组的用户才能在经过身份验证后访问这些路由。
# define short names for group IDs here for the app. This is useful in the next property (app.protect.groups).
# EXCLUDE the curly braces, they are in this file only as delimiters.
# example:
# app.groups=groupA abcdef-qrstuvw-xyz groupB abcdef-qrstuv-wxyz
app.groups=admin {enter-your-admins-group-id-here}, user {enter-your-users-group-id-here}
# A route and its corresponding group(s) that can view it, <space-separated>; the start of the next route & its group(s) is delimited by a <comma-and-space-separator>
# this says: /admins_only can be accessed by admin group, /regular_user can be accessed by admin group and user group
app.protect.groups=/admin_only admin, /regular_user admin user
作用域
范围表明了 Microsoft Entra ID 应用程序请求的访问级别。
根据请求的范围,Microsoft Entra ID 会在登录时向用户显示同意对话。 如果用户同意一个或多个范围并获得了一个令牌,则同意的范围将被编码到生成的 access_token 中。
有关应用程序请求的范围,请参阅 authentication.properties。 默认情况下,应用程序会将范围值设置为 GroupMember.Read.All。 如果应用程序需要调用 Graph 来获取用户的群组成员身份,则需要使用此特定的 Microsoft 图形 API 范围。
详细信息
- 适用于 Java 的 Microsoft 身份验证库 (MSAL)
- Microsoft 标识平台(针对开发人员的 Microsoft Entra ID)
- 快速入门:将应用程序注册到 Microsoft 标识平台
- 了解 Microsoft Entra ID 应用程序同意体验
- 了解用户同意和管理员同意
- MSAL 代码示例