Проверка подлинности WCF Rest Token
Я работаю над приложением WCF Rest. Мне нужно реализовать в нем аутентификацию на основе токенов. Пожалуйста, предложите мне идеальный способ реализации аутентификации на основе токенов WCF Rest.
2 ответа
Мне удалось реализовать аутентификацию на основе токена AAD в службе SOAP на основе WCF .
Для этого я использовал функции расширяемости WCF -
Message Inspector
а также
Custom Invoker
следующим образом
- Инспектор сообщений : с помощью инспектора сообщений мы извлекаем токен-носитель из заголовка авторизации входящего запроса. Опубликуйте это, мы выполняем проверку токена с использованием библиотеки OIDC, чтобы получить ключи и конфигурацию для Microsoft AAD. Если токен проверен, операция вызывается, и мы получаем ответ на стороне клиента.
Если проверка токена не удалась, мы останавливаем обработку запроса с помощью Custom Invoker и возвращаем вызывающему абоненту ответ 401 Unauthorized с настраиваемым сообщением об ошибке.
public class BearerTokenMessageInspector : IDispatchMessageInspector
{
/// Method called just after request is received. Implemented by default as defined in IDispatchMessageInspector
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
WcfErrorResponseData error = null;
var requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if (request == null)
{
error = new WcfErrorResponseData(HttpStatusCode.BadRequest, string.Empty, new KeyValuePair<string, string>("InvalidOperation", "Request Body Empty."));
return error;
}
var authHeader = requestMessage.Headers["Authorization"];
try
{
if (string.IsNullOrEmpty(authHeader))
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Error: Authorization Header empty! Please pass a Token using Bearer scheme."));
}
else if (this.Authenticate(authHeader))
{
return null;
}
}
catch (Exception e)
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + e.Message));
}
if (error == null) //Means the token is valid but request must be unauthorized due to not-allowed client id
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + "The client ID: " + clientID + " might not be in the allowed list."));
}
//This will be checked before the custom invoker invokes the method, if unauthorized, nothing is invoked
OperationContext.Current.IncomingMessageProperties.Add("Authorized", false);
return error;
}
/// Method responsible for validating the token and tenantID Claim.
private bool Authenticate(string authHeader)
{
const string bearer = "Bearer ";
if (!authHeader.StartsWith(bearer, StringComparison.InvariantCultureIgnoreCase)) { return false; }
var jwtToken = authHeader.Substring(bearer.Length);
PopulateIssuerAndKeys();
var validationParameters = GenerateTokenValidationParameters(_signingKeys, _issuer);
return ValidateToken(jwtToken, validationParameters);
}
/// Method responsible for validating the token against the validation parameters. Key Rollover is
/// handled by refreshing the keys if SecurityTokenSignatureKeyNotFoundException is thrown.
private bool ValidateToken(string jwtToken, TokenValidationParameters validationParameters)
{
int count = 0;
bool result = false;
var tokenHandler = new JwtSecurityTokenHandler();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out SecurityToken validatedToken);
result = (CheckTenantID(validatedToken));
return result;
}
/// Method responsible for sending proper Unauthorized reply if the token validation failed.
public void BeforeSendReply(ref Message reply, object correlationState)
{
var error = correlationState as WcfErrorResponseData;
if (error == null) return;
var responseProperty = new HttpResponseMessageProperty();
reply.Properties["httpResponse"] = responseProperty;
responseProperty.StatusCode = error.StatusCode;
var headers = error.Headers;
if (headers == null) return;
foreach (var t in headers)
{
responseProperty.Headers.Add(t.Key, t.Value);
}
}
}
ПРИМЕЧАНИЕ - Пожалуйста, обратитесь к этой сути для получения полного кода Инспектора сообщений .
- Пользовательский Invoker - задача пользовательского Invoker заключается в том, чтобы остановить поток запросов на дальнейшие этапы конвейера обработки запросов WCF, если токен недействителен. Это делается путем установки флага Authorized как false в текущем OperationContext (установленном в Message Inspector) и чтения этого же в Custom Invoker, чтобы остановить поток запросов.
class CustomInvoker : IOperationInvoker
{
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Check the value of the Authorized header added by Message Inspector
if (OperationContext.Current.IncomingMessageProperties.ContainsKey("Authorized"))
{
bool allow = (bool)OperationContext.Current.IncomingMessageProperties["Authorized"];
if (!allow)
{
outputs = null;
return null;
}
}
// Otherwise, go ahead and invoke the operation
return defaultInvoker.Invoke(instance, inputs, out outputs);
}
}
Вот полная суть Custom Invoker.
Теперь вам нужно внедрить инспектор сообщений и настраиваемый активатор в конвейер WCF с помощью элемента расширения поведения конечной точки. Вот суть файлов классов для этого и нескольких других необходимых вспомогательных классов:
- BearerTokenEndpointBehavior
- BearerTokenExtensionElement
- MyOperationBehavior
- OpenIdConnectCachingSecurityTokenProvider
- WcfErrorResponseData
Работа еще не завершена !
Вам необходимо написать настраиваемую привязку и предоставить настраиваемую конечную точку AAD в вашем web.config помимо добавления ключей конфигурации AAD -
//List of AAD Settings
<appSettings>
<add key="AADAuthority" value="https://login.windows.net/<Your Tenant ID>"/>
<add key="AADAudience" value="your service side AAD App Client ID"/>
<add key="AllowedTenantIDs" value="abcd,efgh"/>
<add key="ValidateIssuer" value="true"/>
<add key="ValidateAudience" value="true"/>
<add key="ValidateIssuerSigningKey" value="true"/>
<add key="ValidateLifetime" value="true"/>
<add key="useV2" value="true"/>
<add key="MaxRetries" value="2"/>
</appSettings>
<bindings>
<wsHttpBinding>
//wsHttpBinding needs client side AAD Token
<binding name="wsHttpBindingCfgAAD" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00">
<readerQuotas maxDepth="26214400" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
//Exposing a new baseAddress/wssecureAAD endpoint which will support AAD Token Validation
<service behaviorConfiguration="ServiceBehaviorCfg" name="Service">
<!--wshttp endpoint with client AAD Token based security-->
<endpoint address="wsSecureAAD" binding="wsHttpBinding" bindingConfiguration="wsHttpBindingCfgAAD" name="ServicewsHttpEndPointAAD" contract="ServiceContracts.IService" behaviorConfiguration="AADEnabledEndpointBehavior"/>
</service>
</services>
<behaviors>
<endpointBehaviors> //Injecting the Endpoint Behavior
<behavior name="AADEnabledEndpointBehavior">
<bearerTokenRequired/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions> //Linking the BearerTokenExtensionElement
<add name="bearerTokenRequired" type="TokenValidator.BearerTokenExtensionElement, TokenValidator"/>
</behaviorExtensions>
</extensions>
Теперь ваша служба WCF должна принимать токены AAD на этой настраиваемой конечной точке AAD, и ваши клиенты смогут использовать их, просто изменив привязку и конечную точку со своей стороны. Обратите внимание, что вам нужно будет добавить идентификатор клиента клиента в список allowedTenantIDs в web.config, чтобы разрешить клиенту использовать вашу службу.
Заключительное примечание. Хотя я реализовал аутентификацию на основе AAD от Microsoft, вы должны иметь возможность повторно использовать весь код для реализации любой проверки токена поставщика удостоверений на основе OAuth. Вам просто нужно изменить соответствующие ключи для AADAuthority в web.config.
Проголосуйте, если вы нашли какую-либо из приведенных выше сведений полезной ! Прокомментируйте, если у вас есть вопросы, будем рады их разрешить.
Мир :)
Вы можете реализовать аутентификацию токена на предъявителя
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Http;
[assembly: OwinStartup(typeof(ns.Startup))]
namespace ns
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
config.MessageHandlers.Add(new LogRequestAndResponseHandler());
}
Настройте использование OAuthBearerAuthentication:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/TokenService"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(3),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
И, наконец, установить претензии личности
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "Name"));
identity.AddClaim(new Claim(ClaimTypes.Sid, "Sid"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Role"));
context.Validated(identity);
}
catch (System.Exception ex)
{
context.SetError("Error....");
context.Response.Headers.Add("X-Challenge", new[] { ((int)HttpStatusCode.InternalServerError).ToString() });
}
}
}
}
}
Это самое простое решение и работает как шарм!