如何在 Blazor 服务器中安全存储 JWT 令牌?

问题描述 投票:0回答:1

我最近一直在为 Blazor 服务器 (.NET 6) 编写应用程序,并且正在努力解决用户身份验证问题。

我目前写的如下:

  • 我有一个访问令牌和一个刷新令牌,访问令牌的有效期为 1 分钟,刷新令牌的有效期为 14 天。
  • 当访问令牌过期时,应用程序会检查刷新令牌在数据库中是否有效,如果有效,则刷新它并生成新令牌。
  • 两个令牌都存储在本地存储中,我的问题是,我做得对吗?

在 Blazor 中,我见过大多数使用本地存储的实现,但听到了各种声音(将它们保存在安全的 cookie 或内存中(?))。为了使令牌不易受到 csrf、xss 等攻击,最好的方法是什么?我应该将两个令牌放在一处还是以某种方式将它们分开?

我知道在 Blazor 中我可以使用基于

HttpContext
和 cookie 的内置授权。我需要数据库中的令牌才能管理用户会话。

我编写了一个处理用户身份验证的

CustomAuthenticationStateProvider
类。一切正常,但我不知道安全方面是否做得很好。

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
    {
        private readonly Blazored.LocalStorage.ILocalStorageService _localStorage;
        private readonly Application.Interfaces.ITokenService _tokenService;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());

        public CustomAuthenticationStateProvider(Blazored.LocalStorage.ILocalStorageService localStorage, Application.Interfaces.ITokenService tokenService, IHttpContextAccessor httpContextAccessor)
        {
            _localStorage = localStorage;
            _tokenService = tokenService;
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            try
            {
                var userSession = await _localStorage.GetItemAsync<UserSession>("UserSession");
                if (userSession == null)
                    return await Task.FromResult(new AuthenticationState(_anonymous));

                if (!_tokenService.ValidateToken(userSession.AuthToken))
                {
                    if (!_tokenService.ValidateToken(userSession.RefreshToken))
                    {
                        await this.UpdateAuthenticationState(null);
                        return await Task.FromResult(new AuthenticationState(_anonymous));
                    }
                    else
                    {
                        var refreshTokenValidInDb = await _tokenService.CheckIfRefreshTokenIsValid(userSession.RefreshToken);
                        if (refreshTokenValidInDb)
                        {
                            if (_httpContextAccessor.HttpContext == null)
                            {
                                return await Task.FromResult(new AuthenticationState(_anonymous));
                            }

                            var userAgent = this.GetUserAgent(_httpContextAccessor.HttpContext);
                            var ipAddress = this.GetIpAddress(_httpContextAccessor.HttpContext);
                            var (authToken, refreshToken) = await _tokenService.RefreshAuthTokens(userSession.RefreshToken, userAgent, ipAddress);

                            userSession.AuthToken = authToken;
                            userSession.RefreshToken = refreshToken;

                            await _localStorage.SetItemAsync("UserSession", userSession);
                        }
                        else
                        {
                            await this.UpdateAuthenticationState(null);
                            return await Task.FromResult(new AuthenticationState(_anonymous));
                        }
                    }
                }

                var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, userSession.Id.ToString()),
                    new Claim(ClaimTypes.Name, userSession.Name),
                    new Claim("token", userSession.AuthToken),
                    new Claim("refreshToken", userSession.RefreshToken),
                    new Claim("useragent", userSession.UserAgent),
                    new Claim("ipv4", userSession.IPv4)
                }, "Auth"));


                return await Task.FromResult(new AuthenticationState(claimsPrincipal));
            }
            catch
            {
                return await Task.FromResult(new AuthenticationState(_anonymous));
            }
        }

        public async Task UpdateAuthenticationState(UserSession? userSession)
        {
            ClaimsPrincipal claimsPrincipal;

            if (userSession != null)
            {
                await _localStorage.SetItemAsync("UserSession", userSession);

                claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.NameIdentifier, userSession.Id.ToString()),
                    new Claim(ClaimTypes.Name, userSession.Name),
                    new Claim("token", userSession.AuthToken),
                    new Claim("refreshToken", userSession.RefreshToken),
                    new Claim("useragent", userSession.UserAgent),
                    new Claim("ipv4", userSession.IPv4)
                }));
            }
            else
            {
                await _localStorage.RemoveItemAsync("UserSession");
                claimsPrincipal = _anonymous;
            }

            NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
        }

        /// <summary>
        /// Get ip address from HttpContext
        /// </summary>
        /// <param name="httpContext">HttpContext</param>
        /// <returns>Client ip address</returns>
        private string GetIpAddress(HttpContext httpContext)
        {
            var ipNullable = httpContext.Connection.RemoteIpAddress;
            return (ipNullable != null) ? ipNullable.ToString() : "";
        }

        /// <summary>
        /// Get UserAgent from HttpContext
        /// </summary>
        /// <param name="httpContext">HttpContext</param>
        /// <returns>UserAgent</returns>
        private string GetUserAgent(HttpContext httpContext)
        {
            return httpContext.Request.Headers["User-Agent"];
        }

    }
security web cookies jwt blazor
1个回答
2
投票

最后我是这样做的:

  • 生成的访问令牌的有效期为 10 分钟,并存储在 ProtectedLocalStorage 中
  • 每次您在网站上执行操作时,令牌都会刷新
  • 令牌也存储在数据库中并在那里刷新
  • 如果令牌无效或不在数据库中,则用户未登录
© www.soinside.com 2019 - 2024. All rights reserved.