관리 메뉴

웹개발자의 기지개

[ASP.Net Core] Identity Entity FrameworkCore - 회원가입/로그인/로그아웃 예제 본문

ASP.NET/ASP.NET Core

[ASP.Net Core] Identity Entity FrameworkCore - 회원가입/로그인/로그아웃 예제

http://portfolio.wonpaper.net 2023. 2. 28. 03:41

ASP.Net Core 상에서 회원가입/로그인/로그아웃 라이브러리를 EF Core 형태로 별도로 제공한다.

이러한 Identity 를 간단한 예제와 함께 확인해 보았다.

 

[전체 예제 소스]

https://github.com/wonpaper/IdentityMVC_Login1

 

GitHub - wonpaper/IdentityMVC_Login1

Contribute to wonpaper/IdentityMVC_Login1 development by creating an account on GitHub.

github.com

 

최초 메인화면
로그인한 메인화면

SecurityController.cs 와 Security  폴더내에 DbContext파일과 User, Role 관련 클래스 파일과 Views/Security 폴더네에 로그인/회원가입 파일(Register.cshtml, SignIn.cshtml)들로 구성되어 있다.

 

 

[ appsettings.json ] - DB 연결

 

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "AppDb": "Server=DESKTOP-ASIDMUS;Database=IdentityMVCDB;Trusted_Connection=True;MultipleActiveResultSets=true",
    "ApplicationDbContextConnection": "Server=(localdb)\\mssqllocaldb;Database=IdentityMVCDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
 }
cs

EF 관련 Nuget패키지 또한 적절히 설치하였다.

[ Security/AppIdentityDbContext.cs ]

1
2
3
4
5
6
7
8
9
10
11
12
13
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
 
namespace IdentityMVCExam.Security
{
    public class AppIdentityDbContext : IdentityDbContext<AppIdentityUser,AppIdentityRole,string>
    {
        public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options)
        {
 
        }
    }
}
cs

IdentityDbContext<TUser,TRole,TKey> 에 맞게 배치되도록 Identity 관련 IdentityDbContext 클래스를 상속받는다.

 

[ /Security/AppIdentityUser.cs ]  - Custom 할 IdentityUser 항목들 ( FullName, BirthDate )

1
2
3
4
5
6
7
8
9
10
using Microsoft.AspNetCore.Identity;
 
namespace IdentityMVCExam.Security
{
    public class AppIdentityUser : IdentityUser
    {
        public string FullName { get;set; }    = string.Empty;
        public DateTime BirthDate { get; set; }
    }
}
cs

 

[ /Security/AppIdentityRole.cs ] - Custom 할 IdentityRole  항목들 ( Description )

1
2
3
4
5
6
7
8
9
10
 
using Microsoft.AspNetCore.Identity;
 
namespace IdentityMVCExam.Security
{
    public class AppIdentityRole : IdentityRole
    {
        public string Description { get; set; } = string.Empty;
    }
}
cs

 

 

[ DB Migration ]

패키지관리자 콘솔을 열고, 

PM> Add-Migration IdentityMVC -Context AppIdentityDbContext
PM> Update-Database -Context AppIdentityDbContext

 

https://learn.microsoft.com/ko-kr/aspnet/core/security/authentication/identity-configuration?view=aspnetcore-6.0 

 

ASP.NET Core 구성 Identity

ASP.NET Core Identity 기본값을 이해하고 사용자 지정 값을 사용하도록 속성을 구성하는 Identity 방법을 알아봅니다.

learn.microsoft.com

MS 에서 Identity 세부 속성과 설정 부분 참고하면 된다.

비밀번호 규칙도 변경 가능하다.

 

[  Program.cs ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using IdentityMVCExam.Security;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
 
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("AppDb") ?? throw new InvalidOperationException("Connection string 'ApplicationDbContextConnection' not found.");
 
// DbContext 연결, Identity 등록, EF에 DbContext 등록
builder.Services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddIdentity<AppIdentityUser,AppIdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>();
builder.Services.ConfigureApplicationCookie(opt => {
    opt.LoginPath = "/Security/SignIn";                 // 로그인 처리페이지
    opt.AccessDeniedPath = "/Security/AccessDenied";    // 접근권한 없는 User가 접근할때 처리페이지
 
});
 
builder.Services.Configure<IdentityOptions>(options =>
{
    // 비밀번호 규칙
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = false;
    options.Password.RequiredLength = 8;
    options.Password.RequiredUniqueChars = 0;
});
 
 
 
// Add services to the container.
builder.Services.AddControllersWithViews();
 
#region [1] Session 개체 사용
//[0] 세션 개체 사용: Microsoft.AspNetCore.Session.dll NuGet 패키지 참조 
//services.AddSession(); 
// Session 개체 사용시 옵션 부여 
builder.Services.AddSession(options =>
{
    // 세션 유지 시간
    options.IdleTimeout = TimeSpan.FromMinutes(30);
});
#endregion
 
var app = builder.Build();
 
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
 
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
 
app.UseRouting();
 
app.UseAuthentication();
app.UseAuthorization();
 
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
 
cs

 

[ /Views/_ViewImports.cshtml ] - Identity 관련 항목들 using 시킨다.

1
2
3
4
5
6
@using IdentityMVCExam
@using IdentityMVCExam.Models
@using Microsoft.AspNetCore.Identity
@using Microsoft.AspNetCore.Authorization
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
 
cs

 

[ /Controllers/SecurityController.cs ]  - Controller 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using IdentityMVCExam.Models;
using IdentityMVCExam.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
 
namespace IdentityMVCExam.Controllers
{
    public class SecurityController : Controller
    {
        private readonly UserManager<AppIdentityUser> userManager;
        private readonly RoleManager<AppIdentityRole> roleManager;
        private readonly SignInManager<AppIdentityUser> signinManager;
 
        public SecurityController(UserManager<AppIdentityUser> userManager, RoleManager<AppIdentityRole> roleManager,
            SignInManager<AppIdentityUser> signinManager)
        {
            this.userManager = userManager;
            this.roleManager = roleManager;
            this.signinManager = signinManager;
        }
 
        // 회원가입
        public IActionResult Register()
        {
            return View();
        }
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Register(Register obj)
        {
            if (ModelState.IsValid)
            {
                // 최초 회원가입시 Manager Role이 없으면 Manager Role 값을 생성해준다.
                if (!roleManager.RoleExistsAsync("Manager").Result)
                {
                    AppIdentityRole role = new AppIdentityRole();
                    role.Name = "Manager";
                    role.Description = "Can perform CRUD operations.";
                    IdentityResult roleResult = roleManager.CreateAsync(role).Result;
                }
 
                AppIdentityUser user = new AppIdentityUser();
                user.UserName = obj.UserName;
                user.Email = obj.Email;
                user.FullName = obj.FullName;
                user.BirthDate = obj.BirthDate;
 
                // AppIdentityUser 생성한다.
                IdentityResult result = userManager.CreateAsync(user, obj.Password).Result;
                if (result.Succeeded)
                {
                    // User 에 Manager Role을 추가해준다. 이때 Wait() 는 비동기처리로 진행이 완료될때까지 잠시 기다려준다.
                    userManager.AddToRoleAsync(user, "Manager").Wait();
                    return RedirectToAction("SignIn""Security");
                }
                else
                {
                    ModelState.AddModelError("""회원가입 입력 항목을 모두 정확하게 입력해 주십시오.");
                }
            }
            return View(obj);
        }
 
 
        // 로그인
        public IActionResult SignIn()
        {
            return View();
        }
 
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult SignIn(SignIn obj)
        {
            if (ModelState.IsValid)
            {
                var result = signinManager.PasswordSignInAsync(obj.UserName, obj.Password,obj.RememberMe, false).Result;
                if (result.Succeeded)
                {
                    return RedirectToAction("Index""Home");
                }
                else
                {
                    ModelState.AddModelError("""로그인 사용자 정보가 맞지 않습니다.");
                }
            }
            return View(obj);
        }
 
        // 로그아웃
 
        // 로그아웃
        [HttpGet]
        public IActionResult SignOut()
        {
            signinManager.SignOutAsync().Wait();
            return RedirectToAction("Index""Home");
        }
 
        [HttpPost]
        [Authorize]
        [ValidateAntiForgeryToken]
        public IActionResult SignOutPost()
        {
            signinManager.SignOutAsync().Wait();
            return RedirectToAction("Index""Home");
        }
 
 
        // 접근허용금지 처리
 
        public IActionResult AccessDenied()
        {
            return View();
        }
    }
}
 
cs

 

[  /Models/Register.cs ]   - Model 관련 회원가입시 폼틀이 되는 클래스 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
 
namespace IdentityMVCExam.Models
{
    public class Register
    {
        [Required(ErrorMessage = "{0}을 입력해 주십시오.")]
        [Display(Name = "사용자이름")]
        public string UserName { get; set; }
 
        [Required(ErrorMessage = "{0}를 입력해 주십시오.")]
        [Display(Name = "비밀번호")]
        public string Password { get; set; }
 
        [Required(ErrorMessage = "{0}란을 입력해 주십시오.")]
        [Display(Name = "비밀번호 확인")]
        [Compare("Password", ErrorMessage = "입력하신 비밀번호와 일치하지 않습니다.")]
        public string ConfirmPassword { get; set; }
 
        [Required(ErrorMessage = "{0}을 입력해 주십시오.")]
        [Display(Name = "이메일")]
        [EmailAddress]
        public string Email { get; set; }
 
        [Required(ErrorMessage = "{0}을 입력해 주십시오.")]
        [Display(Name = "사용자이름(전체)")]
        public string FullName { get; set; }
 
        [Required(ErrorMessage = "{0}을 제대로 선택해 주십시오.")]
        [Display(Name = "생년월일")]
        public DateTime BirthDate { get; set; }
    }
}
 
cs

 

[ /Models/SignIn.cs ]  - Model 관련 로그인 폼틀이 되는 클래스 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
 
namespace IdentityMVCExam.Models
{
    public class SignIn
    {
        [Required(ErrorMessage="{0}을 입력해 주십시오.")]
        [Display(Name = "사용자이름")]
        public string UserName { get; set; }
 
        [Required(ErrorMessage = "{0}를 입력해 주십시오.")]
        [Display(Name = "비밀번호")]
        public string Password { get; set; }
 
        [Required]
        [Display(Name = "사용자저장")]
        public bool RememberMe { get; set; }
    }
}
 
cs

 

[  /Views/Security/Register.cshtml  ]  - View 관련 회원가입 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@model Register
 
<h2>회원가입</h2>
<form asp-controller="Security" asp-action="Register" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
 
    <table>
        <tr>
            <td class="right"><label asp-for="UserName">이름</label> :</td>
            <td class="left"><input type="text" asp-for="UserName" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="Password">비밀번호</label> :</td>
            <td class="left"><input type="password" asp-for="Password" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="ConfirmPassword">비밀번호 확인</label> :</td>
            <td class="left"><input type="password" asp-for="ConfirmPassword" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="Email">이메일</label> :</td>
            <td class="left"><input type="text" asp-for="Email" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="FullName">전체 이름</label> :</td>
            <td><input type="text" asp-for="FullName" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="BirthDate">생년월일</label> :</td>
            <td class="left"><input type="date" asp-for="BirthDate" /></td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">회원가입하기</button>
            </td>
        </tr>
    </table>
 
    <a asp-controller="Security" asp-action="SignIn">[로그인페이지로 이동하기]</a>
 
</form>
cs

 

[  /Views/Security/SignIn.cshtml  ]  - View 관련 로그인 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@model SignIn
 
<h2>로그인</h2>
<form asp-controller="Security" asp-action="SignIn" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
 
    <table>
        <tr>
            <td class="right"><label asp-for="UserName">이름</label> :</td>
            <td class="left"><input type="text" asp-for="UserName" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="Password">비밀번호</label> :</td>
            <td class="left"><input type="password" asp-for="Password" /></td>
        </tr>
        <tr>
            <td class="right"><label asp-for="RememberMe">사용자저장</label> :</td>
            <td class="left"><input type="checkbox" asp-for="RememberMe" /></td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">로그인</button>
            </td>
        </tr>
    </table>
 
 
    <a asp-controller="security" asp-action="register">[회원가입]</a>
 
</form>
cs

 

[ /Views/_Layout.cshtml ] - 레이아웃 관련 틀 cs페이지 (회원로그인과 비로그인 상태 확인)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"- IdentityMVCExam</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/IdentityMVCExam.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container-fluid">
                <class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">IdentityMVC예제</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
@if (User.Identity.IsAuthenticated) {
                            <li class="nav-item">
                            <class="nav-link text-dark" asp-area="" asp-controller="Security" asp-action="SignOut">@User.Identity.Name 님 로그아웃</a>
                        </li>
else {
                        <li class="nav-item">
                            <class="nav-link text-dark" asp-area="" asp-controller="Security" asp-action="SignIn">로그인</a>
                        </li>
                        <li class="nav-item">
                            <class="nav-link text-dark" asp-area="" asp-controller="Security" asp-action="Register">회원가입</a>
                        </li>
}
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
 
    <br /><br />
    <hr />
    @if (User.Identity.IsAuthenticated) {
        <h2>@User.Identity.Name 님 로그인하였습니다.</h2>
        <form asp-controller="Security" asp-action="SignOutPost" method="post">
            <button type="submit">로그아웃</button>
        </form>
    }
 
    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2023 - IdentityMVC예제
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>
 
cs

User.Identity.IsAuthenticated 으로 로그인인지 구분하고,

User.Identity.Name 값을 불러올 수 있다.

 

 

 

이제 전체 빌드하고 돌려보고 DB 의 데이터 삽입된 내용을 살펴보면,

회원가입시 Custom 한  FullName, BirthDate 항목과 데이터 내용을 살펴본다.

Role 은 Description 이라는 역할을 최초 회원가입시에 자동 Insert  되도록 해놓았다.

 

참고 : https://github.com/Apress/beg-database-prog-using-asp.net-core-3

[ Apress Beginning DataBase Programming Using ASP.Net Core 3 에서 Identity 부분 참고]

 

 

Comments