11Web API安全措施(一)后端 - ASP.NET Core MVC


前言

Web API安全措施的主要流程:
在这里插入图片描述
角色主要有三类:应用程序、授权机构、资源(包含Web API)
安全措施的流程:注册信息、身份验证、创建Token、验证Token、授权

从图中可以看出,首先会在授权机构中进行身份信息注册(保存到数据库),然后应用程序将身份信息发送给授权机构。身份信息通过后,授权机构会返回Token给应用程序,应用程序请求资源时将Token一起发送,资源会通过授权机构验证Token,通过后会将资源返回给应用程序,从而实现Web API的安全。


以下是Web API安全措施的具体实现

一、注册信息

通过界面等方式将信息注册保存到数据库中,此处直接硬编码将信息写到程序里(省略注册的具体实现)。

定义程序类(代表数据库中保存的注册信息):

public class Apllication
{
    public int ApplicationId { get; set; }
    public string? ApplicationName { get; set; }
    public string? ClientId { get; set; }
    public string? Secret { get; set; }
    public string? Scopes { get; set; }
}

硬编码的注册信息:

public static class AppRepositry
{
    private static List<Apllication> _applications = new List<Apllication>()
    {
        new Apllication
        {
            ApplicationId = 1,
            ApplicationName = "Test",
            ClientId = "123",
            Secret = "admin",
            Scopes = "read,write"
        }
    };

    public static Apllication? GetApllicationByClientId(string clientId) 
    {
        return _applications.FirstOrDefault(x => x.ClientId == clientId);
    }

}

二、身份验证

应用程序向授权机构发送请求,验证id和密码是否正确,若正确则返回Token。
请求方法:

[HttpPost("auth")]
public IActionResult Authenticate([FromBody]AppCredential credential)
{
    var expiresAt = DateTime.UtcNow.AddMinutes(10);
    if (Authenticator.Authenticate(credential.ClientId, credential.Secret))
    {
        return Ok(new
        {
            access_token = Authenticator.CreateToken(credential.ClientId, expiresAt, configuration.GetValue<string>("SecretKey")),
            expires_at = expiresAt
        });
    }
    else
    {
        ModelState.AddModelError("Unauthorized", "You are not authorized.");
        var problemDetails = new ValidationProblemDetails(ModelState)
        {
            Status = StatusCodes.Status401Unauthorized
        };
        return new UnauthorizedObjectResult(problemDetails);
    }
}

验证:

public static bool Authenticate(string clientId, string secret)
{
    var app = AppRepositry.GetApllicationByClientId(clientId);
    if (app == null)  return false;

    return (app.ClientId == clientId && app.Secret == secret);
}

三、创建Token

在进行身份验证时,就会创建Token,可以进行自定义Token(需要将token保存到数据库中,和数据库进行交互),这里使用JWT(不需与数据库交互)。

3.1 安装依赖

在Nuget安装System.IdentityModel.Tokens.Jwt
在这里插入图片描述

3.2 代码实现

JWT由算法、负载、签证三部分组成。算法就是加密算法(使用secretKey),负载就是封装的键值对信息,签证就是加密后的标识(服务端用来比对是否篡改过前两部分信息)。

secretKey在配置文件appsettings.json中,程序通过IConfiguration直接获取
在这里插入图片描述

代码如下:

public static object CreateToken(string clientId, DateTime expiresAt, string strSecretKey)
{
    //算法
    //负载
    //签证

    var secretKey = Encoding.ASCII.GetBytes(strSecretKey);

    var app = AppRepositry.GetApllicationByClientId(clientId);
    var claims = new List<Claim>() {
        new Claim("AppName", app?.ApplicationName??string.Empty),
        new Claim("Read", (app?.Scopes??string.Empty).Contains("read")?"true":"false"),
        new Claim("Write", (app?.Scopes??string.Empty).Contains("write")?"true":"false")
    };

    var jwt = new JwtSecurityToken(
        signingCredentials: new SigningCredentials(
            new SymmetricSecurityKey(secretKey),
            SecurityAlgorithms.HmacSha256Signature),
        claims: claims,
        expires: expiresAt,
        notBefore: DateTime.UtcNow
        );

    return new JwtSecurityTokenHandler().WriteToken(jwt);
}

3.3 接口访问

直接使用id和密码访问认证接口,返回token
在这里插入图片描述

3.4 解析Token

打开https://jwt.io/网站,输入token信息,能看到负载信息。
在这里插入图片描述

四、验证Token

4.1 代码实现

验证Token代码如下:

public static bool VerifyToken(string token, string strSecretKey)
{
    if (string.IsNullOrWhiteSpace(token)) return false;

    var secretKey = Encoding.ASCII.GetBytes(strSecretKey);
    SecurityToken securityToken;

    try
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        tokenHandler.ValidateToken(token, new TokenValidationParameters()
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(secretKey),
            ValidateLifetime = true,
            ValidateAudience = false,
            ValidateIssuer = false,
            ClockSkew = TimeSpan.Zero
        }, out securityToken);
    }
    catch (SecurityException)
    {
        return false;
    }
    catch (Exception)
    {
        throw;
    }

    return securityToken != null;
}

4.2 授权筛选器

使用授权筛选器应用Token验证

public class JwtTokenAuthFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
    public async Task OnAuthorizationAsync(AuthorizationFilterContext filterContext)
    {
        if (!filterContext.HttpContext.Request.Headers.TryGetValue("Authorization", out var token))
        {
            filterContext.Result = new UnauthorizedResult();
            return;
        }

        var configuration = filterContext.HttpContext.RequestServices.GetService<IConfiguration>();

        if (!Authenticator.VerifyToken(token, configuration.GetValue<string>("SecretKey")))
        {
            filterContext.Result = new UnauthorizedResult();
        }
    }
}

使用属性特性启动授权筛选器
在这里插入图片描述

4.3 功能验证

使用Postman在header里加上Authorization: token(jwt有时间限制)
在这里插入图片描述

不加token
在这里插入图片描述

五、授权

从JWT中获取负载信息(claim),将属性特性标注到属性上,在授权筛选器中比较属性信息和负载信息从而实现权限控制。

5.1 新建属性类

新建声明权限类

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true )]
public class RequiredClaimAttribute : Attribute
{
    public string ClaimType { get; }
    public string ClaimValue { get; }

    public RequiredClaimAttribute(string claimType, string claimValue)
    {
        ClaimType = claimType;
        ClaimValue = claimValue;
    }
}

5.2 使用属性类

在控制器方法中声明权限属性值
在这里插入图片描述

5.3 判断权限

在授权筛选器中获取出属性特性中声明的权限,再从token里获取出负载信息里的权限,然后比较属性值。

public async Task OnAuthorizationAsync(AuthorizationFilterContext filterContext)
{
    if (!filterContext.HttpContext.Request.Headers.TryGetValue("Authorization", out var token))
    {
        filterContext.Result = new UnauthorizedResult();
        return;
    }

    var configuration = filterContext.HttpContext.RequestServices.GetService<IConfiguration>();

    var claims = Authenticator.VerifyToken(token, configuration.GetValue<string>("SecretKey"));
    if (claims == null) 
    {
        filterContext.Result = new UnauthorizedResult(); //401
    }
    else
    {
        var requiredClaims = filterContext.ActionDescriptor.EndpointMetadata.OfType<RequiredClaimAttribute>().ToList();

        // 403
        if (requiredClaims != null && !requiredClaims.All(rc=>claims.Any(c=>c.Type.ToLower()==rc.ClaimType.ToLower() &&
        c.Value.ToLower()==rc.ClaimValue.ToLower())))
        {
            filterContext.Result = new StatusCodeResult(403);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值