基于WCF Rest Token的身份验证

时间:2016-01-01 05:06:37

标签: wcf wcf-authentication

我正在研究WCF Rest应用程序我需要在其中实现基于令牌的身份验证。请建议我实现基于令牌的身份验证WCF Rest的完美方法。

2 个答案:

答案 0 :(得分:1)

您可以实施承载令牌身份验证

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() });
                }
            }
        }
    }
}

这是最简单的解决方案,就像魅力一样!

答案 1 :(得分:0)

我能够在基于 WCF 的 SOAP 服务中实现基于 AAD 令牌的身份验证

为此,我通过以下方式利用了 WCF 可扩展性功能 - Message InspectorCustom Invoker

  1. 消息检查器:使用消息检查器,我们从传入请求的授权标头中提取承载令牌。发布后,我们使用 OIDC 库执行令牌验证以获取 Microsoft AAD 的密钥和配置。如果令牌通过验证,则调用该操作并在客户端获得响应。

    如果令牌验证失败,我们会使用自定义调用程序停止请求处理,并向调用方返回 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);
        }
    }
}

注意 - 请参阅this gist for complete Message Inspector code

  1. 自定义调用程序 - 如果令牌无效,自定义调用程序的工作是停止请求流到 WCF 请求处理管道的进一步阶段。这是通过在当前 OperationContext(在消息检查器中设置)中将 Authorized 标志设置为 false 并在自定义调用程序中读取相同内容以停止请求流来完成的。
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);
    }
}

这是complete gist for Custom Invoker

现在您需要使用端点行为扩展元素将消息检查器和自定义调用程序注入您的 WCF 管道。以下是执行此操作的类文件的要点以及其他一些需要的帮助类:

  1. BearerTokenEndpointBehavior
  2. BearerTokenExtensionElement
  3. MyOperationBehavior
  4. OpenIdConnectCachingSecurityTokenProvider
  5. 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 令牌,并且您的租户只需更改其一侧的绑定和端点即可使用相同的令牌。请注意,您需要在 web.config 的 allowedTenantIDs 列表中添加租户的客户端 ID,以便授权租户访问您的服务。


最后说明 - 虽然我已经实现了 Microsoft 的基于 AAD 的身份验证,但您应该能够重用整个代码来实现任何基于 OAuth 的身份提供者的令牌验证。您只需要在 web.config 中更改 AADAuthority 的相应密钥。