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 { /// /// 帐号控制器。 /// [Produces("application/json")] [Route("api/[controller]")] [ApiVersion("1.0")] //[Authorize(Roles = "Profile")] [ApiController] public class AccountController : ControllerBase { private readonly IOptionsMonitor jwtOptions; private readonly IJwtService jwtService; private readonly IMemoryCache memoryCache; private readonly DriveDbContext dbContext; public AccountController(IJwtService jwtService, IOptionsMonitor jwtOptions, IMemoryCache memoryCache, DriveDbContext dbContext) { this.jwtService = jwtService; this.jwtOptions = jwtOptions; this.memoryCache = memoryCache; this.dbContext = dbContext; } /// /// 登录系统。 /// /// 角色类型 /// 登录信息 /// //[ProducesResponseType(ErrorCodes.E600)] //[ProducesResponseType(ErrorCodes.E611)] //[ProducesResponseType(ErrorCodes.E612)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status200OK)] [AllowAnonymous] [HttpPost("SignIn")] public async Task> 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); } /// /// 登出系统。 /// /// [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status200OK)] [HttpPost("SignOut")] public Microsoft.AspNetCore.Mvc.ActionResult AccountSignOut() { //TODO: 把 JWT 放入黑名单,其它地方则验证黑名单,将来再处理 return Ok(); } /// /// 修改密码。 /// /// 修改密码信息 //[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 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(); } /// /// 刷新令牌。 /// //[ProducesResponseType(ErrorCodes.E613)] //[ProducesResponseType(ErrorCodes.E614)] [ProducesResponseType(StatusCodes.Status200OK)] [AllowAnonymous] [HttpPost("[action]")] public async Task> RefreshToken([FromBody] RefreshTokenInfo info) { if (string.IsNullOrEmpty(info.RefreshToken) || string.IsNullOrEmpty(info.Token)) return this.ErrorCode(ErrorCodes.E613); //校验缓存中是否有该刷新令牌 if (memoryCache.TryGetValue(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); } } } }