使用 AngularJS、WebAPI 2 和 Oauth 2 时,如何将授权信息发送回我的客户端应用程序?

Posted

技术标签:

【中文标题】使用 AngularJS、WebAPI 2 和 Oauth 2 时,如何将授权信息发送回我的客户端应用程序?【英文标题】:How can I send authorization information back to my client app when using AngularJS, WebAPI 2 and Oauth 2? 【发布时间】:2014-07-07 05:44:02 【问题描述】:

我有一个AngularJS 客户端应用程序,它使用javascript(不是coffeescript 或typescript)Oauth2 使用最新的Identity 2WebAPI 2 应用程序进行身份验证。我的应用程序中的所有软件都是最新的,并且基于this example。我的客户端浏览器目标是 IE9 及以上。

请注意,我对上面的示例做了一些小的更改,因为我没有使用转换对发送到服务器的所有数据进行 urlencode。相反,我只在下面的身份验证方法中进行 urlencode:

user.authenticate = function (userName, password, rememberMe, successCallback, errorCallback) 
    var config = 
        method: 'POST',
        url: '/Token',
        headers:  'Content-Type': 'application/x-www-form-urlencoded' ,
        data: 'grant_type=password&username=' + encodeURIComponent(userName) + '&password=' + encodeURIComponent(password),
    ;

我正在使用 VS2013 Update 2 进行开发,在服务器上,我使用 C#、最新的实体框架和 SQL Server 2012。

要登录,我的客户端调用 WebAPI 的 /Token 方法并传递用户 ID 和密码。 WebAPI 然后用一个令牌响应我存储的客户端。对于 WebAPI 的每个请求,都会发回令牌并进行身份验证:

$http.defaults.headers.common.Authorization = 'Bearer ' + user.data.bearerToken;

到目前为止,这工作得很好,但就目前而言,应用程序无法区分分配给他们不同角色的用户之间的区别。

某些 WebAPI 方法只能由具有特定角色的用户执行。我想调整我的前端 AngularJS 应用程序的菜单,以便只有当用户具有此角色时,相应的链接才会可见。我确实意识到这不会阻止用户检查 html 和发布,但我并不担心这一点,因为我仍然有方法修饰来限制非角色用户执行操作的能力。

谁能给我一个示例,说明如何使用我在问题中提到的上面提到的产品套件以及 JavaScript Web 令牌(如果它们有助于使解决方案保持最新)来做到这一点.据我了解,角色由声明处理,但我不明白如何添加这些并使用令牌将它们发送回客户端。我在互联网上做了很多研究,但我找不到任何好的例子,因为我认为其中大部分都是非常新的,没有多少人有机会探索 SPA 如何使用这些最新的软件组件。

回答此问题时请注意,我不是在寻找可以告诉社区如何在服务器上设置角色的答案,也不是在寻找解释提供角色的重要性的答案检查服务器。我想几乎每个人都知道这一点。我真正认为有用的是一些非常详细的技术建议,带有示例代码和解释。为了保持答案的重点,如果不满足此需求的答案未作为建议答案发布,可能会对每个人都有帮助。

提前谢谢你。

【问题讨论】:

您能否详细解释一下“角色由索赔处理”?声明是角色的替代方案,但如果您仍想使用角色,请添加类型为 ClaimTypes.Role 的声明 我认为这可能会有所帮助:msdn.microsoft.com/en-us/library/hh545448.aspx 【参考方案1】:

这里有另一个答案:

ApplicationOAuthProvider.cs

  public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);


            if (user == null)
            
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            

只需添加一个自定义标题!

 context.OwinContext.Response.Headers.Add("Roles", userManager.GetRoles(user.Id).ToArray());

【讨论】:

【参考方案2】:

我的情况与您的情况非常相似,但我没有使用令牌进行身份验证,而是使用身份服务器 (Thinktecture) 来处理我的身份验证。我的应用程序重定向到身份服务器进行身份验证,并返回一些非常基本的声明(用户名和电子邮件)。一旦有人尝试首先浏览该页面,就会发生这种情况。一旦用户通过身份验证并重定向到我的应用程序,我就会再次调用服务器以获取用户的权限。这些权限存储在安全服务 (AngularJS) 中,该服务还公开了“hasPermissions”方法。然后我使用 ng-if 来决定是否要显示页面的某些部分——包括菜单项。大意是这样的:

var service = 
    currentUser: ...,
    isAuthenticated: function() 
        return ...;
    ,
    checkAccess: function(permission) 
        return service.isAuthenticated() ?
            !!(service.currentUser.permissions.indexOf(permission) > -1) : false;
    

请记住,所有决定点击开发工具按钮并查看的人都可以看到所有权限和 html 元素。在执行任何操作之前,您必须在服务器端进行相同的检查。我们有一个基于 this 的自定义 Authorization 属性,用于检查用户是否具有执行 MVC/WebAPI 操作所需的权限,然后再执行简单的情况,或者在执行任何操作之前在操作或 HTTP 资源中实际检查它需要提升的权限。

如果您希望客户端看不到任何 html 元素或站点的某些部分,您可以将模板指向 MVC 操作,该操作将进行身份验证,然后返回 HTML 模板或重定向到另一个页面(不是 SPA 领域)并在返回响应之前让它们在服务器上进行身份验证。

【讨论】:

感谢您的建议,但我正在寻找一种基于声明的解决方案,可以更紧密地集成角色和声明。 角色是声明。 mithun_daa 的意思是,您需要向客户提供声明(或者,在此示例中,这些声明为您提供的权限)以供客户采取行动 @Martin - 这是我正在寻找更多信息的客户提供这些声明背后的机制。最新版本的 Identity、WebAPI 和 Oauth2 发生了很多变化。我什至观看了 Thinktekture 架构师的视频,他提到他不能再推荐使用他自己的产品进行新开发,因为微软现在有更好的解决方案。似乎事情在不断变化,我找不到很好的例子来证明做到这一点的最佳方式。我希望有一个基于最新版本软件的好例子。谢谢【参考方案3】:

您的问题的简短答案是ApplicationOAuthProvider.CreateProperties 方法。它默认为您创建,位于 WebApi2/Provider/ApplicationOAuthProvider.cs 下,默认情况下它只发送 userName

//WepApi2/Providers/ApplicationOAuthProvider.cs
public static AuthenticationProperties CreateProperties(string userName)

    IDictionary<string, string> data = new Dictionary<string, string>
    
         "userName", userName 
    ;
    return new AuthenticationProperties(data);

我会进行以下更新(以防我稍后需要发送更多用户数据):

public static AuthenticationProperties CreateProperties(string userName, ClaimsIdentity oAuthIdentity)
 
  IDictionary<string, string> data = new Dictionary<string, string>
  
       "userName", userName,
       "roles",string.Join(",",oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray())

  ;
  return new AuthenticationProperties(data);

如果您没有对 WebApi 项目进行重大更改,ApplicationOAuthProvider.CreateProperties 仅在两个地方引用,只需更新调用代码以将oAuthIdentityuser.UserName 一起传递,您将获得用户角色与访问令牌响应一起发送:


 "access_token": "ZpxAZyYuvCaWgShUz0c_XDLFqpbC0-DIeXl_tuFbr11G-5hzBzSUxFNwNPahsasBD9t6mDDJGHcuEqdvtBT4kDNQXFcjWYvFP7U2Y0EvLS3yejdSvUrh2v1N7Ntz80WKe5G_wy2t11eT0l48dgdyak8lYcl3Nx8D0cgwlQm-pePIanYZatdPFP9q5jzhD-_k9SF-ARTHgf0ePnbvhLBi1MCYQjvfgPKlbBHt0M5qjwGAeFg1IhSVj0gb4g9QTXoiPhRmxGBmjOpGgzxXixavmrpM7cCBFLoR3DCGnIJo6pwT-6VArxlB8-ZyyOZqh_6gGtptd0lIu8iJRUIGwO9HFNkROdoE9T4buwLnhPpWpy9geBjPVwsB1K3xnbch26YbklhxIHVybBxeIVXd17QTw_LjlQ5TJdqpAYfiZ5B9Nx2AFYYYe3--aemh4y1XOIvN",
 "token_type": "bearer",
 "expires_in": 1209599,
 "userName": "MK",
 "roles": "Admin,Public",
 ".issued": "Fri, 23 May 2014 17:36:54 GMT",
 ".expires": "Fri, 06 Jun 2014 17:36:54 GMT"

现在您有了可用的角色,您可以使用 Angular 条件指令根据用户角色显示/隐藏操作。

如果您需要更多说明,请告诉我。

编辑:

使用Authorize 属性装饰您的控制器方法是有效的,因为HttpContext.Current.User.Identity 实际上是ClaimsIdentity。但为了不在应用程序内部硬编码安全逻辑,我更喜欢使用ClaimsAuthorizationManager

public ActionResult Secure()

  if(!ClaimsPrincipalPermission.CheckAccess("resource", "action"))
    return new HttpUnauthorizedResult();

  ViewBag.Message = "You are allowed to perform action on resource.";
  return View();

使用RoleManager 创建角色:

RoleManager roleManger = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>());
roleManager.Create(new IdentityRole()  Name = "Admin" );

角色分配使用UserManager

userManager.AddToRole(user.Id, "Admin");

【讨论】:

谢谢。我会检查你的答案并更新 cmets。 MK。 - 谢谢你的建议。我在上面检查了您的代码示例,效果很好。但是我有一个问题,尽管我的用户被分配了角色并且他们适合身份表,但这些角色并不作为声明出现。因此,当我检查分配了角色的用户时,我发现他们没有提出任何要求。在上述情况下,您的用户被分配了 Admin 和 Public 角色,您是如何分配角色 Admin 的?您是否还通过检查来装饰您的控制器方法以确保用户处于管理员角色? @SamanthaJ 角色存在于ClaimsIdentity 中,我使用的是修改后的UserManager,我更新了答案以使用默认的 asp.net 身份 2。您也可以使用 @ 查询角色987654342@直接。至于角色检查,我更喜欢使用ClaimsAuthorizationManagermsdn.microsoft.com/en-us/library/… 为了不将上次编辑与之前的更新混淆,更新涉及CreateProperties方法(发送ClaimsIdentity而不是IdentityUser)。 感谢您的更新。我试过了,效果很好。只需几行代码,它就可以满足我的需求。我会接受这个答案。再次感谢您的帮助。【参考方案4】:

我认为有两种方法可以解决您的问题。

    将“角色”信息通过哈希或简单字符串附加到令牌中,因为您是生成令牌的人,然后您可以在角度上对其进行解密。

    您似乎想使用 ASP.NET 身份系统并在那里存储和检索角色信息。如果是这种情况,您可以通过this post 注意“初始化数据库以创建管理员角色和管理员用户”部分。

IMO,#1 将使您在如何存储和使用用户数据方面更加灵活,因为 #2 您正在关注 Microsoft 的 IdentityUser ,尽管它有时看起来很神奇,而且它往往会发布限制,您需要花时间了解如何它在幕后工作并使其适用于您的项目。

要了解更多关于您在创建 WebAPI 项目期间选择的“个人用户帐户”,您可以前往http://www.asp.net/visual-studio/overview/2013/creating-web-projects-in-visual-studio#indauth

【讨论】:

以上是关于使用 AngularJS、WebAPI 2 和 Oauth 2 时,如何将授权信息发送回我的客户端应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

WebApi 上的 AngularJS 和 OWIN 身份验证

将多个值从 WebApi(post)返回到 AngularJs

AngularJS 2调用.net core WebAPI的几个坑

ASP.NET WebAPI2 和 AngularJS

移动浏览器上的 AngularJS 和 ASP.Net WebAPI 社交登录

AngularJS 使用 $resource 将数据发布到 WebAPI