You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现对他人拥有资源的安全匿名访问?附实现思路与代码示例

如何实现对他人拥有资源的安全匿名访问?附实现思路与代码示例

针对你提出的「用户创建资源后,能通过邮件分享给好友,好友无需注册就能匿名安全访问该特定资源」这个场景,确实没有特别通用的“标准Web模式”,不过你的初始思路——用ASP.NET Core的数据保护功能加密资源相关信息——是非常合理且安全的方案。下面我来梳理下完整的实现逻辑和优化后的代码示例:

核心实现思路

  1. 加密访问令牌:使用IDataProtectionProvider加密包含「资源ID + 资源所有者ID」的信息,生成无法被篡改的共享令牌,将其放入共享链接(比如/shared-resource/[加密后的令牌])
  2. 分离权限逻辑:用两个控制器分别处理「登录用户的资源管理」和「匿名用户的共享访问」,避免权限逻辑混杂
  3. 统一业务逻辑:把资源的核心业务逻辑抽离到独立服务类中,让两个控制器复用同一套业务规则,减少代码冗余

完整代码实现

1. 共享的资源业务服务类

先把资源的核心操作抽离到服务类,让登录用户控制器和匿名访问控制器复用同一套业务规则:

public class PersonIdentity
{
    public int PersonId { get; }

    // 从Claims初始化(登录用户场景)
    public PersonIdentity(IEnumerable<Claim> claims)
    {
        // 从JWT Claims中解析用户ID,需和登录时的Claim配置一致
        PersonId = int.Parse(claims.First(c => c.Type == "PersonId").Value);
    }

    // 从共享令牌信息初始化(匿名访问场景)
    public PersonIdentity(int personId)
    {
        PersonId = personId;
    }
}

public class ResourceService
{
    private readonly IDataAccess _dataAccess;

    public ResourceService(IDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }

    // 获取资源:同时支持登录用户和匿名共享场景的权限校验
    public ResourceDto GetResource(int resourceId, PersonIdentity identity)
    {
        var resource = _dataAccess.GetResource(resourceId);
        
        // 核心校验:确保访问者是资源所有者(或该资源已被所有者共享)
        if (resource.OwnerId != identity.PersonId)
        {
            throw new UnauthorizedAccessException("无权访问该资源");
        }

        return ResourceDto.ToDto(resource);
    }

    // 登录用户专属的资源操作(比如修改、删除)
    public void UpdateResource(int resourceId, PersonIdentity identity, ResourceUpdateDto updateDto)
    {
        var resource = _dataAccess.GetResource(resourceId);
        if (resource.OwnerId != identity.PersonId)
        {
            throw new UnauthorizedAccessException("无权修改该资源");
        }

        _dataAccess.UpdateResource(resource, updateDto);
    }
}

2. 登录用户的资源管理控制器

处理登录用户的资源查询、修改、生成共享链接等操作,需通过[Authorize]验证身份:

[Authorize]
public class ResourceController : ControllerBase
{
    private readonly ResourceService _resourceService;
    private readonly IDataAccess _dataAccess;

    public ResourceController(ResourceService resourceService, IDataAccess dataAccess)
    {
        _resourceService = resourceService;
        _dataAccess = dataAccess;
    }

    // 登录用户查询自己的资源
    [HttpGet("resources/{id:int}")]
    public IActionResult GetResource(int id)
    {
        var identity = new PersonIdentity(User.Claims);
        try
        {
            var resourceDto = _resourceService.GetResource(id, identity);
            return Ok(resourceDto);
        }
        catch (UnauthorizedAccessException)
        {
            return Forbid();
        }
    }

    // 登录用户修改自己的资源
    [HttpPut("resources/{id:int}")]
    public IActionResult UpdateResource(int id, [FromBody] ResourceUpdateDto updateDto)
    {
        var identity = new PersonIdentity(User.Claims);
        try
        {
            _resourceService.UpdateResource(id, identity, updateDto);
            return NoContent();
        }
        catch (UnauthorizedAccessException)
        {
            return Forbid();
        }
    }

    // 生成资源共享链接(仅资源所有者可操作)
    [HttpGet("resources/{id:int}/generate-share-link")]
    public IActionResult GenerateShareLink(int id, [FromServices] IDataProtectionProvider dataProtectionProvider)
    {
        var identity = new PersonIdentity(User.Claims);
        // 先校验当前用户是资源所有者
        var resource = _dataAccess.GetResource(id);
        if (resource.OwnerId != identity.PersonId)
        {
            return Forbid();
        }

        // 加密「资源ID + 所有者ID」生成共享令牌
        var protector = dataProtectionProvider.CreateProtector("ResourceShareToken");
        var shareToken = protector.Protect(JsonSerializer.Serialize(new ShareToken
        {
            ResourceId = id,
            OwnerId = identity.PersonId
        }));

        // 生成完整的共享链接(可根据前端路由调整格式)
        var shareUrl = Url.Action("GetSharedResource", "SharedResource", new { token = shareToken }, Request.Scheme);
        return Ok(new { ShareUrl = shareUrl });
    }
}

// 用于加密的共享令牌模型
public class ShareToken
{
    public int ResourceId { get; set; }
    public int OwnerId { get; set; }
}

3. 匿名访问的共享资源控制器

处理匿名用户的共享资源访问,无需身份验证,但需解密令牌并验证权限:

[AllowAnonymous]
public class SharedResourceController : ControllerBase
{
    private readonly ResourceService _resourceService;
    private readonly IDataProtectionProvider _dataProtectionProvider;

    public SharedResourceController(ResourceService resourceService, IDataProtectionProvider dataProtectionProvider)
    {
        _resourceService = resourceService;
        _dataProtectionProvider = dataProtectionProvider;
    }

    [HttpGet("shared-resource")]
    public IActionResult GetSharedResource(string token)
    {
        try
        {
            // 解密共享令牌
            var protector = _dataProtectionProvider.CreateProtector("ResourceShareToken");
            var shareToken = JsonSerializer.Deserialize<ShareToken>(protector.Unprotect(token));

            // 用令牌中的所有者ID创建身份,调用共享资源服务
            var identity = new PersonIdentity(shareToken.OwnerId);
            var resourceDto = _resourceService.GetResource(shareToken.ResourceId, identity);
            return Ok(resourceDto);
        }
        catch (CryptographicException)
        {
            // 令牌无效或被篡改
            return BadRequest("无效的共享链接");
        }
        catch (UnauthorizedAccessException)
        {
            // 令牌信息与资源实际权限不匹配
            return Forbid("无权访问该资源");
        }
    }
}

可选优化点

  1. 令牌过期机制:在ShareToken中添加ExpiryTime字段,加密时设置过期时间,解密时校验,避免链接永久有效
  2. 共享状态记录:在资源表中添加IsShared字段或单独建共享记录表,生成令牌时标记资源为可共享,访问时校验状态
  3. 访问次数限制:给每个共享令牌添加唯一标识,记录访问次数,防止链接被滥用

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:00:30