背景
最近在项目测试中系统出现平行越权和垂直越权漏洞,修复漏洞需要对用户的请求进行身份校验和权限校验,由于系统设计的时候就没有考虑权限的问题,找了一个临时的方案,自定义一个注解来进行权限校验。
实现逻辑
1.定义校验注解信息,使用注解并采用aop进行身份校验和权限校验逻辑实现
2.身份校验:每次请求都会带有权限令牌和cookie,需要校验权限中的用户是否和cookie的用户一致
3.权限校验:获取用户拥有的所有权限,校验当前接口是否包含在用户权限范围内
注解定义
定义一个命名为CheckAuth的注解,注解内容包含一个命名为value的属性,属性默认为空字符串,后续需要在value中填写接口所支持的菜单权限信息
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author 莫须有
* @Description 校验权限注解
*/
@Target({METHOD})
@Retention(RUNTIME)
@Documented
public @interface CheckAuth {
String value() default "";
}
注解运用
在需要进行权限校验的接口上添加@CheckAuth()注解,注解中可填写value属性来定义注解支持的权限,下面示例中当接口被请求,将会校验请求用户是否包含datamanage:asset:myasset和datamanage:asset:assetcenter权限
@RequestMapping(value = "/get/table/list", method = RequestMethod.GET)
@CheckAuth("datamanage:asset:myasset,datamanage:asset:assetcenter")
public List<BaseTableListDTO> getTableList(@ApiIgnore OAuth2Authentication authentication,
@RequestParam String typeId,
@RequestParam String moduleFlag) {
String userId = authentication.getName();
return resourceManagerService.getTableList(userId, typeId, moduleFlag);
}
校验逻辑实现
使用aop对使用@CheckAuth注解的接口进行增强,添加接口的权限校验
校验分为两部分,第一部分会校验用户身份信息,分别从令牌和cookie中获取用户信息进行一致性校验,第二部分校验用户权限是否包含接口所拥有的权限
import com.xasj.common.base.CommonHttpStatusEnum;
import com.xasj.common.base.GlobalException;
import com.xasj.dubbo.system.manage.interfaces.ISystemManageService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import java.util.List;
@Component
@Aspect
@Slf4j
public class PermissionAspect {
@Reference(check = false, group = "kg-system-manage")//消费端初始化时不检查服务端状态
private ISystemManageService systemManageService;
@Around("@annotation(checkAuth)")
public Object checkPermission(ProceedingJoinPoint joinPoint, CheckAuth checkAuth) throws Throwable {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 从权限信息中获取用户id信息
String userId = "";
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication oauth = (OAuth2Authentication) authentication;
userId = oauth.getName();
}
// 获取请求cookie信息
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Cookie[] cookies = sra.getRequest().getCookies();
// 从cookie中获取用户id信息
boolean checked = true;
for (Cookie cookie : cookies) {
if("userId".equals(cookie.getName())){
checked = userId.equals(cookie.getValue());
}
}
// 校验用户信息是否一致
if (!checked){
throw new GlobalException("身份信息校验失败", CommonHttpStatusEnum.NO_OPERATE_AUTH.getCode());
}
// 获取用户所有的权限信息
List<String> oauths = systemManageService.getUserAuths(userId);
// 获取接口支持的权限信息
String value = checkAuth.value();
String[] split = value.split(","); // 接口允许的权限
// 校验接口权限是否包含在用户权限中
boolean hasAuth = false;
for (String s : split) {
if (oauths.contains(s)){
hasAuth = true;
break;
}
}
if (!hasAuth){
throw new GlobalException("没有操作权限", CommonHttpStatusEnum.NO_OPERATE_AUTH.getCode());
}
// 校验完成接口放行
Object result = joinPoint.proceed();
return result;
}
}
总结
通过以上步骤就完成了一个简单的通过AOP实现的权限校验注解功能,这样实现方式非常粗糙,后续需要进行优化,甚至需要对系统权限管理进行重构,如要使用请谨慎考虑!