ASP.NET MVC - ตั้งค่า IIdentity ที่กำหนดเองหรือ IPrincipal


650

ฉันต้องทำสิ่งที่ค่อนข้างง่าย: ในแอปพลิเคชัน ASP.NET MVC ของฉันฉันต้องการตั้งค่า IIdentity / IPrincipal แบบกำหนดเอง แล้วแต่จำนวนใดจะง่ายกว่า / เหมาะสมกว่า ฉันต้องการที่จะขยายการเริ่มต้นเพื่อที่ฉันสามารถเรียกสิ่งที่ต้องการและUser.Identity.Id User.Identity.Roleไม่มีอะไรแฟนซีเพียงคุณสมบัติพิเศษบางอย่าง

ฉันได้อ่านบทความและคำถามมากมาย แต่ฉันรู้สึกว่าฉันทำให้มันหนักกว่าที่เป็นจริง ฉันคิดว่ามันจะง่าย หากผู้ใช้เข้าสู่ระบบฉันต้องการตั้งค่า IIdentity ที่กำหนดเอง ดังนั้นฉันคิดว่าฉันจะใช้Application_PostAuthenticateRequestใน global.asax ของฉัน อย่างไรก็ตามที่เรียกว่าในทุกคำขอและฉันไม่ต้องการโทรไปยังฐานข้อมูลในทุกคำขอที่จะขอข้อมูลทั้งหมดจากฐานข้อมูลและวางในวัตถุ IPrincipal ที่กำหนดเอง ที่ดูเหมือนว่าไม่จำเป็นมากช้าและผิดที่ (ทำสายฐานข้อมูลมี) แต่ฉันอาจผิด หรือข้อมูลนั้นมาจากไหน?

ดังนั้นฉันจึงคิดว่าเมื่อใดก็ตามที่ผู้ใช้ลงชื่อเข้าใช้ฉันสามารถเพิ่มตัวแปรที่จำเป็นบางอย่างในเซสชันของฉันซึ่งฉันเพิ่มลงใน IIdentity ที่กำหนดเองในApplication_PostAuthenticateRequestตัวจัดการเหตุการณ์ อย่างไรก็ตามฉันContext.Sessionอยู่ที่nullนั่นเพื่อที่จะไม่ไป

ฉันทำงานนี้มาหนึ่งวันแล้วและฉันรู้สึกว่าฉันขาดอะไรไป ไม่ควรทำยากเกินไปใช่มั้ย ฉันยังสับสนกับทุกสิ่งที่เกี่ยวข้อง (กึ่ง) ที่มาพร้อมกับสิ่งนี้ MembershipProvider, MembershipUser, RoleProvider, ProfileProvider, IPrincipal, IIdentity, FormsAuthentication.... ฉันเพียงคนเดียวที่ทุกคนพบอย่างนี้ทำให้เกิดความสับสน?

หากใครบางคนบอกฉันได้ว่าวิธีแก้ปัญหาที่ง่ายสง่างามและมีประสิทธิภาพในการจัดเก็บข้อมูลพิเศษบางอย่างในความเป็น II โดยไม่ต้องมีฟัซซี่พิเศษทั้งหมด ฉันรู้ว่ามีคำถามที่คล้ายกันใน SO แต่ถ้าคำตอบที่ฉันต้องการอยู่ในนั้นฉันต้องมองข้าม


1
สวัสดี Domi มันเป็นการรวมกันของการจัดเก็บข้อมูลเท่านั้นที่ไม่เคยเปลี่ยนแปลง (เช่น ID ผู้ใช้) หรืออัปเดตคุกกี้โดยตรงหลังจากที่ผู้ใช้เปลี่ยนแปลงข้อมูลที่จะต้องสะท้อนให้เห็นในคุกกี้ทันที หากผู้ใช้ทำเช่นนั้นฉันเพียงอัปเดตคุกกี้ด้วยข้อมูลใหม่ แต่ฉันพยายามไม่เก็บข้อมูลที่เปลี่ยนแปลงบ่อย
Razzie

26
คำถามนี้มีการดู 36k และมีการโหวตขึ้นหลายครั้ง สิ่งนี้เป็นข้อกำหนดทั่วไปหรือไม่และถ้าเป็นเช่นนั้นจะไม่มีวิธีที่ดีกว่า 'สิ่งที่กำหนดเอง' ทั้งหมดหรือไม่
Simon_Weaver

2
@Simon_Weaver มี ASP.NET Identity ทราบซึ่งสนับสนุนข้อมูลที่กำหนดเองเพิ่มเติมในคุกกี้ที่เข้ารหัสได้ง่ายขึ้น
จอห์น

1
MemberShip...ผมเห็นด้วยกับคุณมีข้อมูลมากเช่นคุณโพสต์: Principal, Identity, ASP.NET ควรทำให้ง่ายขึ้นง่ายขึ้นและมากที่สุดสองวิธีในการจัดการกับการพิสูจน์ตัวตน
บรอดแบนด์

1
@Simon_Weaver สิ่งนี้แสดงให้เห็นอย่างชัดเจนว่ามีความต้องการระบบ IMHO ที่ยืดหยุ่นได้ง่ายขึ้นง่ายขึ้น
niico

คำตอบ:


838

นี่คือวิธีที่ฉันทำ

ฉันตัดสินใจใช้ IPrincipal แทน IIdentity เพราะนั่นหมายความว่าฉันไม่ต้องติดตั้งทั้ง 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 - สำหรับซีเรียลไลซ์ข้อมูลที่กำหนดเองในฟิลด์ userdata ในวัตถุ 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. การเข้าถึงในมุมมองมีดโกน

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

และในรหัส:

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

ฉันคิดว่ารหัสนี้อธิบายตนเอง หากไม่เป็นเช่นนั้นแจ้งให้เราทราบ

นอกจากนี้เพื่อให้เข้าถึงได้ง่ายยิ่งขึ้นคุณสามารถสร้างตัวควบคุมฐานและแทนที่วัตถุผู้ใช้ที่ส่งคืน (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

9
การใช้งานที่ดี; ระวังสำหรับ RoleManagerModule แทนที่ผู้กำหนดเองของคุณด้วย RolePrincipal นั่นทำให้ฉันเจ็บปวดมาก - stackoverflow.com/questions/10742259//
David Keaveny

9
ตกลงฉันพบวิธีแก้ปัญหาแล้วเพียงเพิ่มสวิตช์อื่นที่ผ่าน "" (สตริงว่าง) เนื่องจากอีเมลและข้อมูลประจำตัวจะไม่ระบุตัวตน
Pierre-Alain Vigeant

3
DateTime.Now.AddMinutes (N) ... วิธีการทำเช่นนี้เพื่อไม่ให้ผู้ใช้ออกจากระบบหลังจาก N นาทีผู้ใช้ที่เข้าสู่ระบบจะสามารถยืนยันได้ (เมื่อผู้ใช้ตรวจสอบตัวอย่างเช่น 'จดจำฉัน')?
1110

4
หากคุณกำลังใช้ WebApiController คุณจะต้องตั้งค่าThread.CurrentPrincipalที่Application_PostAuthenticateRequestให้มันทำงานที่มันไม่ได้พึ่งพาHttpContext.Current.User
โจนาธาน Levison

3
@AbhinavGujjar FormsAuthentication.SignOut();ทำงานได้ดีสำหรับฉัน
LukeP

109

ฉันไม่สามารถพูดได้โดยตรงสำหรับ 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


มาจาก PHP ฉันมักจะใส่ข้อมูลเช่น UserID และส่วนอื่น ๆ ที่จำเป็นในการให้สิทธิ์การเข้าถึงที่ จำกัด ในเซสชัน การจัดเก็บด้านไคลเอนต์ทำให้ฉันกังวลคุณสามารถแสดงความคิดเห็นเกี่ยวกับสาเหตุที่จะไม่มีปัญหาหรือไม่
John Zumbrum

@JohnZ - ตั๋วถูกเข้ารหัสบนเซิร์ฟเวอร์ก่อนที่จะส่งผ่านสายดังนั้นจึงไม่เหมือนกับที่ลูกค้าจะสามารถเข้าถึงข้อมูลที่เก็บไว้ในตั๋วได้ โปรดทราบว่ารหัสเซสชันจะถูกเก็บไว้ในคุกกี้ด้วยเช่นกันดังนั้นจึงไม่แตกต่างกันอย่างสิ้นเชิง
John Rasch

3
หากคุณอยู่ที่นี่คุณควรดูวิธีแก้ปัญหาของ
LukeP

2
ฉันกังวลเกี่ยวกับความเป็นไปได้ที่จะมีขนาดเกินขนาดคุกกี้สูงสุด ( stackoverflow.com/questions/8706924/… ) ด้วยวิธีการนี้ ฉันมักจะใช้Cacheเป็นSessionทดแทนเพื่อให้ข้อมูลบนเซิร์ฟเวอร์ ใครสามารถบอกฉันได้ว่านี่เป็นวิธีการที่มีข้อบกพร่องหรือไม่?
Red Taz

2
วิธีการที่ดี ปัญหาที่อาจเกิดขึ้นกับสิ่งนี้คือถ้าวัตถุผู้ใช้ของคุณมีมากกว่าสองสามคุณสมบัติ (และโดยเฉพาะอย่างยิ่งถ้าวัตถุที่ซ้อนกัน) การสร้างคุกกี้จะล้มเหลวอย่างเงียบ ๆ เมื่อค่าที่เข้ารหัสเกิน 4KB (ตีได้ง่ายกว่าคุณอาจคิดว่า) ถ้าคุณเพียงเก็บข้อมูลสำคัญมันก็ดี แต่คุณจะต้องกดฐานข้อมูลที่เหลือ ข้อควรพิจารณาอีกประการหนึ่งคือ "อัปเกรด" ข้อมูลคุกกี้เมื่อวัตถุผู้ใช้มีการเปลี่ยนแปลงลายเซ็นหรือลอจิก
Geoffrey Hudik

63

นี่คือตัวอย่างเพื่อให้งานสำเร็จลุล่วง bool isValid ถูกตั้งค่าโดยดูที่แหล่งข้อมูลบางแห่ง (สมมติว่าฐานข้อมูลผู้ใช้ของคุณ) ID ผู้ใช้เป็นเพียง 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

สิ่งนี้จะช่วยให้คุณเข้าถึงข้อมูลผู้ใช้ที่กำหนดเอง


2
FYI - มันเป็นคำขอ. คุกกี้ [] (พหูพจน์)
Dan Esparza

10
อย่าลืมตั้งค่า Thread.CurrentPrincipal รวมถึง Context.User เป็น CustomPrincipal
Russ Cam

6
GetUserIdentity () มาจากไหน
Ryan

ตามที่ฉันได้กล่าวถึงในความคิดเห็นมันทำให้การใช้งานของ System.Web.Security.IIdentity Google เกี่ยวกับอินเทอร์เฟซนั้น
Sriwantha Attanayake

16

MVC มอบวิธีการ OnAuthorize ให้คุณซึ่งแฮงค์จากคลาสตัวควบคุมของคุณ หรือคุณสามารถใช้ตัวกรองการกระทำที่กำหนดเองเพื่อดำเนินการอนุญาต MVC ทำให้มันง่ายที่จะทำ ฉันโพสต์บล็อกโพสต์เกี่ยวกับที่นี่ http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0


แต่เซสชันอาจสูญหายและผู้ใช้ยังคงตรวจสอบสิทธิ์ได้ ไม่นะ
Dragouf

@brady gaster ฉันอ่านโพสต์บล็อกของคุณ (ขอบคุณ!) ทำไมมีคนใช้การแทนที่ "OnAuthorize ()" ตามที่กล่าวไว้ในโพสต์ของคุณในรายการ global.asax "... AuthenticateRequest (.. )" ที่กล่าวถึงโดยคนอื่น คำตอบ? เป็นที่ต้องการมากกว่าหนึ่งในการตั้งค่าผู้ใช้หลักการ?
RayLoveless

10

นี่เป็นวิธีแก้ปัญหาหากคุณต้องการเชื่อมต่อกับ @User เพื่อใช้ในมุมมองของคุณ ไม่มีวิธีแก้ปัญหาสำหรับการปรับแต่งการเป็นสมาชิกที่จริงจัง แต่ถ้าต้องการคำถามดั้งเดิมสำหรับมุมมองเพียงอย่างเดียวนี่อาจจะเพียงพอ ด้านล่างนี้ใช้สำหรับตรวจสอบตัวแปรที่ส่งคืนจาก authorizefilter ใช้เพื่อตรวจสอบว่ามีลิงค์ใดที่นำเสนอหรือไม่ (ไม่ใช่สำหรับตรรกะการอนุญาตหรือการอนุญาตการเข้าถึงชนิดใด ๆ )

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()

1
ในโซลูชันของคุณเราต้องทำการเรียกฐานข้อมูลทุกครั้ง เนื่องจากวัตถุผู้ใช้ไม่มีคุณสมบัติที่กำหนดเอง มีเพียงชื่อและ IsAuthanticated
oneNiceFriend

ขึ้นอยู่กับการนำไปใช้และพฤติกรรมที่ต้องการทั้งหมด ตัวอย่างของฉันมีฐานข้อมูล 0 บรรทัดหรือบทบาทตรรกะ หากใครใช้ IsInRole ก็จะสามารถแคชในคุกกี้ฉันเชื่อว่า หรือคุณใช้ตรรกะแคชของคุณเอง
Base

3

ขึ้นอยู่กับคำตอบของ LukePและเพิ่มวิธีการบางอย่างที่จะติดตั้งtimeoutและให้ความร่วมมือกับrequireSSLWeb.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;

3

เอาล่ะฉันเป็น cryptkeeper ที่จริงจังโดยการลากคำถามเก่า ๆ นี้ขึ้นมา แต่มีวิธีที่ง่ายกว่ามากในการทำเช่นนี้ซึ่งได้รับการติดต่อจาก @Baserz ด้านบน และนั่นคือการใช้การรวมกันของวิธีการขยาย C # และแคช (อย่าใช้เซสชั่น)

ในความเป็นจริง Microsoft ได้ให้หมายเลขส่วนขยายดังกล่าวไว้ใน Microsoft.AspNet.Identity.IdentityExtensionsเนมสเปซแล้ว ตัวอย่างเช่นGetUserId()เป็นวิธีส่วนขยายที่ส่งคืนรหัสผู้ใช้ นอกจากนี้ยังมีGetUserName()และFindFirstValue()ซึ่งส่งกลับการเรียกร้องตาม IPrincipal

ดังนั้นคุณต้องมีเฉพาะ namespace แล้วโทรUser.Identity.GetUserName()เพื่อรับชื่อผู้ใช้ตามที่กำหนดโดย ASP.NET Identity

ฉันไม่แน่ใจว่าสิ่งนี้ถูกแคชไว้หรือไม่เนื่องจาก ASP.NET Identity ที่เก่ากว่าไม่ได้เปิดมาและฉันไม่ได้ใส่ใจที่จะทำวิศวกรรมย้อนกลับ อย่างไรก็ตามหากไม่เป็นเช่นนั้นคุณสามารถเขียนวิธีการขยายของคุณเองซึ่งจะทำการแคชผลลัพธ์นี้ตามระยะเวลา


ทำไม "ไม่ใช้เซสชัน"
Alex

@jitbit - เนื่องจากเซสชันไม่น่าเชื่อถือและไม่ปลอดภัย ด้วยเหตุผลเดียวกันคุณไม่ควรใช้เซสชันเพื่อความปลอดภัย
Erik Funkenbusch

"ไม่น่าเชื่อถือ" สามารถแก้ไขได้ด้วยการทำซ้ำเซสชัน (ถ้าว่าง) "ไม่ปลอดภัย" - มีหลายวิธีในการปกป้องจากการไฮแจ็กเซสชัน (โดยใช้ HTTPS เฉพาะวิธีอื่น ๆ ) แต่ฉันเห็นด้วยกับคุณ แล้วคุณจะแคชที่ไหน ข้อมูลที่ชอบIsUserAdministratorหรือUserEmailอื่น ๆ ? คุณกำลังคิดHttpRuntime.Cacheอะไรอยู่
Alex

@jitbit - นั่นเป็นตัวเลือกหนึ่งหรือโซลูชันการแคชอื่นถ้าคุณมี ตรวจสอบให้แน่ใจว่าหมดอายุรายการแคชหลังจากช่วงระยะเวลาหนึ่ง การรักษาความปลอดภัยยังใช้กับระบบภายในเนื่องจากคุณสามารถแก้ไขคุกกี้และเดา ID ของเซสชันด้วยตนเอง คนที่อยู่ตรงกลางไม่ได้เป็นเพียงความกังวลเท่านั้น
Erik Funkenbusch

2

นอกเหนือจากรหัส LukeP สำหรับผู้ใช้เว็บฟอร์ม (ไม่ใช่ MVC) หากคุณต้องการทำให้การเข้าถึงในโค้ดด้านหลังของหน้าเว็บง่ายขึ้นเพียงเพิ่มรหัสด้านล่างลงในหน้าฐานและรับหน้าฐานในทุกหน้าของคุณ:

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

ดังนั้นในรหัสของคุณด้านหลังคุณสามารถเข้าถึง:

User.FirstName or User.LastName

สิ่งที่ฉันหายไปในสถานการณ์เว็บฟอร์มคือวิธีการรับพฤติกรรมเดียวกันในรหัสที่ไม่ผูกกับหน้าตัวอย่างเช่นในhttpmodulesฉันควรเพิ่มการส่งในแต่ละคลาสหรือมีวิธีที่ชาญฉลาดกว่าในการรับสิ่งนี้หรือไม่

ขอบคุณสำหรับคำตอบของคุณและขอบคุณ LukeP ตั้งแต่ผมใช้ตัวอย่างของคุณเป็นฐานสำหรับผู้ใช้ของฉันเอง (ซึ่งตอนนี้มีUser.Roles, User.Tasks, User.HasPath(int), User.Settings.Timeoutและอีกหลายสิ่งที่ดีอื่น ๆ )


0

ฉันลองวิธีการแก้ปัญหาที่แนะนำโดย LukeP และพบว่ามันไม่สนับสนุนแอตทริบิวต์อนุญาต ดังนั้นฉันจึงแก้ไขมันเล็กน้อย

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

และในที่สุดใน 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;
        }
    }

ตอนนี้ฉันสามารถเข้าถึงข้อมูลในมุมมองและตัวควบคุมได้ง่ายๆโดยการโทร

User.ExInfo()

ในการออกจากระบบฉันเพียงแค่โทร

AuthManager.SignOut();

ตำแหน่งที่ AuthManager อยู่

HttpContext.GetOwinContext().Authentication
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.