在一个用户登录后,即身份认证通过,只能证明该登录身份是合法的,至于具体能访问系统中的什么资源,需要通过授权来控制。一般系统中都是通过用户关联角色、角色再关联权限来实现判断一个用户是否有某资源的使用权限,Shiro也提供了相应的实现权限控制。

    Shiro中的权限控制也是通过Filter来实现的,在前面认证流程中讲到,Shiro的DefaultFilterChainManager类会创建Filter链,链中包含了Shiro一些默认Filter,也可以添加自定义Filter,而且这些Filter都有名字,Shiro会根据Filter配置为每一个配置的URL匹配符创建一个Filter链。

[java] view plain copy
  1. protected FilterChainManager createFilterChainManager() {  
  2.     // 创建DefaultFilterChainManager  
  3.     DefaultFilterChainManager manager = new DefaultFilterChainManager();  
  4.     // 创建Shiro默认Filter,根据org.apache.shiro.web.filter.mgt.DefaultFilter创建  
  5.     Map<String, Filter> defaultFilters = manager.getFilters();  
  6.     //apply global settings if necessary:  
  7.     for (Filter filter : defaultFilters.values()) {  
  8.         // 设置相关Filter的loginUrl、successUrl、unauthorizedUrl属性  
  9.         applyGlobalPropertiesIfNecessary(filter);  
  10.     }  
  11.   
  12.     // 获取在Spring配置文件中配置的Filter  
  13.     Map<String, Filter> filters = getFilters();  
  14.     if (!CollectionUtils.isEmpty(filters)) {  
  15.         for (Map.Entry<String, Filter> entry : filters.entrySet()) {  
  16.             String name = entry.getKey();  
  17.             Filter filter = entry.getValue();  
  18.             applyGlobalPropertiesIfNecessary(filter);  
  19.             if (filter instanceof Nameable) {  
  20.                 ((Nameable) filter).setName(name);  
  21.             }  
  22.             // 将配置的Filter添加至链中,如果同名Filter已存在则覆盖默认Filter  
  23.             manager.addFilter(name, filter, false);  
  24.         }  
  25.     }  
  26.   
  27.     //build up the chains:  
  28.     Map<String, String> chains = getFilterChainDefinitionMap();  
  29.     if (!CollectionUtils.isEmpty(chains)) {  
  30.         for (Map.Entry<String, String> entry : chains.entrySet()) {  
  31.             String url = entry.getKey();  
  32.             String chainDefinition = entry.getValue();  
  33.             // 为配置的每一个URL匹配创建FilterChain定义,  
  34.             // 这样当访问一个URL的时候,一旦该URL配置上则就知道该URL需要应用上哪些Filter  
  35.             // 由于URL匹配符会配置多个,所以以第一个匹配上的为准,所以越具体的匹配符应该配置在前面,越宽泛的匹配符配置在后面  
  36.             manager.createChain(url, chainDefinition);  
  37.         }  
  38.     }  
  39.   
  40.     return manager;  
  41. }  

下面我们看来都有哪些默认Filter,在DefaultFilterChainManager构造方法中调用addDefaultFilters方法:

[java] view plain copy
  1. protected void addDefaultFilters(boolean init) {  
  2.     for (DefaultFilter defaultFilter : DefaultFilter.values()) {  
  3.         addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);  
  4.     }  
  5. }  
  6.   
  7. public enum DefaultFilter {  
  8.   
  9.     anon(AnonymousFilter.class),  
  10.     authc(FormAuthenticationFilter.class),  
  11.     authcBasic(BasicHttpAuthenticationFilter.class),  
  12.     logout(LogoutFilter.class),  
  13.     noSessionCreation(NoSessionCreationFilter.class),  
  14.     perms(PermissionsAuthorizationFilter.class),  
  15.     port(PortFilter.class),  
  16.     rest(HttpMethodPermissionFilter.class),  
  17.     roles(RolesAuthorizationFilter.class),  
  18.     ssl(SslFilter.class),  
  19.     user(UserFilter.class);  
  20.   
  21.     // 省略一些代码...  
  22. }  

DefaultFilter中的PermissionsAuthorizationFilter与RolesAuthorizationFilter就是用于权限控制的Filter,名称分别为perms与roles。PermissionsAuthorizationFilter用于判断用户访问某URL时是否有相应权限,RolesAuthorizationFilter用于判断用户访问某URL时是否有相应角色。


由于Shiro中Filter继承体系比较复杂,要想理解Shiro权限是如何控制的就必须先理解Filter的继承体系,以及理解继承体系中父类Filter的特点及作用。由于Filter继承体系庞大,下面只列出PermissionsAuthorizationFilter与RolesAuthorizationFilter的继承关系。

Shiro源码分析----授权流程

下面对继承关系中一些重要的Filter作简要说明,具体的Filter详细分析容后续再讲。

1.NameableFilter:为Filter添加名称
2.OncePerRequestFilter:保证Filter在链中只被执行一次
3.AdviceFilter:

[java] view plain copy
  1. public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)  
  2.             throws ServletException, IOException {  
  3.   
  4.     Exception exception = null;  
  5.     try {  
  6.         // 前置处理,如果返回false则不再执行链中的后续Filter  
  7.         boolean continueChain = preHandle(request, response);  
  8.         if (log.isTraceEnabled()) {  
  9.             log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");  
  10.         }  
  11.   
  12.         if (continueChain) {  
  13.             // 继续执行链中的后续Filter  
  14.             executeChain(request, response, chain);  
  15.         }  
  16.           
  17.         // 后置处理  
  18.         postHandle(request, response);  
  19.         if (log.isTraceEnabled()) {  
  20.             log.trace("Successfully invoked postHandle method");  
  21.         }  
  22.   
  23.     } catch (Exception e) {  
  24.         exception = e;  
  25.     } finally {  
  26.         cleanup(request, response, exception);  
  27.     }  
  28. }  

4.PathMatchingFilter:基于路径匹配的Filter,重写preHandle方法

[java] view plain copy
  1. protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {  
  2.     // appliedPaths存储该Filter需要被应用的URL路径,例如有这样一个配置:/user_add.jsp = perms["user:add"]  
  3.     // 那么在PermissionsAuthorizationFilter.appliedPaths中就有一条key为/user_add.jsp, value为[user:add]数组  
  4.     // value为数组而不是字符串的原因是权限可以有多个  
  5.   
  6.     // 如果appliedPaths为空则直接继续执行Filter链  
  7.     if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {  
  8.         if (log.isTraceEnabled()) {  
  9.             log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");  
  10.         }  
  11.         return true;  
  12.     }  
  13.   
  14.     for (String path : this.appliedPaths.keySet()) {  
  15.         // 如果匹配,则根据onPreHandle方法的返回值来确定是否继续执行Filter链  
  16.         if (pathsMatch(path, request)) {  
  17.             log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);  
  18.             Object config = this.appliedPaths.get(path);  
  19.             return isFilterChainContinued(request, response, path, config);  
  20.         }  
  21.     }  
  22.   
  23.     //no path matched, allow the request to go through:  
  24.     return true;  
  25. }  
  26. @SuppressWarnings({"JavaDoc"})  
  27. private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,  
  28.                                        String path, Object pathConfig) throws Exception {  
  29.     // 如果该Filter可用  
  30.     if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2  
  31.         // 省略一些代码...  
  32.         return onPreHandle(request, response, pathConfig);  
  33.     }  
  34.   
  35.     // 省略一些代码...  
  36.     return true;  
  37. }  

5.AccessControlFilter:实现onPreHandle方法

[java] view plain copy
  1. public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  2.     return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);  
  3. }  
根据isAccessAllowed方法的返回值来确定是否继续执行Filter链,如果不执行Filter链,则还会执行onAccessDenied方法


6.AuthorizationFilter:实现了onAccessDenied方法

[java] view plain copy
  1. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {  
  2.   
  3. Subject subject = getSubject(request, response);  
  4. // 如果没有登录则重定向至登录页面  
  5. if (subject.getPrincipal() == null) {  
  6.     saveRequestAndRedirectToLogin(request, response);  
  7. else {  
  8.     // 重定向至未授权页面  
  9.     String unauthorizedUrl = getUnauthorizedUrl();  
  10.     //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:  
  11.     if (StringUtils.hasText(unauthorizedUrl)) {  
  12.         WebUtils.issueRedirect(request, response, unauthorizedUrl);  
  13.     } else {  
  14.         // 如果未授权页面未配置则发送401状态码  
  15.         WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);  
  16.     }  
  17. }  
  18. return false;  

7.PermissionsAuthorizationFilter:实现了isAccessAllowed方法

[java] view plain copy
  1. public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {  
  2.   
  3.     Subject subject = getSubject(request, response);  
  4.     // 访问时需要的权限  
  5.     String[] perms = (String[]) mappedValue;  
  6.     // 调用Subject对象的isPermitted或isPermittedAll来判断是否有权限  
  7.     boolean isPermitted = true;  
  8.     if (perms != null && perms.length > 0) {  
  9.         if (perms.length == 1) {  
  10.             if (!subject.isPermitted(perms[0])) {  
  11.                 isPermitted = false;  
  12.             }  
  13.         } else {  
  14.             if (!subject.isPermittedAll(perms)) {  
  15.                 isPermitted = false;  
  16.             }  
  17.         }  
  18.     }  
  19.   
  20.     return isPermitted;  
  21. }  

isPermitted和isPermittedAll最终都委托给了ModularRealmAuthorizer.isPermitted与ModularRealmAuthorizer.isPermittedAll方法,至于为什么为会委托给ModularRealmAuthorizer请参看:Shiro源码分析----登录流程


下面以ModularRealmAuthorizer.isPermitted为例,分析一下是如何进行权限判断的:

[java] view plain copy
  1. public boolean isPermitted(PrincipalCollection principals, String permission) {  
  2.     assertRealmsConfigured();  
  3.     for (Realm realm : getRealms()) {  
  4.         if (!(realm instanceof Authorizer)) continue;  
  5.         // 调用Realm的isPermitted方法  
  6.         if (((Authorizer) realm).isPermitted(principals, permission)) {  
  7.             return true;  
  8.         }  
  9.     }  
  10.     return false;  
  11. }  

在使用Shiro时,Realm对象包含了认证与授权信息,在实际应用时,一般都是存储在数据库中的,且Realm一般都会自定义实现。实现自定义的Realm时,一般继承自org.apache.shiro.realm.AuthorizingRealm类,下面是一个例子:

[java] view plain copy
  1. public class UserRealm extends AuthorizingRealm {  
  2.   
  3.     private UserService userService;  
  4.   
  5.     public void setUserService(UserService userService) {  
  6.         this.userService = userService;  
  7.     }  
  8.   
  9.     // 从数据库中获取权限信息  
  10.     @Override  
  11.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  12.         String username = (String)principals.getPrimaryPrincipal();  
  13.   
  14.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  15.         // 从数据库中查询当前用户所拥有的角色  
  16.         authorizationInfo.setRoles(userService.findRoles(username));  
  17.         // 从数据库中查询当前用户所拥有的权限  
  18.         authorizationInfo.setStringPermissions(userService.findPermissions(username));  
  19.   
  20.         return authorizationInfo;  
  21.     }  
  22.       
  23.     // 从数据库中获取认证信息  
  24.     @Override  
  25.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
  26.   
  27.         String username = (String)token.getPrincipal();  
  28.   
  29.         User user = userService.findByUsername(username);  
  30.   
  31.         if(user == null) {  
  32.             throw new UnknownAccountException();//没找到帐号  
  33.         }  
  34.   
  35.         if(Boolean.TRUE.equals(user.getLocked())) {  
  36.             throw new LockedAccountException(); //帐号锁定  
  37.         }  
  38.   
  39.         //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现  
  40.         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
  41.                 user.getUsername(), //用户名  
  42.                 user.getPassword(), //密码  
  43.                 ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt  
  44.                 getName()  //realm name  
  45.         );  
  46.         return authenticationInfo;  
  47.     }  
  48.   
  49. }  

AuthorizingRealm.isPermetted方法中就是根据用户所拥有的权限与访问时需要的权限进行匹配,如果有权限则继续执行Filter链,反之则重定向至配置的未授权页面。
理解了PermissionsAuthorizationFilter的判断逻辑,那么RolesAuthorizationFilter的判断逻辑就很容易理解了,因为其流程是一样的,只是RolesAuthorizationFilter是基于用户角色进行判断的。


8.RolesAuthorizationFilter:实现了isAccessAllowed方法:

[java] view plain copy
  1. @SuppressWarnings({"unchecked"})  
  2. public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {  
  3.   
  4.     Subject subject = getSubject(request, response);  
  5.     // 获取访问时需要的角色  
  6.     String[] rolesArray = (String[]) mappedValue;  
  7.   
  8.     if (rolesArray == null || rolesArray.length == 0) {  
  9.         //no roles specified, so nothing to check - allow access.  
  10.         return true;  
  11.     }  
  12.       
  13.     Set<String> roles = CollectionUtils.asSet(rolesArray);  
  14.     // 委托给Subject.hasAllRoles方法  
  15.     return subject.hasAllRoles(roles);  
  16. }  

同理,hasAllRoles方法,最终都委托给了ModularRealmAuthorizer.hasAllRoles方法

[java] view plain copy
  1. public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {  
  2.     assertRealmsConfigured();  
  3.     for (String roleIdentifier : roleIdentifiers) {  
  4.         if (!hasRole(principals, roleIdentifier)) {  
  5.             return false;  
  6.         }  
  7.     }  
  8.     return true;  
  9. }  
  10. public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {  
  11.     assertRealmsConfigured();  
  12.     for (Realm realm : getRealms()) {  
  13.         if (!(realm instanceof Authorizer)) continue;  
  14.         if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {  
  15.             return true;  
  16.         }  
  17.     }  
  18.     return false;  
  19. }  

AuthorizingRealm.hasAllRoles方法中就是根据用户所拥有的角色与访问时需要的角色进行匹配,如果有角色则继续执行Filter链,反之则重定向至配置的未授权页面。


至此,Shiro授权流程分析完毕,如有错误之处,敬请指正。

相关文章:

  • 2021-11-17
  • 2021-09-10
  • 2021-04-01
  • 2021-10-27
  • 2021-11-03
  • 2021-11-26
  • 2021-05-29
猜你喜欢
  • 2021-10-13
  • 2022-02-25
  • 2022-12-23
  • 2021-05-07
  • 2022-12-23
  • 2021-11-29
  • 2021-06-30
相关资源
相似解决方案