使用组和组声明保护 Java WebSphere 应用
本文将介绍如何创建一个 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 ID 博客,了解最新进展情况。
设置示例
以下各部分将介绍如何设置示例应用程序。
克隆或下载示例存储库
要克隆示例,请打开 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”值,以供稍后使用。 可以在应用程序的一个或多个配置文件中使用此值。
选择“保存”以保存更改。
在应用的注册页面上,选择导航窗格中的“证书和机密”,打开可生成机密和上传证书的页面。
在“客户端密码”部分中,选择“新建客户端密码”。
键入描述,例如“应用机密”。
从可用期限中选择一个:“1 年内”、“2 年内”或“永不过期”。
选择 添加 。 此时将显示生成的值。
复制并保存生成的值,以供在之后的步骤中使用。 代码的配置文件需要使用此值。 此值不会再显示,也无法通过任何其他方式获取。 因此,在导航到任何其他屏幕或窗格之前,请务必从 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 令牌详细信息”,观察应用程序是否会在用户未获授权时显示 错误,而不是 ID 令牌声明。
401: unauthorized
关于代码
此示例使用 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 Graph API 以检索用户的组成员身份,如下例所示:
{
...
"_claim_names": {
"groups": "src1"
},
{
"_claim_sources": {
"src1": {
"endpoint":"[Graph Url to get this user's group membership from]"
}
}
...
}
在此示例中创建超额方案以进行测试
要创建超额方案,可以按照以下步骤操作:
可以使用 AppCreationScripts 文件夹中提供的 BulkCreateGroups.ps1 文件来创建大量组并为其分配用户。 此文件有助于在开发过程中测试超额情况。 记得更改
objectId
脚本中提供的用户 。运行此示例并发生超额时,你会在用户登录后在主页上看到 _claim_names。
强烈建议尽可能使用组筛选功能,以避免出现组超额的情况。 有关详细信息,请参阅配置应用程序以便从用户可能分配给的已筛选组中接收组声明值部分。
如果无法避免遇到组超额的情况,则建议按照以下步骤来处理令牌中的组声明:
- 检查声明 _claim_names,其中一个值是 groups。 此声明表示超额。
- 如已找到,则调用 _claim_sources 中指定的终结点来获取用户的组。
- 如果未找到,则查看组声明中的用户组。
注意
处理超额需要调用 Microsoft Graph 来读取已登录用户的组成员身份,因此你的应用需要拥有 GroupMember.Read.All 权限才能成功执行 getMemberObjects 函数。
有关 Microsoft Graph 编程的详细信息,请观看视频面向开发人员的 Microsoft Graph 简介。
ConfidentialClientApplication
如下例所示,在 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
的实例从会话中将其实例化,如以下代码所示:// 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 才能获得完整的组列表。 请参阅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 Graph API 范围。
详细信息
- 适用于 Java 的 Microsoft 身份验证库 (MSAL)
- Microsoft 标识平台(针对开发人员的 Microsoft Entra ID)
- 快速入门:将应用程序注册到 Microsoft 标识平台
- 了解 Microsoft Entra ID 应用程序同意体验
- 了解用户同意和管理员同意
- MSAL 代码示例