【问题标题】:How to store user information in one place after login and access in multiple WEB API controllers如何在多个WEB API控制器中登录和访问后将用户信息存储在一处
【发布时间】:2024-01-16 22:55:01
【问题描述】:

我正在使用 AngularJS 开发 Web API。几天前我已经实现了 Web API 令牌机制,并且能够使用访问令牌登录应用程序。我已经使用外部数据库表而不是 ASP.NET 身份表来授权用户。

我想将用户信息存储在类中,以便在用户登录后可以从不同的控制器轻松访问。目前我在控制器类中使用 ClaimsIdentity 来获取用户信息。

UserIdentityViewModel.cs

public class UserIdentityViewModel
    {
        public string UserName { get; set; }
        public Guid UserId { get; set; }
    }

Startup.cs

public class Startup
    {
        public void Configuration(IAppBuilder app)
        {                
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            var myProvider = new AuthorizationServerProvider();
            OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/Token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = myProvider
            };
            app.UseOAuthAuthorizationServer(options);

            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }
    }

AuthorizationServerProvider.cs

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated(); // 
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {           
            string userId = context.UserName;
            string password = context.Password;

            EmployeeAccessBLL chkEmpAccessBLL = new EmployeeAccessBLL();
            EmployeeAccessViewModel vmEmployeeAccess = chkEmpAccessBLL.CheckEmployeeAccess(Convert.ToInt32(userId), password);

            if(vmEmployeeAccess != null)
            {
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim("username", vmEmployeeAccess.EmpName));
                identity.AddClaim(new Claim("userid", Convert.ToString(vmEmployeeAccess.EmployeeId)));

                UserIdentityViewModel vmUser = new UserIdentityViewModel();
                vmUser.UserId = vmEmployeeAccess.EmployeeId;
                vmUser.UserName = vmEmployeeAccess.EmpName;

                context.Validated(identity);
            }
            else
            {
                context.SetError("invalid_grant", "Provided username and password is incorrect");
                return;
            }
        }       
    }

EventController.cs

 public class StreamEventController : ApiController
    {
        [Authorize]
        [Route("api/addevent")]
        [HttpPost]
        public List<string> AddEvent(StreamEventViewModel vmEvent)
        {
            //Able to get User Information from Identity.Claims
            var identity = (ClaimsIdentity)User.Identity;
            string userId = identity.Claims
                            .Where(c => c.Type == "userid")
                            .Select(c => c.Value).FirstOrDefault();

            //Not able to get User Information from following as new object instance gets created
            UserIdentityViewModel vmUser = new UserIdentityViewModel();

            vmEvent.CreatedBy = vmUser.UserId;
            vmEvent.ModifiedBy = vmUser.UserId;
       }
}

我不想在每个控制器的每个方法中编写“Identity.Claims”,而是使用简单的 get/set 方法或任何其他方法来获取 User Information 。我认为使用静态类也很糟糕,因为它会存储一个用户信息,而多个用户登录信息会丢失。

请帮助我并与我分享在其他 Web API 项目中用于登录的最佳方法。

【问题讨论】:

    标签: angularjs authentication asp.net-web-api2 access-token


    【解决方案1】:

    您可以添加一个私有变量,该变量将在控制器的构造函数中设置,如下所示:

    // Should only be used in protected methods.
    private ClaimsIdentity ThisUser = null;
    
    public MyController()
    {
        if (User.Identity.IsAuthenticated)
            ThisUser = (ClaimsIdentity)User.Identity;
    }
    
    [Authorize]
    [Route("api/addevent")]
    [HttpPost]
    public List<string> AddEvent(StreamEventViewModel vmEvent)
    {
        string userId = ThisUser.FindFirstValue("userid");
    
    }
    

    或者创建一个加载所有属性的用户类:

    private UserClass ThisUser = null;
    
    public MyController()
    {
        if (User.Identity.IsAuthenticated)
            ThisUser = new UserClass(User);
    }
    
    [Authorize]
    [Route("api/addevent")]
    [HttpPost]
    public List<string> AddEvent(StreamEventViewModel vmEvent)
    {
        string userId = ThisUser.UserId;
    
    }
    

    UserClass 类似于:

    public class UserClass
    {
        public string UserId { get; private set; }
    
        public UserClass(IPrincipal user)
        {
            UserId = user.FindFirstValue("userid");
        }
    }
    

    但这只是同一件事的开销。 您可以考虑将内容移至扩展程序。在这种情况下,你会得到类似的东西:

    public static class RequestExtensions
    {
        public static UserClass GetUser(this HttpRequestMessage request)
        {
            return new UserClass(request.GetOwinContext().Authentication.User);
        }
    
        public static ClaimsIdentiy GetUser2(this HttpRequestMessage request)
        {
            return new (ClaimsIdentity)request.GetOwinContext().Authentication.User;
        }
    }
    

    您可以致电:

    [Authorize]
    [Route("api/addevent")]
    [HttpPost]
    public List<string> AddEvent(StreamEventViewModel vmEvent)
    {
        string userId = Request.GetUser.UserId;
    
        string userId2 = Request.GetUser2.FindFirstValue("userid");
    
    }
    

    我想我会选择Request.GetUser2.FindFirstValue("userid");

    代码旨在为您提供一个想法。我没有测试代码,但我认为它应该可以工作。

    【讨论】:

    • 非常感谢 Ruard 给了我最好的选择。正如你提到的,我尝试使用扩展方法获得成功的结果。