Spring+Shiro权限控制+动态读取数据库

在上一篇中只是简单的实现了权限控制,但都是配置在xml中,这样很不灵活,这一篇,我会从数据库中动态读取权限,然后根据用户组进行分配。建议先配置学习下之前的。跑成功了再来看这一篇。
上一篇传送门Spring+Shiro权限控制


需要的jar包

这里不需要添加新的jar包,直接在上一篇的基础写。
在web.xml中加入filter,创建shiro.xml然后添加到web.xml中。 然后将shiro.xml中最后一行解开,修改filters对应的value-ref的值。


shiro里面的内容

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- 配置shiro -->
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
       <!-- 指定Shiro验证用户登录的类为自定义的Realm(若有多个Realm,可用[realms]属性代替) -->
       <property name="realm">
           <bean class="com.liaoliao.shiro.MyRealm"/>
       </property>
    </bean>
<bean id="simplePermFilter" class="org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter"></bean> 
 <!-- org.apache.shiro.spring.web.ShiroFilterFactoryBean -->
<bean id="shiroFilter" class="com.liaoliao.shiro.ShiroPermissionFactory">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/sys/toLogin"/>
    <property name="successUrl" value="/sys/login"/>
    <property name="unauthorizedUrl" value="/sys/toLogin"/>
    <!-- 权限配置 -->
     <property name="filters">    
           <map>    
               <entry key="anyRoles" value-ref="anyRoles"/>  
           </map>    
       </property>    
     <property name="filterChainDefinitions">
        <value>
            /bootstrap/**       = anon
            /css/**             = anon
            /fonts/**           = anon
            /images/**          = anon
            /js/**              = anon
            /public/**          = anon
            /public/**          = anon
            /sys/toLogin        = anon
            /sys/Login          = anon
            /sys/logout         = anon
        </value>
    </property> 
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean id="anyRoles" class="com.liaoliao.shiro.CustomRolesAuthorizationFilter" />  

</beans>

shiro.xml的内容跟之前的差别很小。然后MyRealM的内容没有改变,跟之前的一样。

MyRealM.java

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import com.liaoliao.admin.entity.AdminUser;
import com.liaoliao.admin.entity.Permission;
import com.liaoliao.admin.service.AdminUserService;
import com.liaoliao.admin.service.PermissionService;


public class MyRealm extends AuthorizingRealm {
    
    @Autowired
    private AdminUserService adminUserService;
    
    @Autowired
    private PermissionService permissionService;
    
    
    /**
     * 为当前登录的Subject授予角色和权限
     * -----------------------------------------------------------------------------------------------
     * 经测试:本例中该方法的调用时机为需授权资源被访问时
     * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     * -----------------------------------------------------------------------------------------------
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        //获取当前登录的用户名
        String currentUsername = (String)super.getAvailablePrincipal(principals);
        //从数据库中获取当前登录用户的详细信息
        AdminUser adminUser = adminUserService.findByUserName(currentUsername);
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
     //   Set<String>  perlist = new HashSet<String>();
       if(adminUser != null){
        //在这里添加的role对应的就是权限的名,比如说,你给一个url添加了roles[管理员],有个叫mopoint的用户登录了,
        //他想要访问这个url,那么在这里就需要给他赋予管理员这个权限,也就是说
        //这里面simpleAuthorInfo.addRole("管理员");加上的就是管理员三个字。
         simpleAuthorInfo.addRole(adminUser.getAdminGroup().getGroupName());

        //这里我是通过数据库读取出来然后放入集合里面去的。
         /* List<Permission> pers = permissionService.findByGroupId(adminUser.getAdminGroup().getId());
            if(pers!=null && pers.size()>0){
                for(Permission p:pers){
                    perlist.add(p.getNavigation().getNavigationUrl());
                }
                simpleAuthorInfo.addStringPermissions(perlist);
            } */

        //这里的perlist就是你能访问的url,比如说你数据库存放的一个url:/sys/videoList;这个url需要权限[管理员]。 
        //在上面已经给你的账号mopoint加上了role:[管理员],对应的,这里需要加上这个url。然后你的账号就能访问这个url了。
      //比如配置在shiro.xml中的是/sys/videoList,那么这里的url就是"/sys/videoList";
         String url="/sys/videoList";
         simpleAuthorInfo.addStringPermissions(url);
         return simpleAuthorInfo;
         }else{
        //如果返回空表示用户访问失败,会自动跳转到刚才unauthorizedUrl指定的地址。配置在shiro.xml里面
             return null;
         }
    }

    

    /**
     * 验证当前登录的Subject
     * 当在登录时执行Subject.login(),就会调用下面的这个接口:
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
    //实际上这个authcToken在用户登录时通过currentUser.login(token)传过来的。
        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
        if(token.getUsername()==null){
    //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
            return null;
        }
        AdminUser adminUser = adminUserService.findByUserName(token.getUsername());
        if(null != adminUser){
            String username = adminUser.getUserName();
            String password = adminUser.getPassWord();
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, password, this.getName());
            this.setAuthenticationSession(adminUser);
            return authcInfo;
        }else{
            throw new  UnknownAccountException("用户帐号不存在!");
        }
    }

    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * 比如Controller里面,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setAuthenticationSession(Object value){
     /*   Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();
            session.setTimeout(1000 * 60 * 60 * 2);
            session.setAttribute("currentUser", value);
        }*/
    }
}



controller的写法也没有区别,还是之前的那样。

LoginController.java


@Controller("adminLogin")
@RequestMapping("/sys")
public class LoginController {

    /**
     * 跳转到登录页面
     * @param request
     * @return
     */
    @RequestMapping("/toLogin")
    public String toLogin(HttpServletRequest request){
        return "page/login";
    }

    //用户退出
       @RequestMapping("/logout")
        public String logout(HttpSession session){
            String currentUser = (String)session.getAttribute("currentUser");
            System.out.println("用户[" + currentUser + "]准备登出");
            SecurityUtils.getSubject().logout();
            System.out.println("用户[" + currentUser + "]已登出");
            return InternalResourceViewResolver.REDIRECT_URL_PREFIX + "/login";
        }

    /**
     * 验证登录:
     * @param request
     * @param response
     * @param userName
     * @param passCode
     * @return
     */
    @RequestMapping("/Login")
    public String Login(HttpServletRequest request,HttpServletResponse response,String userName,String passWord){
            UsernamePasswordToken token=new UsernamePasswordToken();
            token.setRememberMe(true);
            //获取当前的Subject
            Subject currentUser = SecurityUtils.getSubject();
            try {
                currentUser.login(token);
                System.out.println("对用户[" + token.getUsername() + "]进行登录验证...验证通过");
            }catch(Exception e){
            //这里细分,大概有五种异常,有兴趣可以点击文章最后的链接去看看。
                e.printStackTrace();
                System.out.println("用户名或密码不正确");
                request.setAttribute("message_login", "用户名或密码不正确");
                return InternalResourceViewResolver.FORWARD_URL_PREFIX +"/sys/toLogin";
            }    
            //验证是否登录成功,这里的isAuthenticated()方法有时候不怎么灵通,具体我也不知道啥原因,欢迎小伙伴找我探讨~
            if(currentUser.isAuthenticated()){
                AminUser au=adminUserService.findByUserName(userName);
                AdminUser sessionAu= (AdminUser) request.getSession().getAttribute("adminUser");
                if(au == null && sessionAu == null){
                    System.out.println(111);
                    return InternalResourceViewResolver.FORWARD_URL_PREFIX +"/sys/toLogin";
                }
                request.setAttribute("adminUser", au);
                HttpSession session = request.getSession();
                session.setAttribute("adminUser", au);
                session.setAttribute("token", token);
                List<Permission> pList=permissionService.findByGroupId(au.getAdminGroup().getId());
                request.setAttribute("list", pList);    
                return "forward:/sys/theHome";
            }else{
                System.out.println("未通过!");
                token.clear();
                return InternalResourceViewResolver.REDIRECT_URL_PREFIX +"/sys/toLogin";
            }
      }

}


接下来的是重点,首先是启动的时候从数据库中动态读取权限,然后配置到xml中。


启动读取数据库配置权限


package com.liaoliao.shiro;


import java.util.List;
import org.apache.shiro.config.Ini;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.liaoliao.admin.entity.Navigation;
import com.liaoliao.admin.entity.Permission;
import com.liaoliao.admin.service.NavigationService;
import com.liaoliao.admin.service.PermissionService;


public class ShiroPermissionFactory extends ShiroFilterFactoryBean {  
    
    /**配置中的过滤链*/  
    public static String filterChainDefinitions;  
      
    @Autowired  
    private NavigationService navigationService; 
    
    @Autowired
    private PermissionService permissionService;
  
    /** 
     * 从数据库动态读取权限 
     */  
    @Override  
    public void setFilterChainDefinitions(String definitions) {
        ShiroPermissionFactory.filterChainDefinitions = definitions;  
        //数据库动态权限  
        List<Navigation> navigations = navigationService.findAll();  
        for(Navigation na : navigations){  
            //字符串拼接权限  
            if(na.getNavigationUrl() != null && !("".equals(na.getNavigationUrl()))){
                List<Permission> permissions = permissionService.findByNavigationId(na.getId());
                String pers="";
                if(permissions!=null && permissions.size() == 1){
                    for(Permission p:permissions){
                        pers+=p.getAdminGroup().getGroupName()+",";
                    }
                    pers = pers.substring(0,pers.length()-1);
                    definitions = definitions+na.getNavigationUrl() + " = "+"authc,roles["+pers+"]";  
                }
                if(permissions!=null && permissions.size()>1){
                    for(Permission p:permissions){
                        pers+=p.getAdminGroup().getGroupName()+",";
                    }
                    pers = pers.substring(0,pers.length()-1);
                    definitions = definitions+na.getNavigationUrl() + " = "+"authc,anyRoles["+pers+"]";  
                }
                if(permissions==null || permissions.size()==0){
                    definitions = definitions+na.getNavigationUrl() + " = "+"authc,roles[超级管理员]";
                }
            }
            definitions+="\n";
        }
        System.out.println(definitions);
        //从配置文件加载权限配置  
        Ini ini = new Ini();  
        ini.load(definitions);  
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);  
        if (CollectionUtils.isEmpty(section)) {  
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);  
        }  
        //加入权限集合  
        setFilterChainDefinitionMap(section);  
    }  
} 



这里要注意的就是filterChainDefinitions这个变量名不能变,要跟shiro.xml中的权限name对应。我在这里是通过用户组名给每个url分配权限的。
比如说用户列表(/sys/userList)这个功能,我分配给你admin这个用户组没分配给user这个用户组,那么在这里,通过遍历出来的就是
/sys/userList authc,roles[admin];如果权限列表(/sys/permissionList),分配给了admin和user这两个用户组,那么遍历出来的就是:
/sys/permissionList authc,anyRoles[admin,user];到这里的时候,anyRoles里面还是且的关系,也就是说,你一个账号必须即是admin这个用户组
的,还必须是user这个用户组的,就不怎么合理,下面会讲解怎么处理这种情况。
在这里有两种存储方式,一种就是我写的这种,拼接字符串;还有种就是通过创建Map,然后通过key,value存储。
我这里用的就是拼接字符串,拼接字符串一定要注意的一个问题就是加个换行符。不然就会报些乱七八糟的错,我这里报的是:


org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'accountAction': Unsatisfied dependency expressed through field 'redisService';
 nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'redisService':
 Unsatisfied dependency expressed through field 'redisTemplate'; 
 nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
 No qualifying bean of type 'org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.String>' available:
 expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:443)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:325)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4745)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:596)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1805)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)


如果不写CustomRolesAuthorizationFilter这个文件,在这里可以实现:a,b两个用户组访问一个url,a中的用户可以访问,b中的用户不可以访问。
但是不能实现:比如说我这里有三个用户组a、b、c;两个url:sys/userList、sys/adminList;其中sys/userList可以被a,b中的用户组访问,sys/adminList可以被a,b,c中的用户访问;
无法限制c用户访问第一个url;所以就需要重写一个方法:isAccessAllowed,我用这个方法重写了一个anyRoles,这个方法的作用是实现了且的功能,在roles里面如果你写成roles[a,b];
那么你必须需要a和b的权限,才能访问这个路径,但是正常情况都是,a和b只要有一个就可以访问了。所以这里重写了isAccessAllowed方法,方法如下:


CustomRolesAuthorizationFilter方法


package com.liaoliao.shiro;


import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
import org.apache.shiro.subject.Subject;  
import org.apache.shiro.web.filter.authz.AuthorizationFilter;  
  
// AuthorizationFilter抽象类事项了javax.servlet.Filter接口,它是个过滤器。  
public class CustomRolesAuthorizationFilter extends AuthorizationFilter {  
  
    @Override  
    protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {  
        Subject subject = getSubject(req, resp);  
        String[] rolesArray = (String[]) mappedValue;  
        if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问  
            return true;  
        }  
        for (int i = 0; i < rolesArray.length; i++) {  
            if (subject.hasRole(rolesArray[i])) { //若当前用户是rolesArray中的任何一个,则有权限访问  
                return true;  
            }  
        }  
        return false;  
    }  
}


这里就实现了或的功能,既能admin访问,又能user访问。
这样,动态读取数据库配置权限就讲解完了,下次有时候我会把动态更新配置写出来,欢迎小伙伴奉上鲜花鸡蛋~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容