导航
- 常见的登录实现方式
- 什么是JWT
- JWT的组成
- Header
- Payload
- Signature
- 认证流程
- .NET Core 实现jwt登录认证
- 集成步骤
- 测试
- 前端如何调用带jwt的web api
- 前端调jwt的web api
- 解决跨域访问问题
- 结语
- 参考
常见的登录实现方式
- cookie
1)用户登录认证之后,将用户信息保存在本地浏览器中cookie(还可以设置过期时间),
2)后面每次发起http请求,都自动携带上该信息,就能达到认证用户,保持用户在线。
- cookie+session
1)用户登录成功之后,在服务端就会生成一个键值对。key叫做sessionid,value就保存ssession(用户信息),客户端那边就需要把sessionid存储到cookie。
2)后续的http请求会携带上sessionid,服务器就根据sessionid来查找对应的信息。
- token
1)用户登录成功之后,token 由服务器本身根据算法生成后下发给客户端,服务器端无需额外存储。
2)客户端请求服务器时,在请求头中追加携带该token
3)服务器端对token进行验签,从而决定本次访问是拒绝还是放行。
以上是我们常见的登录认证方式,但是在前后端分离的项目中,如今比较流行的还是JWT。
什么是JWT
JWT(json web token)基于开放标准(RFC 7519),是一种无状态的分布式的身份验证方式,主要用于在网络应用环境间安全地传递声明。它是基于JSON的,所以它也像json一样可以在.Net、JAVA、JavaScript,、PHP等多种语言使用。
为什么要使用JWT?
传统的Web应用一般采用Cookies+Session来进行认证。但对于目前越来越多的App、小程序等应用来说,它们对应的服务端一般都是RestFul 类型的无状态的API,再采用这样的的认证方式就不是很方便了。而JWT这种无状态的分布式的身份验证方式恰好符合这样的需求。
JWT的组成:
它是由三段“乱码”字符串通过两个“.”连接在一起组成。官网https://jwt.io/提供了它的验证方式
它的三个字符串分别对应了Header、Payload和Signature三部分
Header
Header:
{
"alg": "HS256",
"typ": "JWT"
}
标识加密方式为HS256,Token类型为JWT, 这段JSON通过Base64Url编码形成上例的第一个字符串
Payload
Payload是JWT用于信息存储部分,其中包含了许多种的声明(claims)。
可以自定义多个声明添加到Payload中,系统也提供了一些默认的类型
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
这部分通过Base64Url编码生成第二个字符串。
Signature
Signature是用于Token的验证。它的值类似这样的表达式:Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret),也就是说,它是通过将前两个字符串加密后生成的一个新字符串。
所以只有拥有同样加密密钥的人,才能通过前两个字符串获得同样的字符串,通过这种方式保证了Token的真实性。
认证流程
- 认证服务器:用于用户的登录验证和Token的发放。
-应用服务器:业务数据接口。被保护的API。
-客户端:一般为APP、小程序等。
.NET Core 实现jwt登录认证
- 安装Microsoft.AspNetCore.Authentication.JwtBearer包
- 增加用于验证的实体
public class TokenManagement
{
[JsonProperty("secret")]
public string Secret { get; set; }
[JsonProperty("issuer")]
public string Issuer { get; set; }
[JsonProperty("audience")]
public string Audience { get; set; }
[JsonProperty("accessExpiration")]
public int AccessExpiration { get; set; }
[JsonProperty("refreshExpiration")]
public int RefreshExpiration { get; set; }
}
- appsettings.json文件中增加jwt配置
"tokenManagement": {
"secret": "*#@^%$!(zhike-secret-key)^~",
"issuer": "zhike.business.Api",
"audience": "zhike.business.Api",
"accessExpiration": 30,
"refreshExpiration": 60
}
- StartUp类中注册Authentication
services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
- 注册中间件
app.UseAuthentication();
- 新建BookController
[Route("api/[controller]")]
[ApiController]
public class BookController : Controller
{
// GET: api/<controller>
[HttpGet]
[AllowAnonymous]
public IEnumerable<string> Get()
{
return new string[] { "ASP", "C#" };
}
// POST api/<controller>
[HttpPost]
[Authorize]
public JsonResult Post()
{
return new JsonResult("Create Book ...");
}
}
- 增加接口
public interface IAuthenticateService:IService
{
bool IsAuthenticated(LoginRequestDTO request, out string token);
}
- 实现接口
public class AuthenticateService : IAuthenticateService
{
private readonly TokenManagement _tokenManagement;
public AuthenticateService(IOptions<TokenManagement> tokenManagement)
{
this._tokenManagement = tokenManagement.Value;
}
public bool IsAuthenticated(LoginRequestDTO request, out string token)
{
//TODO:验证账户密码逻辑 自行补全
token = string.Empty;
var claims = new[]
{
new Claim(ClaimTypes.Name,request.UserName),
new Claim(ClaimTypes.Name,request.Password)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
SigningCredentials credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials);
token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return true;
}
}
9.在Controller中增加Login
[Produces("application/json")]
[Route("v1/account")]
public class AccountController : BaseApiController
{
private readonly IAuthenticateService _authService;
public AccountController(
IAuthenticateService authService)
{
_authService = authService;
}
/// <summary>
/// 登录
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost("login")]
[ProducesResponseType(typeof(ResponseInfo<LoginVM>), 200)]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody]LoginRequestDTO req)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid Request");
}
string sToken;
if (_authService.IsAuthenticated(req, out sToken))
{
return Ok(sToken);
}
return BadRequest("Invalid Request");
}
}
测试
启动jwt.demo.passport站点,在浏览器输入:http://localhost:5000/swagger/index.html
入参:
{
"userName": "jeffreyHu",
"password": "123456"
}
返回结果
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjpbImplZmZyZXlIdSIsIjEyMzQ1NiJdLCJleHAiOjE1OTE1MjU0MTYsImlzcyI6IkhTVHJhZGUuQXBwbGV0TWFuYWdlLkFwaSIsImF1ZCI6IkhTVHJhZGUuQXBwbGV0TWFuYWdlLkFwaSJ9.6r0zaoWCpLlwOU-zgXQJZ2fuFj9dqzi9wSZsAo986e0"
此时,我们可以拷贝该字符串到jwt官网验证是否解码
以上基本实现 .NET CORE登录生成jwt-token功能。
前端如何调用带jwt的web api
前后端分离模式,很早之前笔者有过实践(.NET3.0时代 )。最近几年炒的很火,jwt的应用使得前后端分离更加便捷。
前端调jwt的web api
基本思路是调用登录接口,获取token,使用token请求其他JWT接口:
这里以angular为例,如下
getHomeDetails(): Observable<HomeDetails> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let authToken = localStorage.getItem('auth_token');
headers.append('Authorization', `Bearer ${authToken}`);
return this.http.get(this.baseUrl + "/dashboard/home",{headers})
.map(response => response.json())
.catch(this.handleError);
}
解决跨域访问问题
- nuget安装 Microsoft.AspNetCore.Cors 中间件。
- 在Startup类里先定义一个全局变量。
private readonly string AllowSpecificOrigin = "AllowSpecificOrigin";
3.在Startup的ConfigureServices中添加以下代码来配置跨域处理。
#region 跨域
services.AddCors(options =>
{
options.AddPolicy(AllowSpecificOrigin,
builder =>
{
builder.AllowAnyMethod()
.AllowAnyOrigin()
.AllowAnyHeader();
});
});
#endregion
4.在Startup的Configure中添加以下代码来配置跨域处理。
app.UseRouting();
//CORS 中间件必须配置为在对 UseRouting 和 UseEndpoints的调用之间执行。 配置不正确将导致中间件停止正常运行。
app.UseCors(AllowSpecificOrigin);
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
结语
登录认证的方式很多,不同的场景有不同的方式,没有哪个最通用的方案。
JWT一种相对比较流行且轻量的实现方案,目前在前后端分离项目中使用比较多。