과거에는 웹 측의 신원 인증이 쿠키 | 세션 인증을 기반으로 했지만, 더 많은 터미널이 등장하기 전에는 이에 문제가 없었습니다. 그러나 웹 API 시대에는 브라우저와 클라이언트가 다양하므로 문제가 있습니다. 이러한 클라이언트는 쿠키가 무엇인지 모릅니다. (쿠키는 실제로 세션을 유지하기 위해 브라우저가 만드는 작은 트릭이지만 HTTP 자체는 Stateless이며 다양한 클라이언트가 제공할 수 있는 모든 것은 HTTP 작업을 위한 API입니다.)
기반 토큰 이에 대한 응답으로 신원 인증이 탄생했습니다. 더 개방적이고 보안이 더 높습니다.
토큰 기반 신원 인증을 구현하는 방법은 여러 가지가 있지만 여기서는 Microsoft에서 제공하는 API만 사용합니다.
다음 예에서는 Microsoft JwtSecurityTokenHandler를 사용하여 Beare 토큰을 기반으로 ID 인증을 완료하도록 안내합니다.
참고: 이런 종류의 기사는 단계별 튜토리얼입니다. 전체 코드를 다운로드하고 코드 구조를 분석하는 것이 의미가 있습니다.
프로젝트 만들기
VS에서 새 프로젝트를 만들고 프로젝트 유형으로 ASP.NET Core 웹 애플리케이션(.NET Core)을 선택한 다음 프로젝트 이름을 CSTokenBaseAuth
코딩보조 클래스 만들기프로젝트 루트 디렉터리에 Auth 폴더를 만들고 RSAKeyHelper.cs 및 TokenAuthOption.cs 두 파일을 추가합니다. RSAKeyHelper.cs
using System.Security.Cryptography; namespace CSTokenBaseAuth.Auth { public class RSAKeyHelper { public static RSAParameters GenerateKey() { using (var key = new RSACryptoServiceProvider(2048)) { return key.ExportParameters(true); } } } }TokenAuthOption.cs
using System; using Microsoft.IdentityModel.Tokens; namespace CSTokenBaseAuth.Auth { public class TokenAuthOption { public static string Audience { get; } = "ExampleAudience"; public static string Issuer { get; } = "ExampleIssuer"; public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey()); public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature); public static TimeSpan ExpiresSpan { get; } = TimeSpan.FromMinutes(20); } }Startup.cs
ConfigureServices에 다음 코드를 추가합니다.
services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); });전체 코드는 다음과 같습니다.
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); // Enable the use of an [Authorize("Bearer")] attribute on methods and classes to protect. services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); }); services.AddMvc(); }Configure 메소드에 다음 코드를 추가하세요
app.UseExceptionHandler(appBuilder => { appBuilder.Use(async (context, next) => { var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature; //when authorization has failed, should retrun a json message to client if (error != null && error.Error is SecurityTokenExpiredException) { context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { authenticated = false, tokenExpired = true } )); } //when orther error, retrun a error message json to client else if (error != null && error.Error != null) { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { success = false, error = error.Error.Message } )); } //when no error, do next. else await next(); }); });이 코드는 주로 Handle Error에 사용됩니다. 신원 인증에 실패하면 예외가 발생하며 이 예외는 여기에서 처리됩니다. 그런 다음 같은 방법으로 다음 코드를 추가합니다.
app.UseExceptionHandler(appBuilder => { appBuilder.Use(async (context, next) => { var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature; //when authorization has failed, should retrun a json message to client if (error != null && error.Error is SecurityTokenExpiredException) { context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { authenticated = false, tokenExpired = true } )); } //when orther error, retrun a error message json to client else if (error != null && error.Error != null) { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { success = false, error = error.Error.Message } )); } //when no error, do next. else await next(); }); });JwtBearerAuthentication 적용
app.UseJwtBearerAuthentication(new JwtBearerOptions { TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = TokenAuthOption.Key, ValidAudience = TokenAuthOption.Audience, ValidIssuer = TokenAuthOption.Issuer, ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(0) } });완전한 코드는 다음과 같아야 합니다
using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication.JwtBearer; using CSTokenBaseAuth.Auth; using Microsoft.AspNetCore.Diagnostics; using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; namespace CSTokenBaseAuth { public class Startup { public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); if (env.IsEnvironment("Development")) { // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately. builder.AddApplicationInsightsSettings(developerMode: true); } builder.AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); // Enable the use of an [Authorize("Bearer")] attribute on methods and classes to protect. services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); }); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseApplicationInsightsRequestTelemetry(); app.UseApplicationInsightsExceptionTelemetry(); #region Handle Exception app.UseExceptionHandler(appBuilder => { appBuilder.Use(async (context, next) => { var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature; //when authorization has failed, should retrun a json message to client if (error != null && error.Error is SecurityTokenExpiredException) { context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { authenticated = false, tokenExpired = true } )); } //when orther error, retrun a error message json to client else if (error != null && error.Error != null) { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject( new { success = false, error = error.Error.Message } )); } //when no error, do next. else await next(); }); }); #endregion #region UseJwtBearerAuthentication app.UseJwtBearerAuthentication(new JwtBearerOptions { TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = TokenAuthOption.Key, ValidAudience = TokenAuthOption.Audience, ValidIssuer = TokenAuthOption.Issuer, ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(0) } }); #endregion app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Login}/{action=Index}"); }); } } }Controllers에서 새 Web API 컨트롤러 클래스를 만들고 이름을 지정합니다. TokenAuthController.cs. 여기에서 로그인 인증을 완료하겠습니다. 동일한 파일 아래에 사용자 모델과 사용자 저장 공간을 시뮬레이션하는 데 사용되는 두 개의 클래스를 추가합니다.
public class User { public Guid ID { get; set; } public string Username { get; set; } public string Password { get; set; } } public static class UserStorage { public static List<User> Users { get; set; } = new List<User> { new User {ID=Guid.NewGuid(),Username="user1",Password = "user1psd" }, new User {ID=Guid.NewGuid(),Username="user2",Password = "user2psd" }, new User {ID=Guid.NewGuid(),Username="user3",Password = "user3psd" } }; }다음으로 TokenAuthController.cs에 다음 메서드를 추가합니다.
private string GenerateToken(User user, DateTime expires) { var handler = new JwtSecurityTokenHandler(); ClaimsIdentity identity = new ClaimsIdentity( new GenericIdentity(user.Username, "TokenAuth"), new[] { new Claim("ID", user.ID.ToString()) } ); var securityToken = handler.CreateToken(new SecurityTokenDescriptor { Issuer = TokenAuthOption.Issuer, Audience = TokenAuthOption.Audience, SigningCredentials = TokenAuthOption.SigningCredentials, Subject = identity, Expires = expires }); return handler.WriteToken(securityToken); }이 메서드는 인증 토큰만 생성합니다. 다음은 Let's Let's 호출할 다른 메소드 추가 동일 파일에 다음 코드 추가
[HttpPost] public string GetAuthToken(User user) { var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password); if (existUser != null) { var requestAt = DateTime.Now; var expiresIn = requestAt + TokenAuthOption.ExpiresSpan; var token = GenerateToken(existUser, expiresIn); return JsonConvert.SerializeObject(new { stateCode = 1, requertAt = requestAt, expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken = token }); } else { return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" }); } }파일의 전체 코드는 다음과 같아야 합니다. 방법
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Principal; using Microsoft.IdentityModel.Tokens; using CSTokenBaseAuth.Auth; namespace CSTokenBaseAuth.Controllers { [Route("api/[controller]")] public class TokenAuthController : Controller { [HttpPost] public string GetAuthToken(User user) { var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password); if (existUser != null) { var requestAt = DateTime.Now; var expiresIn = requestAt + TokenAuthOption.ExpiresSpan; var token = GenerateToken(existUser, expiresIn); return JsonConvert.SerializeObject(new { stateCode = 1, requertAt = requestAt, expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken = token }); } else { return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" }); } } private string GenerateToken(User user, DateTime expires) { var handler = new JwtSecurityTokenHandler(); ClaimsIdentity identity = new ClaimsIdentity( new GenericIdentity(user.Username, "TokenAuth"), new[] { new Claim("ID", user.ID.ToString()) } ); var securityToken = handler.CreateToken(new SecurityTokenDescriptor { Issuer = TokenAuthOption.Issuer, Audience = TokenAuthOption.Audience, SigningCredentials = TokenAuthOption.SigningCredentials, Subject = identity, Expires = expires }); return handler.WriteToken(securityToken); } } public class User { public Guid ID { get; set; } public string Username { get; set; } public string Password { get; set; } } public static class UserStorage { public static List<User> Users { get; set; } = new List<User> { new User {ID=Guid.NewGuid(),Username="user1",Password = "user1psd" }, new User {ID=Guid.NewGuid(),Username="user2",Password = "user2psd" }, new User {ID=Guid.NewGuid(),Username="user3",Password = "user3psd" } }; } }다음으로 인증 확인 부분을 완료합니다. Controllers에서 새 웹 API 컨트롤러 클래스를 만들고 이름을 ValuesController로 지정합니다. cs다음 코드 추가
public string Get() { var claimsIdentity = User.Identity as ClaimsIdentity; var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value; return $"Hello! {HttpContext.User.Identity.Name}, your ID is:{id}"; }메소드에 장식 속성 추가
[HttpGet] [Authorize("Bearer")] 完整的文件代码应该是这样 using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; namespace CSTokenBaseAuth.Controllers { [Route("api/[controller]")] public class ValuesController : Controller { [HttpGet] [Authorize("Bearer")] public string Get() { var claimsIdentity = User.Identity as ClaimsIdentity; var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value; return $"Hello! {HttpContext.User.Identity.Name}, your ID is:{id}"; } } }
마지막으로 뷰를 추가하겠습니다Controllers에서 새 웹 컨트롤러 클래스를 만들고 이름을 LoginController.cs로 지정합니다코드는 다음과 같아야 합니다
using Microsoft.AspNetCore.Mvc; namespace CSTokenBaseAuth.Controllers { [Route("[controller]/[action]")] public class LoginController : Controller { public IActionResult Index() { return View(); } } }프로젝트 Views 디렉터리에 Login이라는 새 디렉터리를 만들고 그 안에 새 Index.cshtml 파일을 만듭니다. 코드는 다음과 같습니다
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <button id="getToken">getToken</button> <button id="requestAPI">requestAPI</button> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script> $(function () { var accessToken = undefined; $("#getToken").click(function () { $.post( "/api/TokenAuth", { Username: "user1", Password: "user1psd" }, function (data) { console.log(data); if (data.stateCode == 1) { accessToken = data.accessToken; $.ajaxSetup({ headers: { "Authorization": "Bearer " + accessToken } }); } }, "json" ); }) $("#requestAPI").click(function () { $.get("/api/Values", {}, function (data) { alert(data); }, "text"); }) }) </script> </body> </html>PHP 중국어 웹사이트를 지원해주세요. ASP.NET Core에서 토큰 기반 ID 인증 예제 구현에 대한 더 많은 관련 기사를 보려면 PHP 중국어 웹사이트에 주목하세요!