웹 API 응용 프로그램에서 JWT 베어러 토큰 (JSON 웹 토큰)을 지원하려고하는데 길을 잃었습니다.
.NET Core 및 OWIN 응용 프로그램에 대한 지원을 참조하십시오.
현재 IIS에서 응용 프로그램을 호스팅하고 있습니다.
애플리케이션에서이 인증 모듈을 어떻게 달성 할 수 있습니까? <authentication>
양식 / Windows 인증을 사용하는 방법과 유사한 구성을 사용할 수 있는 방법이 있습니까?
답변
이 질문에 대답했습니다. 4 년 전 HMAC를 사용하여 ASP.NET 웹 API를 보호하는 방법 .
이제 보안에서 많은 부분이 변경되었습니다. 특히 JWT가 인기를 얻고 있습니다. 여기서는 JWT를 가장 간단하고 기본적인 방식으로 사용하는 방법을 설명하려고 노력하므로 OWIN, Oauth2, ASP.NET Identity … :)의 정글에서 길을 잃지 않을 것입니다.
JWT 토큰을 모르는 경우 다음을 조금 살펴 봐야합니다.
https://tools.ietf.org/html/rfc7519
기본적으로 JWT 토큰은 다음과 같습니다.
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
예:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQSiZMQQIiQIQIQQIQIQQIQQIQQIQQIQQIQQIQQIQQIQIQQIQQIQIQQIQIQIQIQIQIQIQIQIQIQIQIQIQIQIQIQIQIQIQ
JWT 토큰에는 세 가지 섹션이 있습니다.
- 헤더 : Base64로 인코딩 된 JSON 형식
- 클레임 : Base64로 인코딩 된 JSON 형식입니다.
- 서명 : Base64로 인코딩 된 헤더 및 클레임을 기반으로 만들고 서명했습니다.
위의 토큰과 함께 jwt.io 웹 사이트를 사용하면 토큰을 해독하여 아래와 같이 볼 수 있습니다.
기술적으로 JWT는 헤더에서 서명 된 서명과 헤더에 지정된 보안 알고리즘으로 클레임을 사용합니다 (예 : HMACSHA256). 따라서 클레임에 민감한 정보를 저장하는 경우 HTTP를 통해 JWT를 전송해야합니다.
이제 JWT 인증을 사용하기 위해 레거시 Web Api 시스템이있는 경우 실제로 OWIN 미들웨어가 필요하지 않습니다. 간단한 개념은 JWT 토큰을 제공하는 방법과 요청이 올 때 토큰을 확인하는 방법입니다. 그게 다야.
데모로 돌아가서 JWT 토큰을 가볍게 유지하기 위해 JWT 만 저장 username
하고 저장 expiration time
합니다. 그러나 이런 식으로 역할과 같은 정보를 추가하려면 새로운 로컬 ID (주체)를 다시 작성해야합니다. 역할 권한 부여를 수행하려는 경우. 그러나 JWT에 더 많은 정보를 추가하려는 경우 사용자에게 달려 있습니다. 매우 유연합니다.
OWIN 미들웨어를 사용하는 대신 컨트롤러의 조치를 사용하여 JWT 토큰 엔드 포인트를 제공 할 수 있습니다.
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
이것은 순진한 행동입니다. 프로덕션에서는 POST 요청 또는 기본 인증 엔드 포인트를 사용하여 JWT 토큰을 제공해야합니다.
기반으로 토큰을 생성하는 방법은 username
무엇입니까?
System.IdentityModel.Tokens.Jwt
Microsoft에서 호출 한 NuGet 패키지 를 사용하여 토큰을 생성하거나 원하는 경우 다른 패키지를 생성 할 수도 있습니다. 데모에서는 다음 HMACSHA256
과 SymmetricKey
같이 사용 합니다 .
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
JWT 토큰을 제공하기위한 엔드 포인트가 완료되었습니다. 이제 요청이 올 때 JWT의 유효성을 검사하는 방법은 무엇입니까? 데모에서 내가 JwtAuthenticationAttribute
상속 한 것을 만들었
IAuthenticationFilter
습니다 ( 여기의 인증 필터에 대한 자세한 내용 ).
이 속성을 사용하면 모든 작업을 인증 할 수 있습니다.이 속성을 해당 작업에두기 만하면됩니다.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
WebAPI에 대한 모든 수신 요청의 유효성을 검사하려는 경우 OWIN 미들웨어 또는 DelegateHander를 사용할 수도 있습니다 (컨트롤러 또는 작업에만 해당되지 않음)
다음은 인증 필터의 핵심 방법입니다.
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
워크 플로는 JWT 라이브러리 (위의 NuGet 패키지)를 사용하여 JWT 토큰의 유효성을 검사 한 다음 되돌아옵니다 ClaimsPrincipal
. 사용자가 시스템에 있는지 확인하고 원하는 경우 다른 사용자 지정 유효성 검사를 추가하는 등 더 많은 유효성 검사를 수행 할 수 있습니다. JWT 토큰을 유효성 검증하고 프린시 펄을 다시 가져 오는 코드 :
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
JWT 토큰의 유효성이 검증되고 프린시 펄이 리턴되면, 새로운 로컬 ID를 빌드하고 역할 권한을 점검하기 위해 추가 정보를 넣어야합니다.
config.Filters.Add(new AuthorizeAttribute());
리소스에 대한 익명 요청을 방지하기 위해 전역 범위에서 (기본 인증) 을 추가 해야합니다.
Postman을 사용하여 데모를 테스트 할 수 있습니다.
요청 토큰 (위에서 언급했듯이 데모 용으로 만 해당) :
GET http://localhost:{port}/api/token?username=cuong&password=1
승인 된 요청을 위해 JWT 토큰을 헤더에 넣습니다. 예 :
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
데모는 여기에 있습니다 : https://github.com/cuongle/WebApi.Jwt
답변
최소한의 노력으로 (ASP.NET Core와 마찬가지로 간단하게) 달성했습니다.
이를 위해 나는 OWIN Startup.cs
파일과 Microsoft.Owin.Security.Jwt
라이브러리를 사용 합니다.
앱을 실행 Startup.cs
하려면 다음을 수정해야합니다 Web.config
.
<configuration>
<appSettings>
<add key="owin:AutomaticAppStartup" value="true" />
...
어떻게 Startup.cs
보아야합니까?
using MyApp.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;
[assembly: OwinStartup(typeof(MyApp.App_Start.Startup))]
namespace MyApp.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = ConfigHelper.GetAudience(),
ValidIssuer = ConfigHelper.GetIssuer(),
IssuerSigningKey = ConfigHelper.GetSymmetricSecurityKey(),
ValidateLifetime = true,
ValidateIssuerSigningKey = true
}
});
}
}
}
요즘 많은 사람들이 ASP.NET Core를 사용하고 있으므로 알 수 있듯이 우리가 가지고있는 것과 크게 다르지 않습니다.
그것은 정말로 나를 먼저 당황하게 만들었고, 나는 커스텀 제공자 등을 구현하려고 노력하고있었습니다. 그러나 나는 그것이 그렇게 간단하다고 기대하지 않았습니다. OWIN
그냥 바위!
한 가지 언급 할 점-OWIN 시작 NSWag
라이브러리를 활성화 한 후에 는 작동하지 않습니다 (예 : 일부 사용자는 Angular 앱에 대한 유형 스크립트 HTTP 프록시를 자동 생성 할 수 있습니다).
또한이 솔루션은 매우 간단했다 – 내가 교체 NSWag
로 Swashbuckle
하고 더 이상 문제가되지 않았다.
좋아, 이제 ConfigHelper
코드를 공유 하십시오.
public class ConfigHelper
{
public static string GetIssuer()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Issuer"];
return result;
}
public static string GetAudience()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Audience"];
return result;
}
public static SigningCredentials GetSigningCredentials()
{
var result = new SigningCredentials(GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256);
return result;
}
public static string GetSecurityKey()
{
string result = System.Configuration.ConfigurationManager.AppSettings["SecurityKey"];
return result;
}
public static byte[] GetSymmetricSecurityKeyAsBytes()
{
var issuerSigningKey = GetSecurityKey();
byte[] data = Encoding.UTF8.GetBytes(issuerSigningKey);
return data;
}
public static SymmetricSecurityKey GetSymmetricSecurityKey()
{
byte[] data = GetSymmetricSecurityKeyAsBytes();
var result = new SymmetricSecurityKey(data);
return result;
}
public static string GetCorsOrigins()
{
string result = System.Configuration.ConfigurationManager.AppSettings["CorsOrigins"];
return result;
}
}
또 다른 중요한 측면 – 나는 통해 JWT 토큰을 전송 권한 나 같은 다음를 들어, 헤더 때문에 타이프 라이터 코드 외모 :
(아래 코드는 NSWag에 의해 생성됩니다 )
@Injectable()
export class TeamsServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "https://localhost:44384";
}
add(input: TeamDto | null): Observable<boolean> {
let url_ = this.baseUrl + "/api/Teams/Add";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + localStorage.getItem('token')
})
};
헤더 부분 참조- "Authorization": "Bearer " + localStorage.getItem('token')
답변
다음은 ASP.NET Core Web API에서 JWT 토큰을 사용하는 클레임 기반 인증의 매우 최소화 된 보안 구현입니다.
우선, 클레임이 사용자에게 할당 된 JWT 토큰을 반환하는 엔드 포인트를 노출해야합니다.
/// <summary>
/// Login provides API to verify user and returns authentication token.
/// API Path: api/account/login
/// </summary>
/// <param name="paramUser">Username and Password</param>
/// <returns>{Token: [Token] }</returns>
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] UserRequestVM paramUser, CancellationToken ct)
{
var result = await UserApplication.PasswordSignInAsync(paramUser.Email, paramUser.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
UserRequestVM request = new UserRequestVM();
request.Email = paramUser.Email;
ApplicationUser UserDetails = await this.GetUserByEmail(request);
List<ApplicationClaim> UserClaims = await this.ClaimApplication.GetListByUser(UserDetails);
var Claims = new ClaimsIdentity(new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, paramUser.Email.ToString()),
new Claim(UserId, UserDetails.UserId.ToString())
});
//Adding UserClaims to JWT claims
foreach (var item in UserClaims)
{
Claims.AddClaim(new Claim(item.ClaimCode, string.Empty));
}
var tokenHandler = new JwtSecurityTokenHandler();
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
var encryptionkey = Configuration["Jwt:Encryptionkey"];
var key = Encoding.ASCII.GetBytes(encryptionkey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = Configuration["Jwt:Issuer"],
Subject = Claims,
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Configuration["Jwt:ExpiryTimeInMinutes"])),
//algorithm to sign the token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
token = tokenString
});
}
return BadRequest("Wrong Username or password");
}
이제 다음 과 같이 startup.csConfigureServices
내부 의 서비스에 인증을 추가하여 JWT 인증을 기본 인증 서비스로 추가해야합니다.
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
//ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Encryptionkey"])),
ValidateAudience = false,
ValidateLifetime = true,
ValidIssuer = configuration["Jwt:Issuer"],
//ValidAudience = Configuration["Jwt:Audience"],
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"])),
};
});
이제 다음과 같이 인증 서비스에 정책을 추가 할 수 있습니다.
services.AddAuthorization(options =>
{
options.AddPolicy("YourPolicyNameHere",
policy => policy.RequireClaim("YourClaimNameHere"));
});
양자 택일로 , 당신은 또한 수 (필요 없음) 이것은 단지 응용 프로그램 시작시 한 번 실행으로 데이터베이스에서 귀하의 주장을 모두 채우고이 같은 정책에 추가 :
services.AddAuthorization(async options =>
{
var ClaimList = await claimApplication.GetList(applicationClaim);
foreach (var item in ClaimList)
{
options.AddPolicy(item.ClaimCode, policy => policy.RequireClaim(item.ClaimCode));
}
});
이제 다음과 같이 권한을 부여 할 방법에 정책 필터를 배치 할 수 있습니다.
[HttpPost("update")]
[Authorize(Policy = "ACC_UP")]
public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
{
//your logic goes here
}
도움이 되었기를 바랍니다
답변
JWT 토큰을 지원하기 위해 일부 타사 서버를 사용해야한다고 생각하며 WEB API 2에서는 기본 JWT 지원이 없습니다.
그러나 일부 형식의 서명 된 토큰 (JWT 아님)을 지원하기위한 OWIN 프로젝트가 있습니다. 웹 사이트에 대한 간단한 인증 형식 만 제공하기 위해 축소 된 OAuth 프로토콜로 작동합니다.
자세한 내용은 여기를 참조하십시오 .
다소 길지만 대부분의 부분은 컨트롤러와 ASP.NET Identity의 세부 사항이므로 전혀 필요하지 않습니다. 가장 중요한 것은
9 단계 : OAuth 베어러 토큰 생성 지원 추가
12 단계 : 백엔드 API 테스트
여기에서 프론트 엔드에서 액세스 할 수있는 엔드 포인트 (예 : “/ 토큰”)를 설정하는 방법 (및 요청 형식에 대한 세부 사항)을 읽을 수 있습니다.
다른 단계는 해당 엔드 포인트를 데이터베이스 등에 연결하는 방법에 대한 세부 사항을 제공하며 필요한 부분을 선택할 수 있습니다.
답변
필자의 경우 JWT는 별도의 API로 작성되므로 ASP.NET은 해독하고 유효성 검사 만하면됩니다. 허용되는 답변과 달리 비대칭 알고리즘 인 RSA를 사용하고 있으므로 SymmetricSecurityKey
위에서 언급 한 클래스는 작동하지 않습니다.
결과는 다음과 같습니다.
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using System.Threading.Tasks;
public static async Task<JwtSecurityToken> VerifyAndDecodeJwt(string accessToken)
{
try
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{securityApiOrigin}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
RequireSignedTokens = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
};
new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var validToken);
// threw on invalid, so...
return validToken as JwtSecurityToken;
}
catch (Exception ex)
{
logger.Info(ex.Message);
return null;
}
}