【问题标题】:WCF Rest Token based authentication基于 WCF REST 令牌的身份验证
【发布时间】:2016-01-01 05:06:37
【问题描述】:

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

【问题讨论】:

    标签: wcf wcf-authentication


    【解决方案1】:

    可以实现 Bearer 令牌认证

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

    这是最简单的解决方案,而且效果很好!

    【讨论】:

    • WCF 还不支持 OWIN。
    • 我也无法用 WCF 编译它。
    【解决方案2】:

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

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

    1. Message Inspector :使用消息检查器,我们从传入请求的 Authorization Header 中提取 Bearer Token。发布此消息后,我们使用 OIDC 库执行令牌验证,以获取 Microsoft AAD 的密钥和配置。如果令牌被验证,则调用该操作并在客户端获得响应。

      如果 Token 验证失败,我们使用 Custom Invoker 停止请求处理,并向调用者返回 401 Unauthorized response 和自定义错误消息。

    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(在 Message Inspector 中设置)中将 Authorized 标志设置为 false 并在 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);
        }
    }
    

    这是complete gist for Custom Invoker

    现在您需要使用 Endpoint Behavior Extension Element 将 Message Inspector 和 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 的相应密钥。

    【讨论】:

      猜你喜欢
      • 2010-12-24
      • 1970-01-01
      • 2016-07-16
      • 2017-11-27
      • 2013-08-23
      • 1970-01-01
      • 2021-01-25
      • 2018-01-01
      • 2011-03-18
      相关资源
      最近更新 更多