长亭百川云 - 文章详情

解读 Permission 注解权限认证流程 - nice_0e3

博客园 - nice_0e3

26

2024-07-20

解读 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)
相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2