Ajax 使用 Forms 身份验证调用 Web API

Ajax Calls Web API With Forms Authentication

我们一直在开发 asp.net Web 应用程序,并且我们已经在该应用程序中实现了表单身份验证。最近我们收到要求为其他系统公开应用程序的某些功能。因此,我们计划为此应用程序开发 Web API。我们需要在同一个 IIS Web 应用程序下托管这些 Web API,并且我们需要确保它们的安全。所以我将 [Authorize] 属性添加到 Web API Controller class.

现在我对它的安全性感到困惑。如果我们保留 Forms Authentication,那么我怎样才能让我的外部系统调用得到验证?或者我是否需要使用其他一些身份验证机制(例如:OAuth)但是它会在我们拥有表单身份验证的同一个应用程序中工作吗?我还在 API 控制器 class 上启用了 COR。混淆仅与安全机制有关。

我们有 angular 应用程序和本地移动应用程序来使用这些 API。 Angular 应用程序单独托管在 IIS 中,移动应用程序在 IOS 和 Android 上 运行。

请提出解决方案,因为我不确定应该向哪个方向移动。

此致, 贾米尔

我关注这篇文章Mixing MVC + Web API Authentication (Using Forms Auth + Basic Auth)

简而言之,对于外部系统,要对Web进行身份验证API,不能使用Forms Auth,我们必须使用Basic Auth或OAuth。 OAuth 不能与 Form Auth 混合使用,除非整个应用程序升级为使用 OAuth,但是通过为 Web API 定义 SecurityFilter,我们可以保持 Forms Authentication 在 Web 应用程序中工作,并与我们定义 Basic 并行将使用基本身份验证或我们可以使用的任何其他身份验证机制的身份验证安全性。

对我来说,这个解决方案有效。

  1. 我在 Web 表单项目中创建了一个 Web API 控制器。
  2. 默认情况下,由于使用 Web.Config 在整个站点级别启用了表单身份验证。这个新控制器还使用表单身份验证进行保护。为了跳过该安全性,我在网络配置中添加了一个位置标记,并允许所有用户访问网络 API 路径。

    <location path="api">
        <system.web>
            <authorization>
                <allow users="*" />
            </authorization>
        </system.web>
    </location>
    
  3. 接下来,我定义安全过滤器 class 并实现我自己的请求验证和授权逻辑。

    public class BasicAuthenticationIdentity : GenericIdentity
    {
        public BasicAuthenticationIdentity(string name, string password)
            : base(name, "Basic")
        {
            this.Password = password;
        }
    
        /// 
        /// Basic Auth Password for custom authentication
        /// 
        public string Password { get; set; }
    }
    
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    public class BasicAuthenticationFilter : AuthorizationFilterAttribute
    {
        bool Active = true;
    
        public BasicAuthenticationFilter()
        { }
    
        ///
        /// Overriden constructor to allow explicit disabling of this
        /// filter's behavior. Pass false to disable (same as no filter
        /// but declarative)
        ///
        ///
        public BasicAuthenticationFilter(bool active)
        {
            Active = active;
        }
    
        ///
        /// Override to Web API filter method to handle Basic Auth check
        ///
        ///
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            if (Active)
            {
                var identity = ParseAuthorizationHeader(actionContext);
                if (identity == null)
                {
                    Challenge(actionContext);
                    return;
                }
    
                if (!OnAuthorizeUser(identity.Name, identity.Password, actionContext))
                {
                    Challenge(actionContext);
                    return;
                }
    
                var principal = new GenericPrincipal(identity, null);
    
                Thread.CurrentPrincipal = principal;
    
                // inside of ASP.NET this is required
                //if (HttpContext.Current != null)
                //    HttpContext.Current.User = principal;
    
                base.OnAuthorization(actionContext);
            }
        }
    
        ///
        /// Base implementation for user authentication - you probably will
        /// want to override this method for application specific logic.
        /// 
        /// The base implementation merely checks for username and password
        /// present and set the Thread principal.
        /// 
        /// Override this method if you want to customize Authentication
        /// and store user data as needed in a Thread Principle or other
        /// Request specific storage.
        ///
    
        protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
                return false;
    
            return true;
        }
    
        ///
        /// Parses the Authorization header and creates user credentials
        ///
        protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
        {
            string authHeader = null;
            var auth = actionContext.Request.Headers.Authorization;
            if (auth != null && auth.Scheme == "Basic")
                authHeader = auth.Parameter;
    
            if (string.IsNullOrEmpty(authHeader))
                return null;
    
            authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
    
            var tokens = authHeader.Split(':');
            if (tokens.Length < 2)
                return null;
    
            return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
        }
    
        ///
        /// Send the Authentication Challenge request
        ///
        void Challenge(HttpActionContext actionContext)
        {
            var host = actionContext.Request.RequestUri.DnsSafeHost;
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            //actionContext.Response = actionContext.Request.CreateUserNoAccessResponse();
            actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", host));
        }
    }
    

    public class MyBasicAuthenticationFilter : BasicAuthenticationFilter
    {
        public MyBasicAuthenticationFilter()
        { }
    
        public MyBasicAuthenticationFilter(bool active)
            : base(active)
        { }
    
        protected override bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
        {
            return true;
            //MembershipHelper Members = new MembershipHelper(UmbracoContext.Current);
    
            //if (Members.Login(username, password))
            //{
            //    // Here you can also check against a specific member type or member group
    
            //    Members.Logout();
            //    return true;
            //}
            //return false;
        }
    }
    
    [MyBasicAuthenticationFilter]
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        public List<string> Get()
        {
            return new List<string>() { "Value 1", "Value 2" };
        }
    }
    

    var authHeaders = {};
    authHeaders.Authorization = 'Basic ' + atob(username + ":" + password);
    $.ajax({
       url: "http://localhost:49501/api/values",
       type: "GET",
       headers: authHeaders,
       success: function (response) {
       }
    });