业务场景:
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;
}
}