自定义注解实现身份校验和权限校验

背景

最近在项目测试中系统出现平行越权和垂直越权漏洞,修复漏洞需要对用户的请求进行身份校验和权限校验,由于系统设计的时候就没有考虑权限的问题,找了一个临时的方案,自定义一个注解来进行权限校验。

实现逻辑

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实现的权限校验注解功能,这样实现方式非常粗糙,后续需要进行优化,甚至需要对系统权限管理进行重构,如要使用请谨慎考虑!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要定义一个自定义注解 `@RequiresPermissions`,用于标识需要授权访问的方法,例如: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiresPermissions { String[] value(); // 权限值 } ``` 然后,我们需要实现一个切面,用于拦截被 `@RequiresPermissions` 标识的方法,并进行权限校验,例如: ```java @Component @Aspect public class PermissionCheckAspect { @Autowired private AuthService authService; @Around("@annotation(requiresPermissions)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions) throws Throwable { // 获取当前用户 User user = authService.getCurrentUser(); if (user == null) { throw new UnauthorizedException("用户未登录"); } // 获取当前用户的权限列表 List<String> permissions = authService.getUserPermissions(user); // 校验权限 for (String permission : requiresPermissions.value()) { if (!permissions.contains(permission)) { throw new ForbiddenException("没有访问权限:" + permission); } } // 执行目标方法 return joinPoint.proceed(); } } ``` 在切面中,我们首先通过 `AuthService` 获取当前用户及其权限列表,然后校验当前用户是否拥有被 `@RequiresPermissions` 标识的方法所需的所有权限,如果没有则抛出 `ForbiddenException` 异常,如果有则继续执行目标方法。 最后,我们需要在 Spring 配置文件中启用 AOP 自动代理,并扫描切面所在的包,例如: ```xml <aop:aspectj-autoproxy /> <context:component-scan base-package="com.example.aspect" /> ``` 这样,我们就通过 Spring AOP 和自定义注解模拟实现了类似 Shiro 权限校验的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值