美文网首页SpringBootSpringBoot
springboot Shiro权限管理

springboot Shiro权限管理

作者: 意识流丶 | 来源:发表于2017-03-11 16:06 被阅读2242次

    Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。

    集成Shiro核心分析

    集成Shiro的话,我们需要知道Shiro框架大概的一些管理对象。

    第一:ShiroFilterFactory,Shiro过滤器工厂类,具体的实现类是:ShiroFilterFactoryBean,此实现类是依赖于SecurityManager安全管理器。
    第二:SecurityManager,Shiro的安全管理,主要是身份认证的管理,缓存管理,cookie管理,所以在实际开发中我们主要是和SecurityManager进行打交道的,ShiroFilterFactory主要配置好了Filter就可以了。当然SecurityManager并进行身份认证缓存的实现,我们需要进行对应的编码然后进行注入到安全管理器中。
    第三:Realm,用于身份信息权限信息的验证。
    第四:其它的就是缓存管理,记住登录之类的,这些大部分都是需要自己进行简单的实现,然后注入到SecurityManager让Shiro的安全管理器进行管理就好了。

    集成shiro

    1.pom.xml中添加Shiro依赖,版本(Nov 10, 2016)

    <!--shiro依赖-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0-RC2</version>
    </dependency>
    

    2.注入Shiro Factory和SecurityManager

    在Spring中注入类都是使用配置文件的方式,在Spring Boot中是使用注解的方式
    Shiro核心的类,第一就是ShiroFilterFactory,第二就是SecurityManager
    初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager

    // 必须设置 SecurityManager 
           shiroFilterFactoryBean.setSecurityManager(securityManager);
    

    我们先简单设置好securityManager(),后面还需要其他的注入

    @Configuration
    public class ShiroConfiguration {
    @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            System.out.println("ShiroConfiguration.shirFilter()");
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //拦截器.
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
    
            //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainDefinitionMap.put("/logout", "logout");
    
            //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
            //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
            filterChainDefinitionMap.put("/**", "authc");
    
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //未授权界面;
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    @Bean
        public SecurityManager securityManager(){
           DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
           return securityManager;
        }
    }
    

    上一篇文章已经写好了实体类
    http://www.jianshu.com/p/b5fde9cd2ffb
    接下来就写DAO类来访问数据

    public interface UserInfoDao extends CrudRepository<UserInfo,Long>{
        /**通过username查找用户信息;*/
        public UserInfo findByUsername(String username);
    }
    

    业务处理接口和实现类

    public interface UserInfoService {
        /**通过username查找用户信息;*/
        public UserInfo findByUsername(String username);
    }
    
    @Service
    public class UserInfoServiceImpl implements UserInfoService {
        @Resource
        private UserInfoDao userInfoDao;
        @Override
        public UserInfo findByUsername(String username) {
            System.out.println("UserInfoServiceImpl.findByUsername()");
            return userInfoDao.findByUsername(username);
        }
    }
    

    重点:shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm。

    继承AuthorizingRealm主要需要实现两个方法:
    doGetAuthenticationInfo()doGetAuthorizationInfo();
    关于获取的权限信息了,通过userInfo.getRoleList()可以获取到对应的角色信息,然后在通过对应的角色可以获取到权限信息,当然这些都是JPA帮我们实现了,我们也可以进行直接获取到权限信息。在MyShiroRealm类中会写到

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
    
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
            for(SysRole role:userInfo.getRoleList()){
                //进行角色的添加
                authorizationInfo.addRole(role.getRole());
                for(SysPermission p:role.getPermissions()){
                    //权限的添加
                    authorizationInfo.addStringPermission(p.getPermission());
                }
            }
            return authorizationInfo;
        }
    

    doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。

    doGetAuthenticationInfo()主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。

    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户名
                    userInfo.getPassword(), //密码
                    ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                    getName()  //realm name
            );
    

    完整的MyShiroRealm类

    public class MyShiroRealm extends AuthorizingRealm {
        @Resource
        private UserInfoService userInfoService;
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
    
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
            for(SysRole role:userInfo.getRoleList()){
                authorizationInfo.addRole(role.getRole());
                for(SysPermission p:role.getPermissions()){
                    authorizationInfo.addStringPermission(p.getPermission());
                }
            }
            return authorizationInfo;
        }
    
        /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
            System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
            //获取用户的输入的账号.
            String username = (String)token.getPrincipal();
            System.out.println(token.getCredentials());
    
            //通过username从数据库中查找 User对象,如果找到,没找到.
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            UserInfo userInfo = userInfoService.findByUsername(username);
            System.out.println("----->>userInfo="+userInfo);
            if(userInfo == null){
                return null;
            }
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户名
                    userInfo.getPassword(), //密码
                    ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                    getName()  //realm name
            );
            return authenticationInfo;
        }
    
    }
    

    有一个很重要的步骤就是将我们自定义的Realm注入到Shiro的核心类SecurityManager中。

    首先先把myShiroRealm的bean注入到ShiroConfiguration

    @Bean
        public MyShiroRealm myShiroRealm(){
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            return myShiroRealm;
        }
    

    再将myShiroRealm注入到securityManager中:

    @Bean
        public SecurityManager securityManager(){
           DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
           //设置realm.
           securityManager.setRealm(myShiroRealm());
           return securityManager;
        }
    

    如果我们这时候登录访问http://127.0.0.1:8080/index
    会自动跳转到http://127.0.0.1:8080/login 界面,然后输入账号和密码:admin/123456,这时侯是不能通过的
    我们在上面进行了密文的方式,加密方式并没有告诉Shiro,所以认证失败了。
    在这里我们需要编写一个加密算法类,当然Shiro也已经有了具体的实现HashedCredentialsMatcher

    ShiroConfiguration加入hashedCredentialsMatcher()方法

    /**
         * 凭证匹配器
         * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
         * )
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher(){
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
    
            return hashedCredentialsMatcher;
        }
    

    myShiroRealm()方法中注入凭证匹配器:

    @Bean
        public MyShiroRealm myShiroRealm(){
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myShiroRealm;
        }
    

    开启权限控制,最重要就是开启shiro aop注解支持,然后就可以在controller方法中加入相应的注解:

    /**
         *  开启shiro aop注解支持.
         *  使用代理方式;所以需要开启代码支持;
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    

    到这里的话身份认证权限控制基本是完成了
    下面写下controller
    HomeController中添加login post处理:

    也就是说login()方法要有Get和Post两种访问方法,不然的话index跳转不到login页面
    @PostMapping("/login")
        public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
            System.out.println("HomeController.login()");
            // 登录失败从request中获取shiro处理的异常信息。
            // shiroLoginFailure:就是shiro异常类的全类名.
            String exception = (String) request.getAttribute("shiroLoginFailure");
    
            System.out.println("exception=" + exception);
            String msg = "";
            if (exception != null) {
                if (UnknownAccountException.class.getName().equals(exception)) {
                    System.out.println("UnknownAccountException -- > 账号不存在:");
                    msg = "UnknownAccountException -- > 账号不存在:";
                } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                    System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                    msg = "IncorrectCredentialsException -- > 密码不正确:";
                } else if ("kaptchaValidateFailed".equals(exception)) {
                    System.out.println("kaptchaValidateFailed -- > 验证码错误");
                    msg = "kaptchaValidateFailed -- > 验证码错误";
                } else {
                    msg = "else >> "+exception;
                    System.out.println("else -- >" + exception);
                }
            }
            map.put("msg", msg);
            // 此方法不处理登录成功,由shiro进行处理
            return"/login";
        }
    

    我们新建一个UserInfoController
    @RequiresPermissions是开启shiro aop注解支持后才能使用的

    @Controller
    @RequestMapping("/userInfo")
    public class UserInfoController {
    
        /**
         * 用户查询.
         * @return
         */
        @RequestMapping("/userList")
        public String userInfo(){
            return "userInfo";
        }
    
        /**
         * 用户添加;
         * @return
         */
        @RequestMapping("/userAdd")
        @RequiresPermissions("userInfo:add")//权限管理;
        public String userInfoAdd(){
            return "userInfoAdd";
        }
    
        /**
         * 用户删除;
         * @return
         */
        @RequestMapping("/userDel")
        @RequiresPermissions("userInfo:del")//权限管理;
        public String userDel(){
            return "userInfoDel";
        }
    }
    

    下面你可以进行测试:
    访问http://127.0.0.1:8080/userInfo/userAdd或者http://127.0.0.1:8080/userInfo/userDel
    或者http://127.0.0.1:8080/userInfo/userList
    都会跳到
    http://127.0.0.1:8080/login
    你只有登录了才可以访问userAdd,userDel和userList

    文章主要参考于作者林祥纤的博客

    http://412887952-qq-com.iteye.com/blog/2299777

    相关文章

      网友评论

        本文标题:springboot Shiro权限管理

        本文链接:https://www.haomeiwen.com/subject/hddggttx.html