시스템에 회원가입 등을 기능을 사용하려면 회원정보 저장을 위해 필요한 보안조치 등 신경 써야 할 것이 많은데요. ASP.NET 아이덴티티는 개인정보의 암호처리 등 다양한 기능을 미리 설정해 둔 패키지로 편리하게 회원관리나 관리자기능 구현이 가능합니다.
목차
프로젝트 설정
지네릭 패턴사용하기
서버 아키텍처 - 지네릭 레포지토리
레포지토리는 정적 타입을 가지는 데 이는 엔티티가 증가할 때마다 레포지토리를 추가로 생성해야 하는 불편함을 줍니다. 지네릭은 동적으로 타입을 지정하는 기능으로 지네릭 레포지토리를
jin-co.tistory.com
패키지 설치
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.AspNetCore.Identity
Microsoft.IdentityModel.Tokens
Microsoft.IdentityModel.Tokens.Jwt
Microsoft.AspNetCore.Authentication.JwtBearer
엔티티 생성
유저 엔티티를 저장할 폴더를 만들고
클래스를 생성
아이덴티티 유저를 상속합니다.
IdentityUser
기본속성 외 필요한 속성이 있을 경우 생성한 클래스에 추가하거나
참조하는 다른 엔티티를 생성하여 추가합니다
※ 참조되는 엔티티에 주엔티티와 주엔티티의 아이디를 추가하면 두 엔티티 간에 1대 1 관계가 설정됩니다
이때, 참조되는 엔티티에 유저아이디 항목이 선택필드가 되면 안 되므로 어노테이션을 사용하여 필수 필드로 설정해 주어야 합니다
콘텍스트 생성
유저정보를 저장할 디비가 필요하므로 새로운 콘텍스트를 생성합니다. 콘텍스트를 저장할 폴더를 생성
클래스를 추가하고
아이덴티티 디비 콘텍스트를 상속받습니다
IdentityDbContext
컨스트럭터를 추가하고 콘텍스트를 지정 한 뒤(콘텍스트가 두 개 이상인 경우 지정 필요) 아래 메서드를 추가합니다
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
또, 위에서 추가한 속성이 반영되려면 반드시 아래처럼 아이덴티티 디비 콘텍스트 인터페이스에 지네릭 타입으로 설정한 엔티티를 넣어주어야 합니다
나머지 콘텍스트에도 컨스트럭터에 콘텍스트를 지정합니다
환경변수 지정
유저저장을 위한 디비 커넥션 스트링을 'appsettings' 파일에 환경변수로 저장합니다
서비스 추가
정리를 위해 에이피아이 폴더에 아이덴티티 서비스를 추가할 클래스를 생성합니다 (생성한 클래스 파일을 Program.cs에 연결)
클래스를 스태틱으로 바꾸고 아래 메서드를 추가합니다
public static IServiceCollection AddIdentityService(this IServiceCollection services, IConfiguration config)
{
services.AddDbContext<IdentityContext>(opt =>
{
opt.UseSqlite(config.GetConnectionString("IdentityConnection"));
});
return services;
}
Program.cs 파일로 이동하여 설정한 스태틱 메서드를 아래와 같이 추가합니다
마이그래이션
루트폴더로 이동하여 아래명령어를 통해 마이그래이션을 생성합니다. -p는 콘텍스트 및 데이터가 생성될 폴더 -s는 엔트리포인트 -c는 사용할 콘텍스트 명을 의미하며 (콘텍스트가 복수인 경우 지정필요) -o는 마이그레이션 파일이 생성될 경로를 말합니다
dotnet ef migrations add <MigrationName> -p Infrastructure/ -s API/ -c IdentityContext -o <Path>
기본 유저생성하기
유저생성을 자동화할 클래스 생성
생성한 클래스에 비동기 스태틱 메서드에 유저를 생성하는 코드를 추가합니다. '1234Pp!'는 비밀번호이며 소문자, 대문자, 숫자, 특수문자를 각 1개 이상 포함해야 합니다.
public static async Task SeedUserAsync(UserManager<AppUser> userManager)
{
if (!userManager.Users.Any())
{
var user = new AppUser
{
DisplayName = "Tom",
Email = "tom@tom.com",
UserName = "tomtom",
Address = new Address
{
FirstName = "Tom",
LastName = "Tam",
Street = "1234",
City = "KL",
ZipCode = "12345",
}
};
await userManager.CreateAsync(user, "1234Pp!");
}
}
아이덴티티 서비스 익스텐션 클래스로 이동하여 아래 코드를 추가합니다
services.AddIdentityCore<AppUser>(opt => {
})
.AddEntityFrameworkStores<IdentityContext>()
.AddSignInManager<SignInManager<AppUser>>();
services.AddAuthentication();
services.AddAuthorization();
Program.cs 파일로 이동하여 아래코드를 추가합니다
app.UseAuthentication();
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider;
var authContext = services.GetRequiredService<UserIdentityContext>();
var userManager = services.GetRequiredService<UserManager<User>>();
try
{
await authContext.Database.MigrateAsync();
await UserIdentityContextSeed.SeedUserAsync(userManager);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
작업이 완료되면 API 폴더로 이동하여
cd /API
아래 명령어로 앱을 실행하여 데이터베이스를 생성합니다.
dotnet watch
아이덴티티 사용하기
매개변수 클래스 생성
아래와 같이 로그인이나 회원가입 시 필요한 정보를 개별 매개변수로 추가하면 URL의 파라미터로 요청해야 합니다. 하지만 사용자 정보를 URL에 노출하는 것이 이상적이지는 않죠
해당 정보를 요청의 바디로 옮기는 방법으로 매개변수들을 담은 클래스를 만들고 이를 매개변수로 사용하는 방법입니다.
클래스를 생성하고
필요한 매개변수들을 속성으로 추가합니다
컨트롤러 생성
사용자 정보 엔트리포인트 설정을 위해 컨트롤러를 생성합니다
컨스트럭터를 추가하고 사용자 정보를 가져오는 데 사용되는 'UserManager'와 로그인에 필요한 사용자 정보가 맞는지 비교하기 위한 'SignInManager'를 인젝션 합니다
▶ 로그인
로그인 엔드포인트를 아래와 같이 설정합니다
[HttpPost("login")]
public async Task<ActionResult<User>> Login(AuthDTO authDTO)
{
var user = await _userManager.FindByEmailAsync(authDTO.Email);
var result = await _signInManager.CheckPasswordSignInAsync(user, authDTO.Password, false);
return user;
}
설정 후 앱을 실행했을 때 아래와 같이 회원정보가 반환되면 성공
▶ 회원가입
회원가입 엔드포인트를 아래와 같이 설정합니다
[HttpPost("join")]
public async Task<ActionResult<User>> Join(string name, string email, string password)
{
var user = new User
{
DisplayName = name,
Email = email,
UserName = name,
};
var result = await _userManager.CreateAsync(user, password);
return user;
}
설정 후 앱을 실행했을 때 아래와 같이 가입회원정보가 반환되면 성공
제이슨 웹 토큰 추가하기
토큰은 로그인 시 프론트와 서버 간에 해당 사용자를 식별하기 위해 사용됩니다. 제이슨 웹 토큰은 정보를 제이슨 형태로 암호화해서 보내는 방식으로 많이 사용되는 방식입니다. 주의할 점은 페이로드로 전송되는 사용자 정보는 쉽게 해독이 가능하므로 비밀번호 같은 정보는 토큰생성에 사용하지 않는 것이 좋습니다.
환경변수 생성
토큰 생성에 필요한 비밀키와 발행인을 'appsettings'에 환경변수로 설정합니다. 이때 비밀키의 값은 12자 이상이어야 합니다
"Token": {
"Key": "secret secret secret",
"Issuer": "http://localhost:5099"
}
토큰생성
인터페이스를 생성하고
토큰을 반환하는 메서드를 추가합니다
인터페이스를 구현할 클래스를 생성하고
컨스트럭터를 생성하고 컨피그래이션을 인젝션 하고 시메트릭키를 추가하는 코드를 추가합니다
private readonly IConfiguration _config;
private readonly SymmetricSecurityKey _key;
public TokenService(IConfiguration config)
{
this._config = config;
this._key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Token:Key"]));
}
생성한 인터페이스를 상속하고 구현한 뒤 토큰생성 코드를 추가합니다
public string CreateToken(User user)
{
var claims = new List<Claim> {
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.GivenName, user.DisplayName)
};
var credentials = new SigningCredentials(_key, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = credentials,
Issuer = _config["Token:Issuer"]
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
토큰확인 옵션추가
서버에 해당 유저를 확인하기 위한 요청이 있을 때 이를 확인하는 아래 코드를 위에서 생성한 'IdentityServiceExtension'의 'AddAuthentication'에 옵션으로 추가합니다
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Token:Key"])),
ValidIssuer = config["Token:Issuer"],
ValidateIssuer = true,
ValidateAudience = false
};
});
서비스 추가
Program.cs 파일에 설정한 토큰서비스를 등록합니다
builder.Services.AddScoped<ITokenService, TokenService>();
컨트롤러 업데이트
유저 컨트롤러로 이동하여 설정한 토큰 인터페이스를 인젝션 합니다
반환되는 정보에 토큰을 추가하기 위해 디티오 클래스를 생성합니다 (디티오 사용하기)
생성된 클래스에 반환할 속성들과 토큰을 추가합니다
컨트롤러의 각 엔드포인트의 반환타입을 생성한 디티오로 교체하고 반환되는 값을 아래처럼 수정합니다
설정 후 앱을 실행했을 때 아래와 같이 토큰정보가 반환되면 성공
로그인 기능 다른 컨트롤러에서 사용하기
설정한 로그인 기능을 활용하여 특정 엔드포인트의 접근을 제한하려면 해당 엔드포인트에 아래 어노테이션을 추가하면 됩니다
[Authorize]
다시 서버를 가동하고 해당 유알엘에 토큰없이 요청을 보내면 401이 반환됩니다.
로그인을 가장하여 다시 헤더에 토큰을 추가해서 요청하면
아래처럼 정보가 반환됩니다
이상으로 아이덴티티를 활용하는 방법을 보았습니다
참고
JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
jwt.io
'백엔드 > 닷넷' 카테고리의 다른 글
서버 메모리 - 리디스 (3) | 2023.05.10 |
---|---|
웹 에이피아이 폴더구조 및 개발환경 설정하기 (2) | 2023.05.07 |
브라우저 접근 허용하기 (3) | 2023.04.25 |
지네릭 레포지토리 스페시피케이션 패턴 - 검색기능 추가하기 (0) | 2023.04.25 |
지네릭 레포지토리 스페시피케이션 패턴 - 페이지네이션 추가하기 (0) | 2023.04.24 |