shiro-spring
借助Spring AOP特性实现shiro的注解式校验
引入shiro-spring依赖后一定要注入
AuthorizationAttributeSourceAdvisor
以便借助spring aop进行shiro注解校验
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
StaticMethodMatcherPointcut
如何接入spring框架,借助spring实现自身AOP操作
一个抽象类实现 PointcutAdvisor, Ordered, Serializable 三个接口
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut
这是一个桥模式,实现Pointcut接口代表这是一个切点,但extends StaticMethodMatcher 代表这个抽象类更加适合表示为方法匹配器,一个切点+方法匹配的聚合抽象类,允许两个维度的扩展
一个双重维度的方便super class,
一些SpringAOP相关的接口
StaticMethodMatcherPointcut
统合spring AOP相关描述的两个重要接口
-
MethodMatcher
一个抽象接口用于判断方法能否被增强(advice)
-
TrueMethodMatcher
一个饿汉实现的单例模式,是一个匹配所有方法的规范(Canonical)匹配器
实现
3-arg
的matches方法直接抛出异常代表该方法在运行时不可被调用@Override public boolean matches(Method method, Class<?> targetClass, Object... args) { // Should never be invoked as isRuntime returns false. throw new UnsupportedOperationException(); }
-
boolean matches(Method method, Class<?> targetClass);
执行static的检测目标method是否符合被增强的条件 -
boolean isRuntime();
运行时是否需要调用boolean matches(Method method, Class<?> targetClass, Object... args);
,即使2参数的matches方法判断为true -
boolean matches(Method method, Class<?> targetClass, Object... args);
只有2-arg
的matches方法被调用并返回true且isRuntime也返回true该方法被立刻调用判断是否运行时动态判断
-
-
Pointcut
切入点抽象接口,由classFilter和MethodMatcher组成
所有的网络请求在servlet中都有filter来实现AOP的横切拦截处理,那么class在AOP实现中也可以有自己的ClassFilter
-
ClassFilter getClassFilter();
获取类过滤器 -
Return the MethodMatcher for this pointcut.
获取该pointcut
的方法匹配器
-
-
Advisor
在
pointcut
采取的动作在某个aspect的增强动作,可以是前置、后置、环绕等
-
StaticMethodMatcherPointcut
将大量抽象实现,只留出最重要的2-arg method match
一个想变成pointcut
的methodMatcher
StaticMethodMatcherPointcutAdvisor
public abstract class StaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcut
implements PointcutAdvisor, Ordered, Serializable
一个拥有增强方法以及pointcut
双重身份的超类
继承该抽象类的子类只需实现一个方法是否匹配需要被增强的方法即可boolean matches(Method method, Class<?> targetClass);
借助spring实现注解式安全校验
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.spring.security.interceptor;
import org.apache.shiro.authz.annotation.*;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* TODO - complete JavaDoc
*
* @since 0.1
*/
@SuppressWarnings({"unchecked"})
public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
protected SecurityManager securityManager = null;
/**
* Create a new AuthorizationAttributeSourceAdvisor.
* 初始化注入增强动作(校验用户身份/权限
*/
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
public SecurityManager getSecurityManager() {
return securityManager;
}
public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
this.securityManager = securityManager;
}
/**
* 实现方法匹配,寻找被标注shiro相关注解的方法使之成为pointcut
* Returns <tt>true</tt> if the method or the class has any Shiro annotations, false otherwise.
* The annotations inspected are:
* <ul>
* <li>{@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}</li>
* </ul>
*
* @param method the method to check for a Shiro annotation
* @param targetClass the class potentially declaring Shiro annotations
* @return <tt>true</tt> if the method has a Shiro annotation, false otherwise.
* @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, Class)
*/
public boolean matches(Method method, Class targetClass) {
Method m = method;
// 判断当前方法有没有标注shiro校验相关注解
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
//The 'method' parameter could be from an interface that doesn't have the annotation.
//Check to see if the implementation has it.
// 方法存在继承关系寻找实现类(方法)是否标记
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
//default return value is false. If we can't find the method, then obviously
//there is no annotation, so just use the default return value.
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
/**
* 校验方法上是否被标注shiro权限校验相关注解
*/
private boolean isAuthzAnnotationPresent(Method method) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
}
小结
通过Spring的IOC+AOP的特性,向IOC容器中注入一个PointCut检测器,由于AOP特性进行动态切面检测,并为匹配到的pointcut进行增强操作
@RequiresPermission注解校验流程
-
AuthorizationAttributeSourceAdvisor#matches
校验方法是否加上shiro相关注解,这里主要校验是否加上@RequiresPermission注解后面是spring框架的AOP流程
被DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice调用
被AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice调用
-
调用PermissionAnnotationHandler处理@RequiresPermissions注解,根据ThreadContext取出当前线程会话存储的subject来判断当前登录用户是否拥有某个权限,用户权限被shiro存储在缓存中(这里可使用 redis保存shiro的用户授权信息序列化后的数据)
一些授权校验的动作回去缓存中获取当前登录用户(在某个会话中)缓存的授权信息,因此执行用户切换/用户权限变动需执行缓存清理工作
/**
* 根据principalCollection删除授权缓存
* @param attribute
*/
public void deleteCacheBySimplePrincipalCollection(SimplePrincipalCollection attribute) {
//删除Cache,再访问受限接口时会重新授权
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
Authenticator authc = securityManager.getAuthenticator();
((LogoutAware) authc).onLogout(attribute);
}
可调用subject.runAs来进行用户身份切换
疑问
-
抽象类实现methodMatcher不用实现全部方法也不会报错么
不会,抽象类是抽象描述意味着不需要实现同样是抽象描述的接口中的任何方法,但具体的实现类不同,如果继承的interface中的某个接口没有默认实现则一定要实现,否则运行时报错
Class 'ConcreteStaticMethodMatcher' must either be declared abstract or implement abstract method 'matches(Method, Class<?>)' in 'MethodMatcher'
package priv.wzb.javabase.abstractAndInterface; import java.lang.reflect.Method; /** * @program: Design_Pattern * @author: yuzuki * @create: 2021-05-29 13:38 * @description: AOP_方法匹配接口 **/ public interface MethodMatcher { /** * method是否匹配,在AOP中就是method是否成为一个连接点/切点 * @param method * @param targetClass * @return */ boolean matches(Method method,Class<?> targetClass); /** * 匹配后是否需动态调用3-arg匹配 * @return */ boolean isRuntime(); /** * matchers=true isRuntime=true时触发 * @param method * @param targetClass * @param args * @return */ boolean methes(Method method,Class<?> targetClass,Object... args); } package priv.wzb.javabase.abstractAndInterface; import java.lang.reflect.Method; /** * @program: Design_Pattern * @author: wangzibai01 * @create: 2021-05-29 13:45 * @description: 静态方法检测 **/ public abstract class StaticMethodMatcher implements MethodMatcher { @Override public boolean isRuntime() { return false; } @Override public boolean methes(Method method, Class<?> targetClass, Object... args) { // return false; throw new UnsupportedOperationException(); } } package priv.wzb.javabase.abstractAndInterface; import java.lang.reflect.Method; /** * @program: Design_Pattern * @author: wangzibai01 * @create: 2021-05-29 13:46 * @description: StaticMethodMatcher实现类 **/ public class ConcreteStaticMethodMatcher extends StaticMethodMatcher { @Override public boolean matches(Method method, Class<?> targetClass) { return false; } public static void main(String[] args) { System.out.println("new ConcreteStaticMethodMatcher().isRuntime() = " + new ConcreteStaticMethodMatcher().isRuntime()); } }
-
泛型和通配符
乏型和通配符的区别
乏型: 抽象指定类型,让开发者使用时更方便,优化、设计方面的作用。泛型可以防止类特征消失 通配符: 指定类型范围,避免开发不知道该数据只支持的范围,设置错误类型导致出现bug。灵活使用
-
org.springframework.core.annotation.AnnotationUtils
工具类可校验方法/类上是否标注某个注解