接口安全规范

为了提高接口安全,在编写对外接口时候,特别接口安全的控制,否则很容易出现下面的漏洞。

漏洞名称 漏洞危害 开发建议
越权绕过 攻击者可能使用低权限用户请求高权限的接口,如用户添加按钮。 后端接口上添加权限控制注解,如: @RequiresPermissions("权限标记")
文件上传 很多网站提供文件上传功能(包括图片上传),如果在服务器端没有对上传文件的类型、大小以及保存的路径及文件名进行严格限制,攻击者就很容易上传后门程序取得WebShell,从而控制服务器。 前端和后端使用黑名单、白名单严格限制上传文件的类型和大小。

功能权限检查

越权漏洞主要需要后端解决权限控制的问题,就需要解决登录控制和接口鉴权

  • 登录控制,是逻辑上的细节控制,比如 单设备登录、登录环境检测、登录异常检测
  • 接口鉴权根据不同的框架,有不同的出来方式

Shiro 框架

接口注解 权限描述
@RequiresAuthentication 要求当前Subject已经在session中验证通过(要求当前用户认证通过:subject.isAuthenticated() 结果为true)
@RequiresUser 验证用户是否被记忆,user有两种含义:
1) 一种是成功登录的(subject.isAuthenticated() 结果为true);
2) 另外一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest 用户没有登录认证或被记住过,验证是否是一个guest的请求,与@RequiresUser完全相反。换言之,RequiresUser == !RequiresGuest。此时subject.getPrincipal() 结果为null.
@RequiresPermissions 验证用户是否具有一个或多个权限,该注解将会经常在项目中使用,如果不满足条件则抛出AuthorizationException异常
1) 是否具有某一权限 @RequiresPermission(“account:create”)
2) 是否具有多个权限 @RequiresPermission({“account:create”,”account:update”})
@RequiresRoles 验证当前用户是否具有某角色,与验证权限类似
@RequiresRoles("super")
public string addAdmin(String username,String pwd){
    return adminService.addAdmin(username,pwd);
}

Spring Security 框架

接口注解 权限描述
@PreAuthorize(“authenticated”) 要求当前用户认证通过
@PreAuthorize(“authenticated and hasAuthority(‘权限标记’) “) 要求当前用户认证通过,且具备指定的权限
@PreAuthorize(“authenticated and hasAnyAuthority(‘权限标记’,’*’) “) 要求当前用户认证通过,且具备指定的任意权限(管理员权限通常是*)
@PreAuthorize(“authenticated and hasRole(‘角色编码1’) “) 要求当前用户认证通过,且具备指定的角色
@PreAuthorize(“authenticated and hasAnyRole(‘角色编码1’,’角色编码2’) “) 要求当前用户认证通过,且具备指定的任意角色

数据权限检查

数据越权漏洞的核心点在,使用低权限账号可以通过接口获取到不属于自己权限的数据,要求解决这个问题需要从两个方面进行控制。

  • 请求参数检查,在 增删改查接口 增加数据权限的校验,防止用户操作非自己权限的数据
  • 返回数据过滤,此方式依赖数据权限功能去给指定角色、用户设置某些数据对象的访问范围,以便在数据返回时通过动态修改筛选条件,达到数据过滤的目的。
请求参数检查

访问参数检查注解

接口注解 权限描述
@RequiresDataPermissions(dataType = DataType.SCHOOL, message = “无权限操作该学校数据”) 要求学校代码字段进行数据权限检查
/**
 * 学校代码
 */
@ApiModelProperty(value = "学校代码", required = true)
@NotBlank(message = "学校代码不能为空")
@RequiresDataPermissions(dataType = DataType.SCHOOL, message = "无权限操作该学校数据")
private String schoolCode;

而服务内部有实现数据检查的本地逻辑实现:

/**
 * 数据权限提供者
 */
@Component("dataScopeProvider")
public class MyDataScopeProvider implements DataScopeProvider {

    @Override
    public boolean hasPermissions(DataType dataType, Object data) {
        String xxdm = SubjectUtils.getProfileString(UserProfiles.XXDM);
        if (Objects.requireNonNull(dataType) == DataType.SCHOOL) {
            return Objects.toString(data).equals(xxdm);
        }
        return Boolean.TRUE;
    }

}

“文件上传” 检查

文件上传漏洞就需要前端和后端使用黑名单、白名单严格限制上传文件的类型和大小。

  • 前端控制,根据业务需求,指定上传文件格式、大小限制
  • 后端控制,在上传接口添加文件类型检测、大小限制
  • 运维控制,在 Nginx 负载均衡限制上传文件大小

前端控制

前端需要在文件上传组件增加上传之前的回调夯实,进行文件格式、大小的校验!

handleBeforeUpload (file) {
    // 控制的文件类型
    let allFileType = ['xls', 'doc', 'zip', 'rar', 'xlsx', 'docx', 'jpg', 'png', 'bmp']
    const isSuccessFile = allFileType.includes(file.name.split('.')[1])
    const is50M = file.size / 1024 / 1024 < 50;

    if (!isSuccessFile) {
        Message.error('请上传正确的文件格式!');
    }
    //限制文件上传大小
    if (!is50M) {
        Message.error("上传文件大小不能超过 50MB!");
    }
    return isSuccessFile && is50M;
},

后端控制:Jakarta Bean Validation API

后端扩展了基于 jsr303 校验规范的文件校验实现,实现文件格式、大小、mine类型的校验!

属性注解 校验描述 示例
@FileNotEmpty 文件非空校验 @FileNotEmpty(required = true, message = “上传文件必传”)
@FileNotEmpty 文件格式校验 @FileNotEmpty(required = true, extensions = {“zip”, “rar”},message = “仅支持rar、zip两种格式压缩包格式”)
@FileNotEmpty 文件大小校验 @FileNotEmpty(required = true, maxSize = “100MB”, message = “文件大小不得超过100MB。”)
@FileNotEmpty 文件非空、格式、大小校验 @FileNotEmpty(required = true, extensions = {“zip”, “rar”}, maxSize = “100MB”, message = “仅支持rar、zip两种格式压缩包,且文件大小不得超过100MB。”)
@FileNotEmpty 文件Mime校验 @FileNotEmpty(required = true, mimeTypes = {“application/x-rar-compressed”, “application/zip”},message = “仅支持 application/x-rar-compressed、application/zip 的MIME类型”)
@ApiModel(value = "FileUploadDTO", description = "数据传输对象")
@Data
public class FileUploadDTO {

    @ApiModelProperty(value = "上传文件")
    @FileNotEmpty(required = true, message = "上传文件必传")
    private MultipartFile file1;

    @ApiModelProperty(value = "上传文件")
    @FileNotEmpty(required = true, extensions = {"zip", "rar"}, message = "仅支持rar、zip两种格式压缩包格式")
    private MultipartFile file2;

    @ApiModelProperty(value = "上传文件")
    @FileNotEmpty(required = true, maxSize = "100MB", message = "文件大小不得超过100MB。")
    private MultipartFile file3;

    @ApiModelProperty(value = "上传文件")
    @FileNotEmpty(required = true, extensions = {"zip", "rar"}, maxSize = "100MB", message = "仅支持rar、zip两种格式压缩包,且文件大小不得超过100MB。")
    private MultipartFile file4;

    @ApiModelProperty(value = "上传文件")
    @FileNotEmpty(required = true, mimeTypes = {"application/x-rar-compressed", "application/zip"},message = "仅支持 application/x-rar-compressed、application/zip 的MIME类型")
    private MultipartFile file5;

}

Nginx上传文件大小限制

Nginx默认的上传文件大小是有限制的,一般为2MB,如果你要上传的文件超出了这个值,将可能上传失败。

如果要修改这个大小限制,只需要打开nginx.conf文件。

vim /etc/nginx/nginx.conf

找到”client_max_body_size”,如果没有的话,在http部分加上它。设置你想要修改的大小,例如:

client_max_body_size 30M

然后重启Nginx使配置生效即可。

service restart nginx
作者:Jeebiz  创建时间:2022-11-07 15:11
最后编辑:Jeebiz  更新时间:2024-11-14 21:58