Dotnet 核心 webapi 返回 401 Unauthorized with JWT 身份验证

时间:2021-05-13 01:04:34

标签: javascript asp.net-core .net-core

我在 VS Code 中使用 HTML/Javascript,并在 Visual Studio 2019 中使用 Dot net core 3.1 进行 Web API 开发。同样在 Windows 10 专业版上使用 IIS 来测试 API。

  1. 使用以下代码开发登录页面。一旦用户输入用户名和密码,点击登录按钮,就会调用 Web api“TestAuthService”。

    function fnlogin() {
        const uname = document.getElementById('uname').value;
        const pwd = document.getElementById('pwd').value;

        const logindata = {
            username: uname,
            password: pwd
        }
        const loginurl = 'http://localhost:8091/api/Auth/Login';
        authenticate(loginurl, logindata);        
    }
        
    async function authenticate(loginurl, logindata) {
        console.log(logindata)
        const response = await fetch(loginurl , {
            method: "POST",
            mode: "cors",
            body: JSON.stringify(logindata),
            headers: { "Content-type" : "application/json, charset=UTF-8"}
        });
        const rdata = await response.json();
        console.log(rdata);
        if (!rdata.success) {
            document.getElementById("loginMessage").innerHTML = rdata.message;
            return;
        }

        const inMemoryToken = rdata.data
        localStorage.setItem('user', JSON.stringify(rdata));
        window.location.href = "http://127.0.0.1:5500/Weatherinfo.html";

    }
  1. API TestAuthService 在 IIS 中的 localhost:8091 上发布。成功登录后,JWT 将返回给 javascript。这工作正常。 JWT 通过 Javascript 存储在 Chrome 浏览器的 localStorage 中。 auth 控制器代码如下:
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly IAuthRepository _authRepo;
        public AuthController(IAuthRepository authRepo)
        {
            _authRepo = authRepo;

        }

        [HttpPost("Register")]
        public async Task<ActionResult<ServiceResponse<int>>> Register(UserRegisterDto request)
        {
            var response = await _authRepo.Register(
                new User { Username = request.Username }, request.Password
            );

            if (!response.Success)
            {
                return BadRequest(response);
            }

            return Ok(response);
        }

        [HttpPost("Login")]
        public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
        {
            var response = await _authRepo.Login(
                request.Username, request.Password
            );

            if (!response.Success)
            {
                return BadRequest(response);
            }

            return Ok(response);
        }

AuthRepository 代码如下:

    public class AuthRepository : IAuthRepository
    {
        private readonly AppDbContext _context;
        private readonly IConfiguration _configuration;
        public AuthRepository(AppDbContext context, IConfiguration configuration)
        {
            _configuration = configuration;
            _context = context;

        }

        public async Task<ServiceResponse<string>> Login(string username, string password)
        {
            var response = new ServiceResponse<string>();
            var user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
            if (user == null)
            {
                response.Success = false;
                response.Message = "User not found.";
            }
            else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
            {
                response.Success = false;
                response.Message = "Wrong password.";
            }
            else
            {
                response.Data = CreateToken(user);
            }

            return response;
        }

        public async Task<ServiceResponse<User>> Register(User user, string password)
        {
            ServiceResponse<User> response = new ServiceResponse<User>();
            if (await UserExists(user.Username))
            {
                response.Success = false;
                response.Message = "User already exists.";
                return response;
            }

            CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);

            user.PasswordHash = passwordHash;
            user.PasswordSalt = passwordSalt;

            _context.Users.Add(user);
            await _context.SaveChangesAsync();
            response.Data = user;
            return response;
        }

        public async Task<bool> UserExists(string username)
        {
            if (await _context.Users.AnyAsync(x => x.Username.ToLower().Equals(username.ToLower())))
            {
                return true;
            }
            return false;
        }

        private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512())
            {
                passwordSalt = hmac.Key;
                passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            }
        }

        private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
        {
            using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
            {
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != passwordHash[i])
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        private string CreateToken(User user)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username)
            };

            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));

            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

            var tokendDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = System.DateTime.Now.AddDays(1),
                SigningCredentials = creds
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokendDescriptor);

            return tokenHandler.WriteToken(token);
        }
  1. 如果登录成功,将显示页面 Weatherinfo.html,此页面有按钮“获取天气数据”,单击该按钮会调用另一个 Web api“weatherforecast”
    <button type="button" onclick="return getWeather();">Get Weather data</button>

    <table id="weatherdata">
        <thead>

        </thead>
        <tbody id="weatherdatalist">

        </tbody>
    </table>

    <script>

        async function getWeather() {
            const url = 'http://localhost:5861/weatherforecast';
            const localstorage_user = JSON.parse(localStorage.getItem('user'));
            const inMemToken = localstorage_user.data
            console.log(inMemToken)

            /*
            const response = await fetch(url);
            */
            const response = await fetch(url, {
                headers: {
                    Authorization: 'Bearer ${inMemToken}'
                }
            });
            
            const data = await response.json();
            console.log(data)

            display(data);
        }

        function display(data) {
            let tab = "";

            data.forEach(element => {
                tab += `<tr> 
                    <td > ${element.date} </td>
                    <td> ${element.temperatureC} </td>
                    <td> ${element.temperatureF} </td>
                    <td> ${element.summary} </td>
                    </tr>`;
            });
            document.getElementById("weatherdatalist").innerHTML = tab;
        }
        


    </script>
  1. weatherforecast api 有以下代码

创业班

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(o => o.AddPolicy("AllowOrigins", builder =>
            {
                builder.WithOrigins("http://localhost:5500", "http://127.0.0.1:5500")
                 .AllowAnyMethod()
                 .AllowAnyHeader();
            }));

            services.AddControllers();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = false,
                        ValidateIssuer = false,
                        ValidateAudience = false

                    };

                });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAuthentication();
            app.UseRouting();
            
            app.UseCors("AllowOrigins");

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

控制器如下

   [ApiController]
    [Route("[controller]")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

  1. 问题是weatherforcast api 失败并显示HTTP 错误401(未经授权)

我不确定我是否在下面的代码中正确传递了 JWT

 const response = await fetch(url, {
            headers: {
                Authorization: 'Bearer ${inMemToken}'
            }
        });

1 个答案:

答案 0 :(得分:0)

问题现在似乎已解决。 将 fetch api 调用更正为

            const response = await fetch(url, {
                headers: {
                    "Authorization": `Bearer ${inMemToken}`
                }
            });

``