重写Shiro,拦截RequiresPermissions权限标识获取逻辑,实现Controller多级权限拼接

业务场景:
Shiro权限校验
RequiresPermissions标签是优先获取方法上的注解信息,再从类上注解获取权限标识符
但是系统的 XxxController 层是继承的 BaseController,增删改查方法,在BaseController中,无法对增删改查的 RequiresPermissions 权限标签进行自定义

逻辑分析:
1、aop 切面拦截 controller 对应调用的方法
2、获取对应方法的注解信息
3、使用对应的解析器解析获取到的注解信息
①、获取方法注解信息
②、获取类注解信息
③、返回注解信息
4、从注解获取权限标识符

根据以下代码分析,从注解解析器入手,进行拦截处理

实现效果:

支持 controller类上注解 和 method 上 RequiresPermissions 注解的value值进行拼接
     *@RequiresPermissions("customer:info:")* XxController{
     *@RequiresPermissions("save")* 		public void save() {
     * 		}
     * }
     * 最终权限标识符 ”customer:info:save”

最终实现相关代码

一、shiro配置类

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.session.mgt.SessionManager;

@Configuration
public class ShiroConfig {
	@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        advisor.setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptorExtend());
        return advisor;
    }
}

二、注解解析器

import org.apache.shiro.aop.MethodInvocation;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

/**
 * ShiroAnnotationResolver
 *
 * @author HHKJ-SERVER-ZJ
 * @since 2023/4/24 13:18
 */
public class ShiroAnnotationResolver extends SpringAnnotationResolver {

    /** 开放标识,以此标识为例,如果Controller上添加注解,而方法需要开放权限,使用该标识判断是否为开放接口信息 */
    public static final String OPEN_VALUE = "OPEN";

    @Override
    public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        // 对 RequiresPermissions 注解进行单独处理
        if (clazz == RequiresPermissions.class) {
            return getRequiresPermissionsAnnotation(mi);
        }
        return super.getAnnotation(mi, clazz);
    }

    /**
     * RequiresPermissions 注解类特殊处理
     * <p>
     * 支持 controller类上注解 和 method 上 RequiresPermissions 注解的value值进行拼接
     * “@RequiresPermissions("customer:info:")”
     * XxController{
     * “@RequiresPermissions("save")”
     * public void save() {
     * }
     * }
     * 最终 权限编码 ”customer:info:save”
     * </p>
     *
     * @param mi 方法名称
     * @return 注解信息
     */
    private Annotation getRequiresPermissionsAnnotation(MethodInvocation mi) {
        // 1、解析类上注解信息
        RequiresPermissions clazzAnnotation = AnnotationUtils.findAnnotation(mi.getThis().getClass(), RequiresPermissions.class);
        String[] clazzValues = (clazzAnnotation != null ? clazzAnnotation.value() : null);
        Logical clazzLogical = (clazzAnnotation != null ? clazzAnnotation.logical() : Logical.AND);

        //  2、解析方法上注解信息
        RequiresPermissions methodAnnotation = AnnotationUtils.findAnnotation(mi.getMethod(), RequiresPermissions.class);
        String[] methodValues = (methodAnnotation != null ? methodAnnotation.value() : null);
        Logical methodLogical = (methodAnnotation != null ? methodAnnotation.logical() : Logical.AND);

        // 3、无权限信息,跳过校验
        if (clazzAnnotation == null && methodAnnotation == null) {
            return null;
        }

        // 4、解析注解value值信息信息
        List<String> valueList = new ArrayList<>();
        // 1)如果两个都有值
        if (clazzValues != null && methodValues != null) {
            for (String clazzValue : clazzValues) {
                for (String methodValue : methodValues) {
                    if (OPEN_VALUE.equals(methodValue)) {
                        continue;
                    }
                    valueList.add((clazzValue + methodValue));
                }
            }
        }
        // 2)类上没有注解,方法上有
        if (clazzValues != null && methodValues == null) {
            for (String clazzValue : clazzValues) {
                if (OPEN_VALUE.equals(clazzValue)) {
                    continue;
                }
                valueList.add(clazzValue);
            }
        }
        // 3)方法上有,类上没有注解
        if (clazzValues == null && methodValues != null) {
            for (String methodValue : methodValues) {
                if (OPEN_VALUE.equals(methodValue)) {
                    continue;
                }
                valueList.add(methodValue);
            }
        }
        // 返回对应注解的时候,无法使用注解生成实体类,
        // 返回注解匿名对象
        return new RequiresPermissions() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return RequiresPermissions.class;
            }

            @Override
            public String[] value() {
                return valueList.size() == 0 ? null : valueList.toArray(new String[0]);
            }

            @Override
            public Logical logical() {
                // 处理逻辑,如果两个都为AND 则都需要满足,如果存在一个为 OR 那么都会以 OR 的情况处理
                return Logical.AND.equals(clazzLogical) && Logical.AND.equals(methodLogical) ? Logical.AND : Logical.OR;
            }
        };
    }
}

思路分析:

一、权限提示断点

在这里插入图片描述

1、方法权限拦截器

public abstract class AuthorizingAnnotationMethodInterceptor extends AnnotationMethodInterceptor
{
    public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
        try {
            ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
        } catch(AuthorizationException ae) {
            if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
            throw ae;
        }         
    }
}

2、注解方法拦截器

public abstract class AnnotationMethodInterceptor extends MethodInterceptorSupport {
	private AnnotationHandler handler;
    private AnnotationResolver resolver;
    protected Annotation getAnnotation(MethodInvocation mi) {
        return getResolver().getAnnotation(mi, getHandler().getAnnotationClass());
    }
}

3、SpringAnnotationResolver 注解解析器

public class SpringAnnotationResolver implements AnnotationResolver {
	public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        Method m = mi.getMethod();

        Annotation a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;
        
        Class<?> targetClass = mi.getThis().getClass();
        m = ClassUtils.getMostSpecificMethod(m, targetClass);
        a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;
        
        return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
    }
}

4、PermissionAnnotationHandler Permission注解处理器

public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {
    public void assertAuthorized(Annotation a) throws AuthorizationException {
        if (!(a instanceof RequiresPermissions)) return;

        RequiresPermissions rpAnnotation = (RequiresPermissions) a;
        String[] perms = getAnnotationValue(a);
        Subject subject = getSubject();

        if (perms.length == 1) {
            subject.checkPermission(perms[0]);
            return;
        }
        if (Logical.AND.equals(rpAnnotation.logical())) {
            getSubject().checkPermissions(perms);
            return;
        }
        if (Logical.OR.equals(rpAnnotation.logical())) {
            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
            boolean hasAtLeastOnePermission = false;
            for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
            if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
        }
    }
}

5、创建AopAllianceAnnotationsAuthorizingMethodInterceptor aop注解权限方法拦截器

public class AopAllianceAnnotationsAuthorizingMethodInterceptor
        extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {
        public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
        List<AuthorizingAnnotationMethodInterceptor> interceptors =
                new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
        //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
        //raw JDK resolution process.
        AnnotationResolver resolver = new SpringAnnotationResolver();
        //we can re-use the same resolver instance - it does not retain state:
        interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
        interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
        interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
        interceptors.add(new UserAnnotationMethodInterceptor(resolver));
        interceptors.add(new GuestAnnotationMethodInterceptor(resolver));

        setMethodInterceptors(interceptors);
    }
 }

6、授权增强器

public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
	public AuthorizationAttributeSourceAdvisor() {
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    }
}

7、注解处理器配置类

public class AbstractShiroAnnotationProcessorConfiguration {

    protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }

    protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro权限拦截实现主要分为两个部分,一是定义自己的Realm实现类,二是在Shiro的配置文件中配置过滤器链。 1. 定义自己的Realm实现类 在自定义的Realm实现类中,需要重写doGetAuthorizationInfo方法来授权用户的访问权限。该方法会在用户登录成功后进行调用,并返回该用户所具有的角色和权限信息。在该方法中,可以通过调用Subject对象的getPrincipal方法来获取当前用户的身份信息,然后根据用户的身份信息查询数据库或者其他数据源,获取该用户所具有的角色和权限信息。最后将角色和权限信息封装到一个SimpleAuthorizationInfo对象中并返回。 ``` public class MyRealm extends AuthorizingRealm { /** * 获取用户授权信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 查询用户角色和权限信息 Set<String> roles = new HashSet<>(); roles.add("admin"); authorizationInfo.setRoles(roles); Set<String> permissions = new HashSet<>(); permissions.add("user:add"); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } /** * 获取用户认证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // ... } } ``` 2. 配置过滤器链 在Shiro的配置文件中,通过配置过滤器链来实现权限拦截。在过滤器链中,可以根据URL路径的不同设置不同的过滤器来实现不同的拦截效果。 例如,我们可以使用authc过滤器来拦截需要认证的URL路径,使用perms过滤器来拦截需要特定权限才能访问的URL路径,使用roles过滤器来拦截需要特定角色才能访问的URL路径。具体的配置如下: ``` [urls] /login = anon /logout = logout /user/** = authc, perms[user:manage] /admin/** = authc, roles[admin] ``` 上述配置表示: - /login路径可以匿名访问 - /logout路径需要登录并且执行登出操作 - /user/**路径需要登录,并且需要user:manage权限才能访问 - /admin/**路径需要登录,并且需要admin角色才能访问 通过以上配置,就可以实现Shiro权限拦截功能。如果用户没有登录或者没有特定的角色或权限,就无法访问被拦截的URL路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值