问题描述
我们的项目更换了测试,变得严格的好多,然后我们加重了安全准入,加上之前写的可能不是那么安全,就被查了,然后我们根据问题提供对应的处理方法。
首先遇到的问题就是,水平越权以及垂直越权,其实就是这么看还是蛮抽象的,其实就是举个例子,我目前做的是一个资产明细(按月)查询功能。这个功能大概是角色分为三层,分别是地市,省份,集团,但是呢我们没有完全利用好系统中的性能,导致出现了这个问题。
介绍一下是什么叫做,水平越权以及垂直越权,水平越权的意思是,以我做的功能举例,例如省的角色,山东省的角色,理论上来说,山东省的角色可以看到整个省的数据,山西省的角色,可以看得到山西省的数据,水平越权的意思,就是说山东省的角色,可以通过直接修改省份id查到山西省的角色能够看的数据。垂直越权的问题,就是说山东济南的角色,地市济南这个角色的用户,可以在资产明细(按月)查询到济南的数据,但是查找不到全省的角色,但是通过修改参数,可以使济南的角色查询到山东省的角色能够看的数据。
查询处理
我们想要处理的就是,首先不同的应用增加不同的应用的密码,这个是为了避免垂直越权,不同的角色权限小角色不能够看到权限大角色的数据,然后同一个功能的话,因为是查询,基本上是列表了,然后使用查询条件的初始化的,实话说这块的没有增加额外的新的代码,就是在查询之前,调用页面初始化,判断入参到底是否和角色相关,我们代码这块主要是处理不同应用的密码。
查询加密
/**
* 查询资产明细信息 期间查询
* 地市查询
* @param body
* @return
*/
@PostMapping("/queryViewListPeriodName")
@ApiOperation(value = "明细信息", notes = "明细信息")
@OperateLog("资产信息查询-明细信息")
@RsaDecrypt(msg = "查询失败", isModuleSign = true, moduleName = "AssetDetailsQueryAllRequestBody")
public JSONObject queryViewListPeriodName(@RequestBody AssetDetailsQueryLocalRequestBody body) {
}
注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description
* @Author pgq
* @DATE 2024/8/23 16:52
* @Version 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RsaDecrypt {
String msg() default "无数据访问权限";
boolean isDecrypt() default true;
boolean isModuleSign() default false;
String encryptProperty() default "id";
String moduleName() default "";
}
import cn.chinaunicom.core.base.NowUser;
import cn.chinaunicom.core.utils.encode.RSACoderUtil;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;
/**
* @Description
* @Author pgq
* @DATE 2024/8/20 16:20
* @Version 1.0
*/
@UtilityClass
public class RSACheckUtil {
//私钥
private static String privateKey = "privateKey";
//公钥
private static String publicKey = "publicKey";
/**
* 使用公钥对属性进行加密
*
* @param id
* @param nowUser
* @return
*/
public static String encryptStr(String id, NowUser nowUser) {
String str = nowUser.getStaffId() + "," + nowUser.getRoleId() + "," + id;
byte[] encrypt = RSACoderUtil.encryptByPublicKey(str, publicKey);
return Base64.encodeBase64String(encrypt);
}
/**
* 使用公钥对属性进行加密
*
* @param id
* @return
*/
public static String encryptStr(String id){
byte[] encrypt = RSACoderUtil.encryptByPublicKey(id, publicKey);
return Base64.encodeBase64String(encrypt);
}
/**
* 获取某加密字符串的内容
* @param content
* @return
*/
public static String decryptStr(String content) {
String str = "";
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(content), privateKey);
str = new String(decrypt);
return str;
}
/**
* 获取某加密字符串的内容
* @param content
* @return
*/
public static String decryptStr(String content,NowUser nowUser) {
String str = "";
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(content), privateKey);
str = new String(decrypt);
String[] result = str.split(",");
if (!result[0].equalsIgnoreCase(nowUser.getStaffId().toString())) {
//校验用户 id
return "";
}
if (!result[1].equalsIgnoreCase(nowUser.getRoleId().toString())) {
//校验角色 id
return "";
}
return result[2];
}
/**
* 对当前登录用户的 staffid 和 roleid 、功能模块 生成数据签名
* @param nowUser
* @return
*/
public static String encryptModuleStr(NowUser nowUser,String moduleName) {
String str = nowUser.getStaffId() + "," + nowUser.getRoleId()+","+moduleName;
byte[] encrypt = RSACoderUtil.encryptByPublicKey(str, publicKey);
return Base64.encodeBase64String(encrypt);
}
/**
* 获取加密字符串中的 id
*
* @param encryptStr
* @return
*/
public static String getEncryptStrAttr(String encryptStr, NowUser nowUser) {
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(encryptStr), privateKey);
String descryptStr = new String(decrypt);
String[] result = descryptStr.split(",");
if (!result[0].equalsIgnoreCase(nowUser.getStaffId().toString())) {
//校验用户 id
return "";
}
if (!result[1].equalsIgnoreCase(nowUser.getRoleId().toString())) {
//校验角色 id
return "";
}
return result[2];
}
/**
* 校验操作数据的是否是当前登录用户
*
* @param encryptStr
* @param nowUser
* @param id
* @return
*/
public static boolean checkIsCurrentUser(String encryptStr, NowUser nowUser, String id) {
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(encryptStr), privateKey);
String descryptStr = new String(decrypt);
String[] result = descryptStr.split(",");
if (!result[0].equalsIgnoreCase(nowUser.getStaffId().toString())) {
//校验用户 id
return false;
}
if (!result[1].equalsIgnoreCase(nowUser.getRoleId().toString())) {
//校验角色 id
return false;
}
if (!result[2].equalsIgnoreCase(id)) {
//校验逻辑主键
return false;
}
return true;
}
/**
* 检查是否是当前用户
*
* @param encryptStr
* @param nowUser
* @return
*/
public static boolean checkIsCurrentModule(String encryptStr, NowUser nowUser,String moduleName) {
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(encryptStr), privateKey);
String descryptStr = new String(decrypt);
String[] result = descryptStr.split(",");
if (!result[0].equalsIgnoreCase(nowUser.getStaffId().toString())) {
//校验用户 id
return false;
}
if (!result[1].equalsIgnoreCase(nowUser.getRoleId().toString())) {
//校验角色 id
return false;
}
if (!result[2].equalsIgnoreCase(moduleName)) {
//功能模块
return false;
}
return true;
}
// 待阅已阅校验加密值后返回roleid
public static String checkIsCurrentUserForRoleId(String encryptStr) {
byte[] decrypt = RSACoderUtil.decryptByPrivateKey(Base64.decodeBase64(encryptStr), privateKey);
String descryptStr = new String(decrypt);
String[] result = descryptStr.split(",");
//角色 id
String roleId=result[1];
return roleId;
}
public static void main(String[] args) {
String str = "4";
str = RSACheckUtil.encryptStr(str);
System.out.println(str);
}
}
注意问题
首先保证实体类里面一定要有一个,属性amsRemark
,用来存储加密的串,还有就是如果是多个参数的形式,要求是把存放属性amsRemark
的实体类放到第一个的参数里面,然后当入参是map的时候是不能够处理的,只能够通过方法处理checkIsCurrentUser()
增删改处理
@PostMapping("/saveHeaderInfo")
@ApiOperation(value = "台账-软件新增保存头信息", notes = "台账-软件新增保存头信息")
@OperateLog("台账-软件新增保存头信息")
public Result saveHeaderInfo(TzSoftwareAddEntity entity) {
NowUser nowUser = WebUtil.getNowUser();
boolean flag = RSACheckUtil.checkIsCurrentUser(entity.getAmsRemark(), nowUser, String.valueOf(entity.getSoftwareAddHeaderId()));
if(!flag) {
return failed("新增失败");
}
Map<String, Object> m = new HashMap<>(2);
String billTitle ="";
try{
//解码
billTitle = URLDecoder.decode(entity.getBillTitle(),"UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("exception's message:{}",e.getStackTrace());
}
entity.setBillTitle(billTitle);
final Integer temp = service.saveHeaderInfo(entity);
m.put("billNumber", entity.getBillNumber());
return Result.success(m);
}
类似的代码逻辑
NowUser nowUser = WebUtil.getNowUser();
boolean flag = RSACheckUtil.checkIsCurrentUser(entity.getAmsRemark(), nowUser, String.valueOf(entity.getSoftwareAddHeaderId()));
if(!flag) {
return failed("新增失败");
}
这一块的解密的字符
@Override
public IPage<TzSoftwareAddEntity> queryViewList(TzSoftwareAddRequestBody requestBody){
TzSoftwareAddEntity entity = new TzSoftwareAddEntity();
BeanUtils.copyProperties(requestBody, entity);
NowUser nowUser = WebUtil.getNowUser();
//获取当前登录人的staffId和bgId
entity.nowUserWithAuth(nowUser);
IPage<TzSoftwareAddEntity> result = mapper.queryViewList(requestBody.getPage(), entity);
String amsRemark = "";
for (TzSoftwareAddEntity h : result.getRecords()) {
amsRemark = RSACheckUtil.encryptStr(h.getSoftwareAddHeaderId() + "", nowUser);
h.setAmsRemark(amsRemark);
}
//加密结束
return result;
}
这是对应的加密的代码块,
amsRemark = RSACheckUtil.encryptStr(h.getSoftwareAddHeaderId() + "", nowUser);
h.setAmsRemark(amsRemark);
这是具体的加密的方法
前段的加密的方法
/**
* 校验数据签名
* @param amsRemark
* @param id
* @returns {boolean}
*/
function checkDataSign(amsRemark,id) {
let flag = false;
$.ajax({
async: false,
type: "POST",
url: top.server + "/valueSetController/checkDataSign",
data: {
amsRemark: amsRemark,
id: id
},
dataType: "json",
success: function (res) {
console.log(res);
if (res.status == 200) {
flag = true;
} else {
alertModel(res.msg);
}
}
});
return flag;
}
后台的代码
@PostMapping("/checkDataSign")
public Result checkData(@RequestParam("amsRemark")String amsRemark, @RequestParam("id")String id) {
NowUser nowUser = WebUtil.getNowUser();
boolean flag = RSACheckUtil.checkIsCurrentUser(amsRemark, nowUser, id);
if(flag){
return success("成功");
}
return Result.failed(500, "无访问权限");
}