code

ASP.NET MVC-사용자 지정 IIdentity 또는 IPrincipal 설정

codestyles 2020. 10. 2. 22:20
반응형

ASP.NET MVC-사용자 지정 IIdentity 또는 IPrincipal 설정


매우 간단한 작업이 필요합니다. ASP.NET MVC 응용 프로그램에서 사용자 지정 IIdentity / IPrincipal을 설정하고 싶습니다. 어느 것이 더 쉽고 더 적합합니다. 나는 같은 것을 호출 할 수 있도록 기본을 확장 할 User.Identity.Id과를 User.Identity.Role. 멋진 것은 없으며 추가 속성 만 있습니다.

많은 기사와 질문을 읽었지만 실제보다 어렵게 만들고있는 것 같습니다. 쉬울 거라고 생각 했어요. 사용자가 로그온하면 사용자 지정 IIdentity를 설정하고 싶습니다. 그래서 나는 Application_PostAuthenticateRequestglobal.asax에서 구현할 것이라고 생각했습니다 . 그러나 모든 요청에 ​​대해 호출되며 데이터베이스의 모든 데이터를 요청하고 사용자 지정 IPrincipal 개체에 넣는 모든 요청에 ​​대해 데이터베이스에 대한 호출을 수행하고 싶지 않습니다. 그것은 또한 매우 불필요하고 느리고 잘못된 장소에서 (데이터베이스 호출을 수행하는) 것처럼 보이지만 틀릴 수 있습니다. 아니면 그 데이터의 출처는 어디입니까?

그래서 사용자가 로그인 할 때마다 세션에 필요한 변수를 추가 할 수 있다고 생각했습니다.이 변수를 Application_PostAuthenticateRequest이벤트 처리기 의 사용자 지정 IIdentity에 추가했습니다 . 그러나 나의 Context.Session것이 null거기에 있으므로 그것은 또한 갈 길이 아닙니다.

나는이 일을 지금 하루 동안 해왔고 뭔가 빠졌다고 느낀다. 이건 너무 어렵지 않겠죠? 나는 또한 이것과 함께 제공되는 모든 (반) 관련 항목에 약간 혼란스러워합니다. MembershipProvider, MembershipUser, RoleProvider, ProfileProvider, IPrincipal, IIdentity, FormsAuthentication... 암 i를 단 하나의 사람은 모두이 매우 혼란 발견?

누군가 나에게 모든 추가 퍼즈없이 IIdentity에 추가 데이터를 저장할 수있는 간단하고 우아하며 효율적인 솔루션을 말해 줄 수 있다면 좋을 것입니다. 나는 비슷한 질문이 있다는 것을 알고 있지만 내가 필요한 대답이 거기에 있다면 나는 간과했을 것입니다.


방법은 다음과 같습니다.

IIdentity와 IPrincipal을 모두 구현할 필요가 없기 때문에 IIdentity 대신 IPrincipal을 사용하기로 결정했습니다.

  1. 인터페이스 만들기

    interface ICustomPrincipal : IPrincipal
    {
        int Id { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
    }
    
  2. CustomPrincipal

    public class CustomPrincipal : ICustomPrincipal
    {
        public IIdentity Identity { get; private set; }
        public bool IsInRole(string role) { return false; }
    
        public CustomPrincipal(string email)
        {
            this.Identity = new GenericIdentity(email);
        }
    
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
  3. CustomPrincipalSerializeModel-사용자 지정 정보를 FormsAuthenticationTicket 개체의 사용자 데이터 필드에 직렬화합니다.

    public class CustomPrincipalSerializeModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
  4. 로그인 방법-사용자 지정 정보로 쿠키 설정

    if (Membership.ValidateUser(viewModel.Email, viewModel.Password))
    {
        var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
    
        CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
        serializeModel.Id = user.Id;
        serializeModel.FirstName = user.FirstName;
        serializeModel.LastName = user.LastName;
    
        JavaScriptSerializer serializer = new JavaScriptSerializer();
    
        string userData = serializer.Serialize(serializeModel);
    
        FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                 1,
                 viewModel.Email,
                 DateTime.Now,
                 DateTime.Now.AddMinutes(15),
                 false,
                 userData);
    
        string encTicket = FormsAuthentication.Encrypt(authTicket);
        HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
        Response.Cookies.Add(faCookie);
    
        return RedirectToAction("Index", "Home");
    }
    
  5. Global.asax.cs-쿠키를 읽고 HttpContext.User 개체를 대체합니다.이 작업은 PostAuthenticateRequest를 재정 의하여 수행됩니다.

    protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    
        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    
            JavaScriptSerializer serializer = new JavaScriptSerializer();
    
            CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
    
            CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
            newUser.Id = serializeModel.Id;
            newUser.FirstName = serializeModel.FirstName;
            newUser.LastName = serializeModel.LastName;
    
            HttpContext.Current.User = newUser;
        }
    }
    
  6. Razor보기에서 액세스

    @((User as CustomPrincipal).Id)
    @((User as CustomPrincipal).FirstName)
    @((User as CustomPrincipal).LastName)
    

그리고 코드에서 :

    (User as CustomPrincipal).Id
    (User as CustomPrincipal).FirstName
    (User as CustomPrincipal).LastName

코드가 자명하다고 생각합니다. 그렇지 않은 경우 알려주십시오.

또한 액세스를 더 쉽게하기 위해 기본 컨트롤러를 만들고 반환 된 User 개체 (HttpContext.User)를 재정의 할 수 있습니다.

public class BaseController : Controller
{
    protected virtual new CustomPrincipal User
    {
        get { return HttpContext.User as CustomPrincipal; }
    }
}

그런 다음 각 컨트롤러에 대해 :

public class AccountController : BaseController
{
    // ...
}

다음과 같은 코드에서 사용자 정의 필드에 액세스 할 수 있습니다.

User.Id
User.FirstName
User.LastName

그러나 이것은 뷰 내부에서 작동하지 않습니다. 이를 위해 사용자 정의 WebViewPage 구현을 만들어야합니다.

public abstract class BaseViewPage : WebViewPage
{
    public virtual new CustomPrincipal User
    {
        get { return base.User as CustomPrincipal; }
    }
}

public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
    public virtual new CustomPrincipal User
    {
        get { return base.User as CustomPrincipal; }
    }
}

Views / web.config에서 기본 페이지 유형으로 만듭니다.

<pages pageBaseType="Your.Namespace.BaseViewPage">
  <namespaces>
    <add namespace="System.Web.Mvc" />
    <add namespace="System.Web.Mvc.Ajax" />
    <add namespace="System.Web.Mvc.Html" />
    <add namespace="System.Web.Routing" />
  </namespaces>
</pages>

보기에서 다음과 같이 액세스 할 수 있습니다.

@User.FirstName
@User.LastName

ASP.NET MVC에 대해 직접 말할 수는 없지만 ASP.NET Web Forms의 경우 트릭은 FormsAuthenticationTicket사용자가 인증되면 쿠키를 만들어 쿠키로 암호화하는 것입니다. 이렇게하면 데이터베이스를 한 번만 호출하면 (또는 AD 또는 인증을 수행하는 데 사용하는 모든 항목) 이후의 각 요청은 쿠키에 저장된 티켓을 기반으로 인증됩니다.

이에 대한 좋은 기사 : http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (깨진 링크)

편집하다:

위의 링크가 끊어 졌기 때문에 위의 답변에서 LukeP의 솔루션을 권장합니다. https://stackoverflow.com/a/10524305- 또한 수락 된 답변을 그 답변으로 변경하는 것이 좋습니다.

편집 2 : 끊어진 링크의 대안 : https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html


다음은 작업을 완료하는 예입니다. bool isValid는 일부 데이터 저장소 (사용자 데이터베이스라고 말할 수 있음)를 살펴봄으로써 설정됩니다. UserID는 내가 유지하는 ID입니다. 사용자 데이터에 이메일 주소와 같은 추가 정보를 추가 할 수 있습니다.

protected void btnLogin_Click(object sender, EventArgs e)
{         
    //Hard Coded for the moment
    bool isValid=true;
    if (isValid) 
    {
         string userData = String.Empty;
         userData = userData + "UserID=" + userID;
         FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
         string encTicket = FormsAuthentication.Encrypt(ticket);
         HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
         Response.Cookies.Add(faCookie);
         //And send the user where they were heading
         string redirectUrl = FormsAuthentication.GetRedirectUrl(username, false);
         Response.Redirect(redirectUrl);
     }
}

golbal asax에서 다음 코드를 추가하여 정보를 검색하십시오.

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookies[
             FormsAuthentication.FormsCookieName];
    if(authCookie != null)
    {
        //Extract the forms authentication cookie
        FormsAuthenticationTicket authTicket = 
               FormsAuthentication.Decrypt(authCookie.Value);
        // Create an Identity object
        //CustomIdentity implements System.Web.Security.IIdentity
        CustomIdentity id = GetUserIdentity(authTicket.Name);
        //CustomPrincipal implements System.Web.Security.IPrincipal
        CustomPrincipal newUser = new CustomPrincipal();
        Context.User = newUser;
    }
}

나중에 정보를 사용할 경우 다음과 같이 사용자 지정 주체에 액세스 할 수 있습니다.

(CustomPrincipal)this.User
or 
(CustomPrincipal)this.Context.User

이렇게하면 사용자 지정 사용자 정보에 액세스 할 수 있습니다.


MVC는 컨트롤러 클래스에서 중단되는 OnAuthorize 메서드를 제공합니다. 또는 사용자 지정 작업 필터를 사용하여 권한 부여를 수행 할 수 있습니다. MVC를 사용하면 매우 쉽게 할 수 있습니다. 여기에 블로그 게시물을 올렸습니다. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0


뷰에서 사용하기 위해 일부 메서드를 @User에 연결해야하는 경우 솔루션이 있습니다. 심각한 멤버십 사용자 지정에 대한 해결책은 없지만보기에만 원래 질문이 필요했다면 이것으로 충분할 것입니다. 아래는 권한 부여 필터에서 반환 된 변수를 확인하는 데 사용되었으며 일부 링크가 표시되는지 여부를 확인하는 데 사용되었습니다 (모든 종류의 권한 부여 논리 또는 액세스 권한 부여가 아님).

using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Security.Principal;

    namespace SomeSite.Web.Helpers
    {
        public static class UserHelpers
        {
            public static bool IsEditor(this IPrincipal user)
            {
                return null; //Do some stuff
            }
        }
    }

그런 다음 web.config 영역에 참조를 추가하고보기에서 아래와 같이 호출합니다.

@User.IsEditor()

를 기반으로 LukeP의 대답 , 설정에 대한 몇 가지 방법을 추가 timeout하고 requireSSL협조 Web.config.

참조 링크

LukeP의 수정 된 코드

1, 세트 timeout에 기반 Web.Config. FormsAuthentication.Timeout은 Web.config의에 정의 된 시간 초과 값을 얻을 것이다. 나는 다음을 ticket다시 반환하는 함수로 래핑했습니다 .

int version = 1;
DateTime now = DateTime.Now;

// respect to the `timeout` in Web.config.
TimeSpan timeout = FormsAuthentication.Timeout;
DateTime expire = now.Add(timeout);
bool isPersist = false;

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
     version,          
     name,
     now,
     expire,
     isPersist,
     userData);

2, 구성에 따라 쿠키의 보안 여부를 RequireSSL구성하십시오.

HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;

좋아, 그래서 나는이 아주 오래된 질문을 끌어 올림으로써 여기에 진지한 암호 키퍼이다. 그러나 이것에 대한 훨씬 더 간단한 접근법이있다. 위의 @Baserz에 의해 다뤄졌다. 그리고 그것은 C # Extension 메서드와 캐싱의 조합을 사용하는 것입니다 (세션을 사용하지 마십시오).

실제로 Microsoft는 이미 Microsoft.AspNet.Identity.IdentityExtensions네임 스페이스 에 이러한 확장을 많이 제공했습니다 . 예를 들어 GetUserId()는 사용자 ID를 반환하는 확장 메서드입니다. IPrincipal을 기반으로 클레임을 반환하는 GetUserName()도 있습니다 FindFirstValue().

따라서 네임 스페이스를 포함시킨 다음 호출 User.Identity.GetUserName()하여 ASP.NET Identity에 구성된 사용자 이름을 가져 오면 됩니다.

이전 ASP.NET ID가 오픈 소스가 아니고 리버스 엔지니어링을하지 않았기 때문에 이것이 캐시되었는지 확실하지 않습니다. 그러나 그렇지 않은 경우 고유 한 확장 메서드를 작성하면 특정 시간 동안이 결과를 캐시 할 수 있습니다.


As an addition to LukeP code for Web Forms users (not MVC) if you want to simplify the access in the code behind of your pages, just add the code below to a base page and derive the base page in all your pages:

Public Overridable Shadows ReadOnly Property User() As CustomPrincipal
    Get
        Return DirectCast(MyBase.User, CustomPrincipal)
    End Get
End Property

So in your code behind you can simply access:

User.FirstName or User.LastName

What I'm missing in a Web Form scenario, is how to obtain the same behaviour in code not tied to the page, for example in httpmodules should I always add a cast in each class or is there a smarter way to obtain this?

Thanks for your answers and thank to LukeP since I used your examples as a base for my custom user (which now has User.Roles, User.Tasks, User.HasPath(int) , User.Settings.Timeout and many other nice things)


I tried the solution suggested by LukeP and found that it doesn't support the Authorize attribute. So, I modified it a bit.

public class UserExBusinessInfo
{
    public int BusinessID { get; set; }
    public string Name { get; set; }
}

public class UserExInfo
{
    public IEnumerable<UserExBusinessInfo> BusinessInfo { get; set; }
    public int? CurrentBusinessID { get; set; }
}

public class PrincipalEx : ClaimsPrincipal
{
    private readonly UserExInfo userExInfo;
    public UserExInfo UserExInfo => userExInfo;

    public PrincipalEx(IPrincipal baseModel, UserExInfo userExInfo)
        : base(baseModel)
    {
        this.userExInfo = userExInfo;
    }
}

public class PrincipalExSerializeModel
{
    public UserExInfo UserExInfo { get; set; }
}

public static class IPrincipalHelpers
{
    public static UserExInfo ExInfo(this IPrincipal @this) => (@this as PrincipalEx)?.UserExInfo;
}


    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginModel details, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            AppUser user = await UserManager.FindAsync(details.Name, details.Password);

            if (user == null)
            {
                ModelState.AddModelError("", "Invalid name or password.");
            }
            else
            {
                ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
                AuthManager.SignOut();
                AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);

                user.LastLoginDate = DateTime.UtcNow;
                await UserManager.UpdateAsync(user);

                PrincipalExSerializeModel serializeModel = new PrincipalExSerializeModel();
                serializeModel.UserExInfo = new UserExInfo()
                {
                    BusinessInfo = await
                        db.Businesses
                        .Where(b => user.Id.Equals(b.AspNetUserID))
                        .Select(b => new UserExBusinessInfo { BusinessID = b.BusinessID, Name = b.Name })
                        .ToListAsync()
                };

                JavaScriptSerializer serializer = new JavaScriptSerializer();

                string userData = serializer.Serialize(serializeModel);

                FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                         1,
                         details.Name,
                         DateTime.Now,
                         DateTime.Now.AddMinutes(15),
                         false,
                         userData);

                string encTicket = FormsAuthentication.Encrypt(authTicket);
                HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
                Response.Cookies.Add(faCookie);

                return RedirectToLocal(returnUrl);
            }
        }
        return View(details);
    }

And finally in Global.asax.cs

    protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            PrincipalExSerializeModel serializeModel = serializer.Deserialize<PrincipalExSerializeModel>(authTicket.UserData);
            PrincipalEx newUser = new PrincipalEx(HttpContext.Current.User, serializeModel.UserExInfo);
            HttpContext.Current.User = newUser;
        }
    }

Now I can access the data in views and controllers simply by calling

User.ExInfo()

To log out I just call

AuthManager.SignOut();

where AuthManager is

HttpContext.GetOwinContext().Authentication

참고URL : https://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal

반응형