练习 - 使用 MSAL 让用户登录
在本练习中,你将使用适用于 Java 的 Microsoft 身份验证库 (MSAL4J) 在示例 Java Web 应用程序中添加身份验证,并支持用户使用其 Microsoft Entra 帐户进行登录。
本练习中使用的示例应用程序是一个 Java servlet 应用程序,允许用户登录并显示用户名和基本配置文件信息。 同时还支持调用 Microsoft Graph API 以显示一些用户信息。
创建 Java Web 应用程序
从 shell 或命令行:
为应用程序创建文件夹。
mkdir ~/javawebapp
将 GitHub 存储库中的示例应用程序克隆到新文件夹中。
git clone https://github.com/Azure-Samples/ms-identity-java-servlet-webapp-authentication.git ~/javawebapp
切换到本练习的示例应用程序所在的文件夹。
cd ~/javawebapp/ms-identity-java-servlet-webapp-authentication/2-Authorization-I/call-graph
配置应用程序
若要配置代码,请打开首选 IDE 中的应用程序项目,例如 IntelliJ 或 VS Code。
打开
./src/main/resources/authentication.properties
文件。找到字符串
{enter-your-tenant-id-here}
。 将现有值替换为“目录(租户)ID”(如下图所示),因为该应用是使用“仅该组织目录中的帐户”选项注册的。找到字符串
{enter-your-client-id-here}
,并将现有值替换为从 Azure 门户复制的已注册应用程序的应用程序(客户端)ID (clientId)。找到字符串
{enter-your-client-secret-here}
,并将现有值替换为在 Azure 门户中创建应用程序期间保存的密钥。
运行应用程序
请确保 Tomcat 服务器正在运行,并且你具有向其部署 Web 应用的权限。 请确保服务器主机地址为
http://localhost:8080
。使用 Maven 编译和打包项目:
cd ~/javawebapp/2-Authorization-I/call-graph mvn clean package
在
./target/msal4j-servlet-graph.war
中找到生成的.war
文件。 若要部署到 Tomcat,请将此.war
文件复制到 Tomcat 安装目录中的/webapps/
目录,然后启动 Tomcat 服务器。打开浏览器并导航到
http://localhost:8080/msal4j-servlet-graph/
。 系统会你将重定向以使用 Microsoft Entra ID 登录。 成功登录后,应会看到如下页面:选择“ID 令牌详细信息”按钮以查看一些 ID 令牌的解码声明。
验证码概述
可在项目目录 java/com/microsoft/azuresamples/msal4j/
下的示例应用程序中找到大部分身份验证代码。 它包含多个 servlet,这些 servlet 会在应用程序中提供身份验证终结点,以进行登录、注销和处理来自 Microsoft Entra ID 的重定向回叫。 这些 servlet 使用 java/com/microsoft/azuresamples/msal4j/helpers/
目录中的帮助程序类来调用 MSAL 提供身份验证方法。 在 AuthenticationFilter.java
中定义的 servlet 筛选器将未经身份验证的请求重定向到受保护的路由,再转到 401 未经授权的 HTTP 错误页面。
若要向应用程序添加身份验证,需要在 java/com/microsoft/azuresamples/msal4j/authservlets
和 java/com/microsoft/azuresamples/msal4j/authwebapp
目录下包括 servlet 类、java/com/microsoft/azuresamples/msal4j/helpers/
目录中的帮助程序类和项目中的身份验证 servlet 筛选器 AuthenticationFilter.java
。 下面更详细地介绍了 MSAL 验证码。
MSAL4J 在 Maven 上可用。 你需要在项目的
pom.xml
文件中将 MSAL4J 添加为依赖项:<dependency> <groupId>com.microsoft.azure</groupId> <artifactId>msal4j</artifactId> <version>1.9.1</version> </dependency>
登录过程的第一步是向 Microsoft Entra 租户的
/authorize
终结点发送请求。 利用 MSAL4JConfidentialClientApplication
实例构造授权请求 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:重定向 URI 是标识提供者将安全令牌发送回的 URI。 收集用户凭据后,Microsoft Entra ID 会将浏览器(以及授权代码)重定向到此 URI。 它必须与 Microsoft Entra 应用注册中的重定向 URI 匹配。
- SCOPES:范围是应用程序请求的权限。 通常,这三个范围
openid profile offline_access
足以接收用户登录的 ID 令牌响应,并且默认由 MSAL 设置。
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
实例)并保存到会话中。 这样应用程序便可在需要访问该结果时从会话(通过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());