c# - 结合表单例份验证(授权)和基本身份验证(消息处理程序)的安全 Web API

标签 c# authentication asp.net-web-api

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

我已经尝试了所有方法并且我尝试在谷歌中搜索很多但我找不到解决方案,我只能将身份验证和授权分开但不能一起工作。

代码:

测试 Controller .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 token 保护 web api 并在您的示例中使用这些属性。我认为您也不必编写自己的消息处理程序来执行此操作。

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

首先查看此网页。此外,Toan 的回答和链接也是很好的建议。你当然是在正确的轨道上。您还可以通过构建带身份验证的 MVC 模板,很好地了解安全性在 ASP.NET Web API 中的工作原理,因为这些示例包括帐户管理 Controller 和所有用于执行身份验证和授权的代码。

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

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

这是我在 MVC4 中使用的一个示例,使用名为 BaseApiController 的父类(super class)在 Web API 上执行表单例份验证

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

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

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

我使用 Memberships 而不是 Roles,并且可以使用 Super Class 的 CurrentUser 属性来测试用户是否是某事物的成员。我还可以在类级别或方法级别使用 Web API Controller 上的 [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;
    }
}

关于c# - 结合表单例份验证(授权)和基本身份验证(消息处理程序)的安全 Web API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23351624/

相关文章:

Rest API 为同一资源设计多个 URL?

c# - 在 C# 中创建图像

c# - 在应用程序中在哪里存储字符串?

c# - 以编程方式访问 Google Chrome 主页或起始页

cakephp 防止用户同时从多个位置登录

angularjs - Angular -> Web API 2 : "No ' Access-Control-Allow-Origin' header"

c# - MySQL - 返回不正确的 UTF8 字符

重定向到同一 URL 的 Python 社交身份验证

Java EE 6 登录模块

c# - 如何使用 Unity 容器解决 Web API 中依赖项的依赖关系?