解读 Permission 注解权限认证流程
解读 Permission 注解权限认证流程
Shiro 注解授权简介
授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限。 如判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限等等。
@RequiresPermissions({"xxx:model:edit"})
@RequestMapping({"delete"})
public String delete(String id) {
this.actModelService.delete(id)
return "redirect:" + "delete";
}
使用@RequiresPermissions
注解必须使用以下配置
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="systemAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
通过Spring的DefaultAdvisorAutoProxyCreator
自动代理底层也是aop,来到
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
package org.apache.shiro.spring.security.interceptor;
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;
public AuthorizationAttributeSourceAdvisor() {
this.setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
public SecurityManager getSecurityManager() {
return this.securityManager;
}
public void setSecurityManager(SecurityManager securityManager) {
this.securityManager = securityManager;
}
public boolean matches(Method method, Class targetClass) {
Method m = method;
if (this.isAuthzAnnotationPresent(method)) {
return true;
} else {
if (targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
if (this.isAuthzAnnotationPresent(m)) {
return true;
}
} catch (NoSuchMethodException var5) {
}
}
return false;
}
}
// ...
return false;
}
}
matches方法来判断是否切入,true为切入,false不切入。
this.setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
这里设置了增强advice,为AopAllianceAnnotationsAuthorizingMethodInterceptor
org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor#invoke
public Object invoke(org.aopalliance.intercept.MethodInvocation methodInvocation) throws Throwable {
MethodInvocation mi = this.createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
https://blog.csdn.net/chaitoudaren/article/details/105278868
来到
org.apache.shiro.authz.aop.AnnotationsAuthorizingMethodInterceptor#assertAuthorized
protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
Collection<AuthorizingAnnotationMethodInterceptor> aamis = this.getMethodInterceptors();
if (aamis != null && !aamis.isEmpty()) {
Iterator var3 = aamis.iterator();
while(var3.hasNext()) {
AuthorizingAnnotationMethodInterceptor aami = (AuthorizingAnnotationMethodInterceptor)var3.next();
if (aami.supports(methodInvocation)) {
aami.assertAuthorized(methodInvocation);
}
获取方法拦截器,即shiro鉴权用到的五个注解
-
RequiresAuthentication:
使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
-
RequiresGuest:
使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
-
RequiresPermissions:
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
-
RequiresRoles:
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
-
RequiresUser
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
遍历这五个方法拦截器与请求的方法拦截器进行匹配,请求路由代码中用到的是RequiresPermissions。
然后调用aami.assertAuthorized(methodInvocation);
进行认证。
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
((AuthorizingAnnotationHandler)this.getHandler()).assertAuthorized(this.getAnnotation(mi));
} catch (AuthorizationException var3) {
if (var3.getCause() == null) {
var3.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
}
throw var3;
}
来到org.apache.shiro.authz.aop.PermissionAnnotationHandler#assertAuthorized
this.getAnnotationValue(a);
方法获取@RequiresPermissions
内容,即权限标识
调用 this.getSubject();
获取身份信息
先来看看他是如何获取到身份信息的
// org.apache.shiro.aop.AnnotationHandler#getSubject
protected Subject getSubject() {
return SecurityUtils.getSubject();
}
//org.apache.shiro.SecurityUtils#getSubject
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
这里使用了ThreadContext,直接从当前线程里拿subject
想要知道怎么获取的需要回去看到装载的配置文件
<!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /><!--
<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
<property name="loginUrl" value="${adminPath}/login" />
<property name="successUrl" value="${adminPath}?login" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean>
<!-- 定义Shiro安全管理配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="systemAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
AuthorizationAttributeSourceAdvisor
装载了securityManager
对象
ShiroFilterFactoryBean
调用getObject方法,返回SpringShiroFilter对象注入到spring中
SpringShiroFilter的父类AbstractShiroFilter,每个请求都会经过这个处理
方法doFilterInternal如下
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
Subject subject = this.createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
其中updateSessionLastAccessTime维持session有效,executeChain去执行匹配的过滤器。
在这之前createSubject先创建了Subject,再通过execute绑定到线程
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
return (new WebSubject.Builder(this.getSecurityManager(), request, response)).buildWebSubject();
}
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = this.copy(subjectContext);
context = this.ensureSecurityManager(context);
context = this.resolveSession(context);
context = this.resolvePrincipals(context);
Subject subject = this.doCreateSubject(context);
this.save(subject);
return subject;
}
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
org.apache.shiro.subject.support.SubjectThreadState中
public void bind() {
SecurityManager securityManager = this.securityManager;
if ( securityManager == null ) {
//try just in case the constructor didn't find one at the time:
securityManager = ThreadContext.getSecurityManager();
}
this.originalResources = ThreadContext.getResources();
ThreadContext.remove();
ThreadContext.bind(this.subject);
if (securityManager != null) {
ThreadContext.bind(securityManager);
}
每个shiro拦截到的请求,都会根据seesionid创建Subject,清除当前线程的绑定,然后重新绑定的线程中,之后执行过滤器。
所以我们再SecurityUtils.getSubject()中获取的一直是当前用户的信息
回到org.apache.shiro.authz.aop.PermissionAnnotationHandler#assertAuthorized
地方,获取完subject后会去调用。验证当前用户是否有权限。
subject.checkPermission(perms[0]);
org.apache.shiro.subject.support.DelegatingSubject#checkPermission(java.lang.String)
public void checkPermission(String permission) throws AuthorizationException {
this.assertAuthzCheckPossible();
this.securityManager.checkPermission(this.getPrincipals(), permission);
}
assertAuthzCheckPossible
方法是验证是否有session,登录认证信息等
this.securityManager.checkPermission(this.getPrincipals(), permission);
会去检查当前session是否有权限访问
org.apache.shiro.mgt.AuthorizingSecurityManager#checkPermission(org.apache.shiro.subject.PrincipalCollection, java.lang.String)
public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
this.authorizer.checkPermission(principals, permission);
}
org.apache.shiro.authz.ModularRealmAuthorizer#checkPermission(org.apache.shiro.subject.PrincipalCollection, java.lang.String)
public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
this.assertRealmsConfigured();
if (!this.isPermitted(principals, permission)) {
throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
}
}
org.apache.shiro.authz.ModularRealmAuthorizer#isPermitted(org.apache.shiro.subject.PrincipalCollection, java.lang.String)
public boolean isPermitted(PrincipalCollection principals, String permission) {
this.assertRealmsConfigured();
Iterator var3 = this.getRealms().iterator();
Realm realm;
do {
if (!var3.hasNext()) {
return false;
}
realm = (Realm)var3.next();
} while(!(realm instanceof Authorizer) || !((Authorizer)realm).isPermitted(principals, permission));
return true;
}
org.apache.shiro.realm.AuthorizingRealm#isPermitted
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = this.getPermissionResolver().resolvePermission(permission);
return this.isPermitted(principals, p);
}
org.apache.shiro.realm.AuthorizingRealm#isPermitted(org.apache.shiro.subject.PrincipalCollection, org.apache.shiro.authz.Permission)
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = this.getAuthorizationInfo(principals);
return this.isPermitted(permission, info);
}
org.apache.shiro.realm.AuthorizingRealm#isPermitted(org.apache.shiro.authz.Permission, org.apache.shiro.authz.AuthorizationInfo)
调用erm.implies(permission)
验证权限
挨个遍历对比,查询是否有权限
在shiro处理中需要先执行到org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
,所以需要先经过shiroFilter的校验。
调用栈
matches:82, AuthorizationAttributeSourceAdvisor (org.apache.shiro.spring.security.interceptor)
matches:94, MethodMatchers (org.springframework.aop.support)
getInterceptorsAndDynamicInterceptionAdvice:67, DefaultAdvisorChainFactory (org.springframework.aop.framework)
getInterceptorsAndDynamicInterceptionAdvice:489, AdvisedSupport (org.springframework.aop.framework)
intercept:640, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)
loginFail:-1, LoginController$$EnhancerBySpringCGLIB$$7d5394e1 (com.thinkgem.jeesite.modules.sys.web)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:222, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:137, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:110, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:775, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:705, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:85, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:959, DispatcherServlet (org.springframework.web.servlet)
doService:893, DispatcherServlet (org.springframework.web.servlet)
processRequest:965, FrameworkServlet (org.springframework.web.servlet)
doPost:867, FrameworkServlet (org.springframework.web.servlet)
service:682, HttpServlet (javax.servlet.http)
service:841, FrameworkServlet (org.springframework.web.servlet)
service:765, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:18, URLFilter (com.thinkgem.jeesite.common.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
obtainContent:129, SiteMeshFilter (com.opensymphony.sitemesh.webapp)
doFilter:77, SiteMeshFilter (com.opensymphony.sitemesh.webapp)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:61, ProxiedFilterChain (org.apache.shiro.web.servlet)
executeChain:108, AdviceFilter (org.apache.shiro.web.servlet)
doFilterInternal:137, AdviceFilter (org.apache.shiro.web.servlet)
doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet)
doFilter:66, ProxiedFilterChain (org.apache.shiro.web.servlet)
executeChain:449, AbstractShiroFilter (org.apache.shiro.web.servlet)
call:365, AbstractShiroFilter$1 (org.apache.shiro.web.servlet)
doCall:90, SubjectCallable (org.apache.shiro.subject.support)
call:83, SubjectCallable (org.apache.shiro.subject.support)
execute:383, DelegatingSubject (org.apache.shiro.subject.support)
doFilterInternal:362, AbstractShiroFilter (org.apache.shiro.web.servlet)
doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet)
invokeDelegate:344, DelegatingFilterProxy (org.springframework.web.filter)
doFilter:261, DelegatingFilterProxy (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:85, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)