解讀 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

image-20221015113319240

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

image-20221015122827996

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);
    }

image-20221015123139122

image-20221015123046808

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);
                }

image-20221015125049643

獲取方法攔截器,即shiro鑑權用到的五個註解

  • RequiresAuthentication:

    使用該註解標註的類,實例,方法在訪問或調用時,當前Subject必須在當前session中已經過認證。

  • RequiresGuest:

    使用該註解標註的類,實例,方法在訪問或調用時,當前Subject可以是“gust”身份,不需要經過認證或者在原先的session中存在記錄。

  • RequiresPermissions:

    當前Subject需要擁有某些特定的權限時,才能執行被該註解標註的方法。如果當前Subject不具有這樣的權限,則方法不會被執行。

  • RequiresRoles:

    當前Subject必須擁有所有指定的角色時,才能訪問被該註解標註的方法。如果當天Subject不同時擁有所有指定角色,則方法不會執行還會拋出AuthorizationException異常。

  • RequiresUser

    當前Subject必須是應用的用戶,才能訪問或調用被該註解標註的類,實例,方法。

image-20221015125455497

遍歷這五個方法攔截器與請求的方法攔截器進行匹配,請求路由代碼中用到的是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);
    }

image-20221015141817368

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)

image-20221015142707729

調用erm.implies(permission)驗證權限

image-20221015143022589

挨個遍歷對比,查詢是否有權限

在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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章