C# 将表单身份验证(授权)和基本身份验证(消息处理程序)结合在一起的安全Web API

C# 将表单身份验证(授权)和基本身份验证(消息处理程序)结合在一起的安全Web API,c#,authentication,asp.net-web-api,C#,Authentication,Asp.net Web Api,我试图同时使用表单身份验证(过滤器属性)和基本身份验证(消息处理程序)。我知道Web API的管道在消息处理程序之前执行,而不是在过滤器之前执行 我已经尝试了所有的方法,我也尝试过在谷歌上搜索,但是我找不到解决方案,我只能做认证和授权分离的工作,而不能一起做 代码: TestController.cs public class TestController : ApiController { private readonly worldEntities _db = new worldE

我试图同时使用表单身份验证(过滤器属性)和基本身份验证(消息处理程序)。我知道Web API的管道在消息处理程序之前执行,而不是在过滤器之前执行

我已经尝试了所有的方法,我也尝试过在谷歌上搜索,但是我找不到解决方案,我只能做认证和授权分离的工作,而不能一起做

代码:

TestController.cs

public class TestController : ApiController
{
    private readonly worldEntities  _db = new worldEntities();

    // GET api/Country
    [Authorize]
    public Capital Get()
    {

        var capital = new Capital
        {
            CapitalCountry = _db.cities.FirstOrDefault(),
            Country = _db.countries.FirstOrDefault()
        };
        capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code);
        _db.SaveChanges();
        return capital;

    }


    // Post api/Country
    public Capital Post()
    {

        var capital = new Capital
        {
            CapitalCountry = _db.cities.FirstOrDefault(),
            Country = _db.countries.FirstOrDefault()
        };
        capital.Cities = _db.cities.Where(s => s.CountryCode == capital.Country.Code);
        _db.SaveChanges();
        return capital;

    }

}
public class AuthController : ApiController
{

        public string Get(string id)
        {
            FormsAuthentication.SetAuthCookie(id ?? "FooUser", false);
            return "You are autenthicated now";
        }

}
WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.MessageHandlers.Add(new BasicAuthMessageHandler());

        config.Filters.Add(new AuthorizeAttribute());

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}
BasiAuthMessagHandle.cs

public class BasicAuthMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var headers = request.Headers;
        if (headers.Authorization != null && headers.Authorization.Scheme == "Basic")
        {
            var userPwd = Encoding.UTF8.GetString(Convert.FromBase64String(headers.Authorization.Parameter));
            var user = userPwd.Substring(0, userPwd.IndexOf(':'));
            var password = userPwd.Substring(userPwd.IndexOf(':') + 1);
            // we suppose that it's ok
            var principal = new GenericPrincipal(new GenericIdentity(user), null);
            PutPrincipal(principal);

        }

        return base.SendAsync(request, cancellationToken);
    }

    private void PutPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }
    }


}
Web.Config

<authentication mode="Forms" />


非常感谢你

我认为在如何保护Web API方面存在一些误解。在这里使用基于表单的身份验证的目的是什么

您拥有对用户进行身份验证的基本身份验证。为什么需要使用基于表单的身份验证来对用户进行身份验证

如果要验证用户的权限,请将用户凭据映射到一组声明,并在API控制器上进行检查


您可以查看以下链接

关于如何最好地使用授权包装Web API,在线上有许多示例(请参见Toan的答案)。我知道可以使用表单身份验证令牌保护web api,并使用示例中的属性。我认为你也不必编写自己的消息处理程序

从查看此网页开始。此外,Toan的答案和链接也是很好的建议。你肯定走对了路。通过构建带有身份验证的MVC模板,您还可以很好地了解ASP.NET Web API中的安全性工作原理,因为这些示例包括一个帐户管理控制器和所有用于进行身份验证和授权的代码

假设您已在网站中正确设置表单身份验证。当调用Web API方法时(Get、Post、Put、Delete、Options),Controller.User将是一个填充的IPrincipal对象,其中包含名称、IsAuthenticated bool和角色列表。这些值由表单身份验证块控制,当您使用[AllowAnonymous]或[Authorize]属性时,框架会查询这些值

请注意:在没有SSL的情况下使用表单身份验证是一件非常非常糟糕的事情,因为凭据是以明文形式共享的。表单验证也容易受到跨站点请求伪造的影响

下面是我在MVC4中使用的一个示例,它使用一个名为BaseApiController的超类在Web API上执行表单身份验证

    public BaseApiController()
    {
        CurrentUser = new ScrubbedUser(User);
    }
    protected ScrubbedUser CurrentUser { get; set; }
然后在我的ScrubedUser类中,我从数据库(或缓存/会话)检索用户信息,记住用户可以是匿名的

public class ScrubbedUser
    {
        private IPrincipal Principal { get; set; }
        public ScrubbedUser(string principal)
        {
            Principal = null;
            if (string.IsNullOrEmpty(principal))
            {
                Profile = GetDefaultProfile();
            }
            else
            {
                Profile = GetUserProfile(principal);
            }
            //Get User Memberships
            Memberships = GetUserMemberships();
            Settings = GetUserSettings();
        }
        public SurgeStreetUser(IPrincipal principal) 
        {
            Principal = principal;
            if (Principal == null
                || Principal.Identity == null
                || Principal.Identity.IsAuthenticated == false
                || string.IsNullOrEmpty(Principal.Identity.Name))
            {
                Profile = GetDefaultProfile();
            }
            else
            {
                Profile = GetUserProfile(Principal.Identity.Name);
            }
            //Get User Memberships
            Memberships = GetUserMemberships();
            Settings = GetUserSettings();
        }
        public UserProfile Profile { get; private set; }
        public List<V_UserMembership> Memberships { get; private set; }
        public List<Setting> Settings { get; private set; }

        private UserProfile GetDefaultProfile()
        {
            //Load an Anonymous Profile into the ScrubbedUser instance any way you like
        }
        private UserProfile GetUserProfile(string userName)
        {
            //Load the UserProfile based on userName variable (origin is Principle Identity Name
        }
        private List<V_UserMembership> GetUserMemberships()
        {
            //Load User's Memberships or Roles from Database, Cache or Session
        }
        private UserProfile PopulateCurrentUser(UserProfile userProfile)
        {
            var user = new UserProfile
            {
                //Convenience Method to clone and set a Profile Property
            };
            return user;
        }
        private List<Setting> GetUserSettings()
        {
            //Get the User's Settings or whatever
        }
        //Convenience to return a JSON string of the User (great to use with something like Backbone.js)
        private dynamic JSONRecord
        {
            get
            {
                return new
                {
                    CustId = Profile.CustId,
                    UserName = Profile.UserName,
                    UserId = Profile.UserId,
                    Email = Profile.Email,
                    FirstName = Profile.FirstName,
                    Language = Profile.Language,
                    LastActivityDate = Profile.LastActivityDate,
                    LastName = Profile.LastName,
                    DebugOption = Profile.DebugOption,
                    Device = Profile.Device,
                    Memberships = Memberships,
                    Settings = Settings
                };
            }
        }
    }

您遇到的错误是什么,何时出现?根据您的示例代码,TestController将无法工作,因为您的方法名称是Getcountries和Postcountries。如果没有动态路由(我没有看到),这些方法应该分别命名为Get和Post。谢谢,我已经将名称更改为这些方法。我没有任何错误。在这段代码中,仅适用于基本授权(仅检查授权标头是否存在),但如果cookie存在@PeptoI想要一起使用,则不适用,因为我想验证标头中的app_id和app_secret,并在验证之后验证用户是否有权使用该方法(例如使用cookie),谢谢!!谢谢我已经阅读了这篇文章,但我不知道我是否可以使用授权基础来验证一个App_Id和App_Secret,并在验证用户角色之后,例如。。。当做
public class ListController : BaseApiController
{
    //main is "global"
    public dynamic Get(string id)//JObject values)
    {
        //I can test here for anonymous as well, even if I allow anonymous

        //Example using my own convention on User Profile Class populated by ScrubbedUser constructor
        if (CurrentUser.Profile.CustId == "public")
        {
            return HttpStatusCode.Forbidden;
        }
        //Redundant Code
        if (!User.Identity.IsAuthenticated)
        {
            return HttpStatusCode.Forbidden;
        }
        string filterExt = string.IsNullOrEmpty(id) || id=="global"
            ? "*" : id;
        return ListRepository.GetList(filterExt, SSUser);
    }
    [Authorize]
    public dynamic Post(JObject values)
    {
        //Just a sample, this will not fire unless the user is authenticated
        return CurrentUser.JSONRecord;
    }
}