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



我正在尝试同时使用表单身份验证(Filter Attribute)和基本身份验证(Message Handler)。我知道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;
    }
}

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

}

AuthController.cs

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

Web.Config

<authentication mode="Forms" />

非常感谢!!

关于如何使用授权来最好地包装Web API,在线有很多示例(请参阅Toan的回答)。我知道可以使用Forms Authentication令牌来保护web api,并在您的示例中使用这些属性。我认为您也不应该编写自己的消息处理程序。

http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

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

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

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

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

    public BaseApiController()
    {
        CurrentUser = new ScrubbedUser(User);
    }
    protected ScrubbedUser CurrentUser { get; set; }

然后在我的ScrubbedUser类中,我从数据库(或缓存/会话)中检索用户的信息,记住用户可能是匿名

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

我使用Membership而不是Roles,并且可以使用Super类的CurrentUser属性来测试用户是否是某个组织的成员。我还可以在类级别或方法级别上使用Web API控制器上的[Authorize]属性

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

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

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

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

您可以查看以下链接http://channel9.msdn.com/Shows/Web+营地+电视/保护ASPNET Web API

最新更新