You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
9.9 KiB
281 lines
9.9 KiB
2 years ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using System.Security.Claims;
|
||
|
using System.Threading.Tasks;
|
||
|
using AX.FireTrainingSys.DTOs;
|
||
|
using AX.FireTrainingSys.Models;
|
||
|
using AX.FireTrainingSys.Services;
|
||
|
using Microsoft.AspNetCore.Authorization;
|
||
|
using Microsoft.AspNetCore.Http;
|
||
|
using Microsoft.AspNetCore.Mvc;
|
||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||
|
using Microsoft.EntityFrameworkCore;
|
||
|
using Microsoft.Extensions.Caching.Memory;
|
||
|
using Microsoft.Extensions.Logging;
|
||
|
using Microsoft.Extensions.Options;
|
||
|
using static BCrypt.Net.BCrypt;
|
||
|
|
||
|
namespace AX.FireTrainingSys.Controllers
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 帐号控制器。
|
||
|
/// </summary>
|
||
|
[Produces("application/json")]
|
||
|
[Route("api/[controller]")]
|
||
|
[ApiVersion("1.0")]
|
||
|
//[Authorize(Roles = "Profile")]
|
||
|
[ApiController]
|
||
|
public class AccountController : ControllerBase
|
||
|
{
|
||
|
private readonly IOptionsMonitor<JwtOptions> jwtOptions;
|
||
|
private readonly IJwtService jwtService;
|
||
|
private readonly IMemoryCache memoryCache;
|
||
|
private readonly DriveDbContext dbContext;
|
||
|
|
||
|
|
||
|
public AccountController(IJwtService jwtService,
|
||
|
IOptionsMonitor<JwtOptions> jwtOptions,
|
||
|
IMemoryCache memoryCache,
|
||
|
DriveDbContext dbContext)
|
||
|
{
|
||
|
this.jwtService = jwtService;
|
||
|
this.jwtOptions = jwtOptions;
|
||
|
this.memoryCache = memoryCache;
|
||
|
this.dbContext = dbContext;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 登录系统。
|
||
|
/// </summary>
|
||
|
/// <param name="roleType">角色类型</param>
|
||
|
/// <param name="info">登录信息</param>
|
||
|
/// <returns></returns>
|
||
|
//[ProducesResponseType(ErrorCodes.E600)]
|
||
|
//[ProducesResponseType(ErrorCodes.E611)]
|
||
|
//[ProducesResponseType(ErrorCodes.E612)]
|
||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||
|
[AllowAnonymous]
|
||
|
[HttpPost("SignIn")]
|
||
|
public async Task<ActionResult<IdentityInfo>> AccountSignIn([FromQuery, BindRequired] RoleType roleType, [FromBody] SignInInfo info)
|
||
|
{
|
||
|
var name = info.Name;
|
||
|
|
||
|
var user = await dbContext.Users
|
||
|
.AsNoTracking()
|
||
|
.Where(e => e.Name == name && e.RoleType == roleType)
|
||
|
.FirstOrDefaultAsync();
|
||
|
|
||
|
if (user == default)
|
||
|
return this.ErrorCode(ErrorCodes.E611);
|
||
|
|
||
|
if (!Verify(info.Password, user.Password))
|
||
|
return this.ErrorCode(ErrorCodes.E611);
|
||
|
|
||
|
if (!user.Enabled)
|
||
|
return this.ErrorCode(ErrorCodes.E612);
|
||
|
|
||
|
var userid = user.Id;
|
||
|
var realname = user.RealName;
|
||
|
var rolename = default(string);
|
||
|
|
||
|
if (user.RoleType == RoleType.Admin)
|
||
|
rolename = nameof(RoleType.Admin);
|
||
|
else if (user.RoleType == RoleType.Teacher)
|
||
|
rolename = nameof(RoleType.Teacher);
|
||
|
else
|
||
|
rolename = nameof(RoleType.Student);
|
||
|
|
||
|
var claims = new[]
|
||
|
{
|
||
|
new Claim(JwtClaimTypes.Subject, userid),
|
||
|
new Claim(JwtClaimTypes.Name, realname),
|
||
|
new Claim(JwtClaimTypes.Role, rolename),
|
||
|
new Claim(JwtClaimTypes.Role, "Profile")
|
||
|
};
|
||
|
|
||
|
var identity = new ClaimsIdentity(claims);
|
||
|
|
||
|
var token = jwtService.Create(identity);
|
||
|
var refreshToken = Guid.NewGuid().ToString("N");
|
||
|
var options = jwtOptions.CurrentValue;
|
||
|
|
||
|
memoryCache.Set(refreshToken, userid, DateTimeOffset.Now.AddMinutes(options.RefreshExpires));
|
||
|
|
||
|
var result = new IdentityInfo()
|
||
|
{
|
||
|
Token = token,
|
||
|
RefreshToken = refreshToken,
|
||
|
Expires = options.Expires,
|
||
|
UserId = userid,
|
||
|
RealName = realname,
|
||
|
RoleType = roleType
|
||
|
};
|
||
|
|
||
|
return Ok(result);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 登出系统。
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||
|
[HttpPost("SignOut")]
|
||
|
public Microsoft.AspNetCore.Mvc.ActionResult AccountSignOut()
|
||
|
{
|
||
|
//TODO: 把 JWT 放入黑名单,其它地方则验证黑名单,将来再处理
|
||
|
return Ok();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 修改密码。
|
||
|
/// </summary>
|
||
|
/// <param name="info">修改密码信息</param>
|
||
|
//[ProducesResponseType(ErrorCodes.E611)]
|
||
|
//[ProducesResponseType(ErrorCodes.E612)]
|
||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||
|
[HttpPut("[action]")]
|
||
|
public async Task<Microsoft.AspNetCore.Mvc.ActionResult> Password([FromBody] ModifyPasswordInfo info)
|
||
|
{
|
||
|
var userid = HttpContext.User.FindFirstValue(JwtClaimTypes.Subject);
|
||
|
|
||
|
var user = await dbContext.Users.FirstOrDefaultAsync(e => e.Id == userid);
|
||
|
|
||
|
if (user == null)
|
||
|
return NotFound();
|
||
|
|
||
|
if (!Verify(info.Password, user.Password))
|
||
|
return this.ErrorCode(ErrorCodes.E611);
|
||
|
|
||
|
if (!user.Enabled)
|
||
|
return this.ErrorCode(ErrorCodes.E612);
|
||
|
|
||
|
user.Password = HashPassword(info.NewPassword);
|
||
|
|
||
|
await dbContext.SaveChangesAsync();
|
||
|
|
||
|
return Ok();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 刷新令牌。
|
||
|
/// </summary>
|
||
|
//[ProducesResponseType(ErrorCodes.E613)]
|
||
|
//[ProducesResponseType(ErrorCodes.E614)]
|
||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||
|
[AllowAnonymous]
|
||
|
[HttpPost("[action]")]
|
||
|
public async Task<ActionResult<IdentityInfo>> RefreshToken([FromBody] RefreshTokenInfo info)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(info.RefreshToken) ||
|
||
|
string.IsNullOrEmpty(info.Token))
|
||
|
return this.ErrorCode(ErrorCodes.E613);
|
||
|
|
||
|
//校验缓存中是否有该刷新令牌
|
||
|
if (memoryCache.TryGetValue<string>(info.RefreshToken, out var userid))
|
||
|
{
|
||
|
//校验令牌是否有效
|
||
|
if (!jwtService.Validate(info.Token, out var principal))
|
||
|
return this.ErrorCode(ErrorCodes.E613);
|
||
|
|
||
|
var options = jwtOptions.CurrentValue;
|
||
|
var identity = principal.Identity as ClaimsIdentity;
|
||
|
var realname = principal.FindFirstValue(JwtClaimTypes.Name);
|
||
|
var roletype = RoleType.Student;
|
||
|
|
||
|
if (principal.IsInRole(nameof(RoleType.Admin)))
|
||
|
roletype = RoleType.Admin;
|
||
|
else if (principal.IsInRole(nameof(RoleType.Teacher)))
|
||
|
roletype = RoleType.Teacher;
|
||
|
else if (principal.IsInRole(nameof(RoleType.Student)))
|
||
|
roletype = RoleType.Student;
|
||
|
|
||
|
var newToken = jwtService.Create(identity);
|
||
|
|
||
|
var result = new IdentityInfo()
|
||
|
{
|
||
|
Token = newToken,
|
||
|
RefreshToken = info.RefreshToken,
|
||
|
Expires = options.Expires,
|
||
|
RealName = realname,
|
||
|
RoleType = roletype
|
||
|
};
|
||
|
|
||
|
return Ok(result);
|
||
|
}
|
||
|
|
||
|
//专用于数据同步
|
||
|
//假设刷新令牌和令牌都有效,模拟登录流程
|
||
|
try
|
||
|
{
|
||
|
var jwt = jwtService.Decode(info.Token);
|
||
|
var name = jwt.Claims.FirstOrDefault(e => e.Type == JwtClaimTypes.Name)?.Value;
|
||
|
|
||
|
if (name is null)
|
||
|
return this.ErrorCode(ErrorCodes.E613);
|
||
|
|
||
|
var user = await dbContext.Users
|
||
|
.AsNoTracking()
|
||
|
.Where(e => e.Name == name)
|
||
|
.FirstOrDefaultAsync();
|
||
|
|
||
|
if (user == default)
|
||
|
return this.ErrorCode(ErrorCodes.E611);
|
||
|
|
||
|
if (!user.Enabled)
|
||
|
return this.ErrorCode(ErrorCodes.E612);
|
||
|
|
||
|
userid = user.Id;
|
||
|
var roleType = user.RoleType;
|
||
|
var realname = user.RealName;
|
||
|
var rolename = default(string);
|
||
|
|
||
|
if (roleType == RoleType.Admin)
|
||
|
rolename = nameof(RoleType.Admin);
|
||
|
else if (roleType == RoleType.Teacher)
|
||
|
rolename = nameof(RoleType.Teacher);
|
||
|
else
|
||
|
rolename = nameof(RoleType.Student);
|
||
|
|
||
|
var claims = new[]
|
||
|
{
|
||
|
new Claim(JwtClaimTypes.Subject, userid),
|
||
|
new Claim(JwtClaimTypes.Name, realname),
|
||
|
new Claim(JwtClaimTypes.Role, rolename),
|
||
|
new Claim(JwtClaimTypes.Role, "Profile")
|
||
|
};
|
||
|
|
||
|
var identity = new ClaimsIdentity(claims);
|
||
|
|
||
|
var token = jwtService.Create(identity);
|
||
|
var refreshToken = Guid.NewGuid().ToString("N");
|
||
|
var options = jwtOptions.CurrentValue;
|
||
|
|
||
|
memoryCache.Set(refreshToken, userid, DateTimeOffset.Now.AddMinutes(options.RefreshExpires));
|
||
|
|
||
|
var result = new IdentityInfo()
|
||
|
{
|
||
|
Token = token,
|
||
|
RefreshToken = refreshToken,
|
||
|
Expires = options.Expires,
|
||
|
UserId = userid,
|
||
|
RealName = realname,
|
||
|
RoleType = roleType
|
||
|
};
|
||
|
|
||
|
return Ok(result);
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
return this.ErrorCode(ErrorCodes.E613);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|