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