源码分析 shiro注解授权
Posted 黑马程序员深圳中心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析 shiro注解授权相关的知识,希望对你有一定的参考价值。
Shiro 注解授权简介
授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限。 如判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限等等,当前我们主要分析shiro注解授权底层如何实现。
Shiro 注解授权源码概况
首先在spring配置文件中 我们得开启shiro的注解,需要做以下配置
!-- 基于spring 要求为类创建代理对象方式 强制要求使用cglib -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 属性配置强制使用cglib -->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!-- 开启shiro注解支持-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 切面类:拦截有注解权限的所有方法-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
</bean>
在删除方法上加注解
RequiresPermissions("delete")
public void delete(String ids) {
//业务逻辑......
}
通过删除方法上的权限注解,分析注解授权源码
第一步:在shiro-all-1.2.2.jar包中找到配置文件中的AuthorizationAttributeSourceAdvisor类,此类拦截所有犯法,根据该类中的matches方法判断当前方法是否有注解。
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;
}
public boolean matches(Method method, Class targetClass) {
Method m = method;
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());
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
} 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(Method method) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
}
第二步: 在AuthorizationAttributeSourceAdvisor构造函数中调用了setAdvice方法,传入参数为匿名AopAllianceAnnotationsAuthorizingMethodInterceptor类对象
public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
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);
}
第三步:这里按照Permission分析PermissionAnnotationMethodInterceptor的带参构造函数
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
super( new PermissionAnnotationHandler(), resolver);
}
第四步:调用父类构造函数并从第一个参数可以看出是对权限注解的处理类,父类为AuthorizingAnnotationMethodInterceptor一般我们知道AOP 执行的方法为invoke()看到此类中的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
}
第五步:可以看到其中调用了assertAuthorized方法
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
}
catch(AuthorizationException ae) {
// Annotation handler doesn't know why it was called, so add the information here if
possible.
// Don't wrap the exception here since we don't want to mask the specific exception,
such as
// UnauthenticatedException etc.
if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to
invoke method: " + mi.getMethod()));
throw ae;
}
}
第六步:其中((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));这行代码实际就是调用我们之前传入的注解处理类的assertAuthorized方法查看 PermissionAnnotationHandler的对应方法
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]);
}
}
获取注解的permission字符串,以一个字符串分析,查看checkPermission方法
public void checkPermission(String permission) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermission(getPrincipals(), permission);
}
第七步:继续看securityManager的checkPermission方法,最后在ModularRealmAuthorizer中
public void checkPermission(PrincipalCollection principals, String permission) throws
AuthorizationException {
assertRealmsConfigured();
if (!isPermitted(principals, permission)) {
throw new UnauthorizedException("Subject does not have permission [" + permission +
"]");
}
}
第八步:if语句中判断是否满足条件
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
第九步:最后调到AuthorizingRealm的isPermitted方法
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
getAuthorizationInfo方法为
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals +
"]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals +
"]");
}
}
}
if (info == null) {
// Call template method if the info was not found in a cache
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization
info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
在doGetAuthorizationInfo()方法 也就是我们自定义realm里面可以拓展的方法,一般就是从数据库获取配置的权限字符串然后返回过来了,最后就是比较数据库中查询的权限字符串是否包含方法上的权限注解中的字符串。
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
如果没有方法上的权限注解的字符串就会抛出没有权限的异常。
总结
在Service类上使用注解 @Transactional,这个时候Service类是一个jdk方式的代理对象,如果把权限注解加到Service上是不好用的,会发生类型转换异常。可以在事务注解支持配置中配置proxy-target-class="true",保证事务和权限注解都使用cglib方式创建代理对象。或者可以将方法注解加到Controller中的方法上,这样就不会受事务注解代理对象的影响。
这是一条低调的分割线
往期精选干货
点击文字跳转文章
黑妹微信:heimei0755
愿你我都能拥有
清醒的眼光与前进的勇气
成为不被时代所抛弃的人
以上是关于源码分析 shiro注解授权的主要内容,如果未能解决你的问题,请参考以下文章
前后端分离项目中 springboot 集成 shiro 实现权限控制