在现代应用程序开发中,保护用户凭据和数据安全至关重要。本文将介绍如何使用活动目录联合服务(ADFS)和OAuth 2协议来生成JSON Web Tokens(JWT)安全令牌,以增强应用程序的安全性。
应用程序池身份是一种安全机制,它允许应用程序在没有显式存储用户凭据的情况下运行。这是一种比在配置文件或其他资源文件中存储凭据更安全的方法。
为了简化JWT令牌的生成过程,创建了一个名为“OAuth2 Authorization Provider 1.0.0”的NuGet包,并将其推送到了NuGet服务器。可以通过以下命令安装该包:
Install-Package OAuth2.Authorization
安装包后,需要在Web或应用程序配置文件中设置适当的ADFS OAuth 2配置,并调用上述NuGet包中的帮助函数。
本文将介绍两种获取JWT令牌的方法:Adal流和OAuth代码流。
首先,需要在应用程序的配置文件中添加全局应用程序设置。
<add key="AdfsInstance" value="" />
<add key="ClientId" value="" />
<add key="Resource" value="" />
<add key="RedirectUri" value="http://localhost:56194/redirect" />
<add key="AdfsAuthorityUrl" value="https://{0}/adfs/ls/{1}" />
提供上述配置值后,调用NuGet包中提供的以下函数:
AdfsAuthorization.GetAdfsOAuthJwtAccessTokenForWinAppUserUsingAdal()
该函数内部使用Microsoft.IdentityModel.Clients.ActiveDirectory NuGet包创建ADFS的认证上下文。认证上下文需要resource、clientId、redirectUri、adfsInstance和AuthorityUrl参数来为登录用户提供JWT令牌。
这种方法有两个步骤:
同样,需要在应用程序的配置文件中添加全局应用程序设置。
<add key="AdfsInstance" value="" />
<add key="ClientId" value="" />
<add key="Resource" value="" />
<add key="RedirectUri" value="http://localhost:56194/redirect" />
<add key="AdfsTokenServiceUrl" value="https://{0}/adfs/oauth2/token/" />
提供上述配置后,调用NuGet包中的以下代码即可完成操作:
await AuthorizationManager.GetAdfsOAuthJwtAccessTokenForWinAppUserUsingAuthCodeAsync()
要获取auth代码,需要使用HttpClient请求并提供适当的ADFS认证URL。
以下是使用Adal流获取JWT令牌的完整代码示例:
if (!AdfsConfiguration.IsInitialized)
throw new SecurityException(Constants.AdfsConfigurationInitilizationExceptionMessage);
if (string.IsNullOrEmpty(AdfsConfiguration.AdfsAuthorityUrl))
throw new SecurityException(Constants.AdfsConfigurationAdfsAuthorityUrlInitilizationExceptionMessage);
try
{
var authenticationContext = new AuthenticationContext(string.Format(AdfsConfiguration.AdfsAuthorityUrl, AdfsConfiguration.AdfsInstance, AdfsConfiguration.Resource), false);
var asyncRequest = authenticationContext.AcquireTokenAsync(AdfsConfiguration.Resource, AdfsConfiguration.ClientId, new Uri(AdfsConfiguration.RedirectUri), new PlatformParameters(PromptBehavior.Auto));
var accessToken = asyncRequest.Result.AccessToken;
return accessToken;
}
catch (Exception exp)
{
var additionalInfo = $"additionalInfo : [authenticationContext : {string.Format(AdfsConfiguration.AdfsAuthorityUrl, AdfsConfiguration.AdfsInstance, AdfsConfiguration.Resource)}]";
throw new SecurityException($"AdfsAuthorization.GetAdfsOAuthJwtAccessTokenForWinAppUserUsingAdal is failed, {additionalInfo}", exp);
}
以下是使用OAuth2授权代码流获取JWT令牌的完整代码示例:
var authUrl = string.Format(AdfsConfiguration.AdfsAuthUrl, AdfsConfiguration.AdfsInstance, AdfsConfiguration.ClientId, AdfsConfiguration.Resource, AdfsConfiguration.UrlEncodedRedirectUri);
var authCode = "";
try
{
do
{
var result = await Client.GetAsync(authUrl);
await result.Content.ReadAsStringAsync();
IEnumerable<string> values;
if (result.Headers.TryGetValues("location", out values))
{
foreach (string s in values)
{
if (s.Contains("code="))
{
authUrl = "";
authCode = s.Substring(s.IndexOf("code=", StringComparison.Ordinal) + 5);
}
else
{
authUrl = s;
}
}
}
else
{
authUrl = "";
}
}
while (!string.IsNullOrEmpty(authUrl));
return authCode;
}
catch (Exception exp)
{
var additionalInfo = $"additionalInfo : [authUrl: {authUrl}]";
throw new SecurityException($"AdfsAuthorization.GetAuthCodeForWinAppUserAsync is failed, {additionalInfo}", exp);
}
获取AuthCode后,使用WebClient获取JWT令牌,该令牌是加密格式的。如果想要查看它包含的内容,那么需要有适当的证书来验证并查看它包含的内容。这个功能也在NuGet包中提供。
var client = new WebClient();
try
{
if (AdfsConfiguration.UseProxy == "Y")
{
var proxyObject = new WebProxy("Proxy", 80) { Credentials = CredentialCache.DefaultNetworkCredentials };
client.Proxy = proxyObject;
}
Uri address = new Uri(string.Format(AdfsConfiguration.AdfsTokenServiceUrl, AdfsConfiguration.AdfsInstance));
Uri redirectAddress = new Uri(AdfsConfiguration.RedirectUri);
NameValueCollection values = new NameValueCollection
{
{ "client_id", AdfsConfiguration.ClientId},
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", redirectAddress.ToString() }
};
byte[] responseBytes = client.UploadValues(address, "POST", values);
string response = System.Text.Encoding.UTF8.GetString(responseBytes);
return response;
}
catch (Exception exp)
{
var additionalInfo = $"additionalInfo : [address: {string.Format(AdfsConfiguration.AdfsTokenServiceUrl, AdfsConfiguration.AdfsInstance) }, redirect Uri :{AdfsConfiguration.RedirectUri}]";
throw new SecurityException($"AdfsAuthorization.GetAdfsOAuthTokenByAuthCode is failed, {additionalInfo}", exp);
}
finally
{
client.Dispose();
}