지난 시간에 구글 로그인까지 완료했으니 첫 로그인 시 등장하는 등록 페이지를 조금 바꿔보자.
먼저, 인증과 관련된 페이지는 어떤 것들이 있는지 살펴보기 위해 다음 커맨드를 입력한다.
> dotnet aspnet-codegenerator identity --listFiles
Account.으로 시작하는 파일들이 많이 보일텐데 지금 필요한 파일은 ExternalLogin!
> dotnet aspnet-codegenerator identity -dc 프로젝트이름.Data.ApplicationDbContext --files "Account.ExternalLogin"
지금 필요한 ExternalLogin 페이지가 Area/Identity/Pages/Account/ 위치에 스캐폴드가 적용되어 파일이 생성된걸 확인할 수 있다. 여러 사용자들이 로그인하는 웹사이트를 만들고 있는 사람이라면 ExternalLogin 뿐만 아니라 Login, Register 등등 다양한 페이지를 입맛에 맞게 바꾸자~
(스캐폴드하면서 생긴 _ManageNav.cshtml에서 기본적인 IndentityUser 사용하므로 지난 시간에 만든 ApplicationUser로 변경해 줘야 함)
<ExternalLogin.cshtml>
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
|
@page
@model ExternalLoginModel
@{
ViewData["Title"] = "신규 등록";
}
<h1>@ViewData["Title"]</h1>
<h4 id="external-login-title">공급자(@Model.ProviderDisplayName)로부터 제공받은 계정을 연결합니다.</h4>
<hr />
<p id="external-login-description" class="text-info">
이하 모든 사항을 기입하면 모든 등록 절차가 완료됩니다.
</p>
<div class="row">
<div class="col-md-4">
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" readonly/>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.RealName"></label>
<input asp-for="Input.RealName" class="form-control" />
<span asp-validation-for="Input.RealName" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
|
cs |
<ExternalLogin.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
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using [프로젝트명].Models;
namespace [프로젝트명].Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ExternalLoginModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IEmailSender _emailSender;
private readonly ILogger<ExternalLoginModel> _logger;
public ExternalLoginModel(
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
ILogger<ExternalLoginModel> logger,
IEmailSender emailSender)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ProviderDisplayName { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "ID / E-Mail")]
public string Email { get; set; }
[Required(ErrorMessage = "사용자 이름을 입력해주세요.")]
[StringLength(30, ErrorMessage = "최소 {2}자 이상 입력해야 합니다.", MinimumLength = 2)]
[Display(Name = "사용자 이름")]
public string RealName { get; set; }
}
public IActionResult OnGetAsync(string provider, string returnUrl = null)
{
return OnPost(provider, returnUrl);
// return RedirectToPage("./Login");
}
public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, RealName = Input.RealName };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
await _userManager.AddToRoleAsync(user, "User");
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
}
}
|
cs |
Email의 경우 유니크한 값이고 AspNetUsers 테이블에서 NormalizedUserName은 유니크한 필드이기 때문에 첫 로그인시 등장하는 등록 페이지에서 Provider에게 받은 주소를 변경할 수 없도록 하고 지난 시간 만든 ApplicationUser에 추가한 RealName을 통해 실제 사용자의 이름을 받는 형태로 바꾸었다.
OnGetAsync의 경우 RedirectToPage를 리턴하지 않고 OnPost를 콜 했는데 그 이유는 Shared 폴더의 LoginDisplay.razor을 다음과 같이 수정했기 때문이다.
<LoginDisplay.razor>
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
<form method="post" action="Identity/Account/LogOut">
<button type="submit" class="nav-link btn btn-link">Log out</button>
</form>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/ExternalLogin?provider=Google&returnUrl=/">Log in</a>
</NotAuthorized>
</AuthorizeView>
|
cs |
위와 같은 수정으로 우측 상단의 로그인 페이지를 누르면 즉시 구글 로그인으로 향한다. 현재 개발 중인 인트라넷에서는 구글 인증외의 로그인은 허용하지 않기 때문에 이와 같이 해두는 것이 편하다.
이쯤되서 Pages/FetchData.razor 상단에 인증 속성을 추가 해보자.
@page "/fetchdata"
@attribute [Authorize]
이렇게 하면 해당 페이지는 로그인 하지 않은 사용자의 경우 볼 수 없게 된다. 만약 롤로 구분하려면?
@attribute [Authorize(Roles = "Admin")]
이렇게 하면 현재 추가되는 유저는 기본으로 User 롤을 가지기 때문에 로그인이 되었다해도 해당 페이지는 볼 수 없다.
오늘은 여기까지~
#End.Code123 "블로그질만 시작하면 회사 일정이 많아진다. 신기하다..."
'닷넷 .NET' 카테고리의 다른 글
.NET 5 / Blazor Server / MySQL #4 Blazor Component (0) | 2020.12.08 |
---|---|
.NET 5 / Blazor Server / MySQL #3 정책 적용 (0) | 2020.12.05 |
.NET 5 / Blazor Server / MySQL #1 구글 로그인 (0) | 2020.12.03 |