美文网首页SpringBootSpringBoot权限安全
SpringBoot + Shiro (二)身份校验和角色设置

SpringBoot + Shiro (二)身份校验和角色设置

作者: 忧郁的小码仔 | 来源:发表于2017-07-05 10:25 被阅读915次

    最终demo

    开始之前,我们先造一些数据:

    INSERT INTO test.sys_permission VALUES ('1', 1, '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
    INSERT INTO test.sys_permission VALUES ('2', 1, '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
    INSERT INTO test.sys_permission VALUES ('3', 1, '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
    
    INSERT INTO test.sys_role VALUES ('1', 1, '管理员', 'admin');
    INSERT INTO test.sys_role VALUES ('2', 1, 'VIP会员', 'vip');
    
    INSERT INTO test.user_info (uid,username,name,password,salt,state) VALUES ('1', 'admin','管理员' , 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
    
    INSERT INTO test.sys_role_permission VALUES ('1', '1');
    INSERT INTO test.sys_role_permission VALUES ('1', '2');
    
    INSERT INTO test.sys_user_role VALUES ('1', '1');
    

    数据有了,下面开始Shrio的配置学习。
    开始之前先添加Shiro依赖

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.2.2</version>
            </dependency>
    

    1.Realms

    Realm是一个Dao,通过它来验证用户身份和权限。这里Shiro不做权限的管理工作,需要我们自己管理用户权限,只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。
    config包下再建一个包Shiro,然后在Shiro包下建一个MyShiroRealm类,继承AuthorizingRealm抽象类。

    public class MyShiroRealm extends AuthorizingRealm {
    
        @Resource
        private UserInfoService userInfoService;
    
    
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            System.out.println("开始身份验证");
            String username = (String) token.getPrincipal(); //获取用户名,默认和login.html中的username对应。
            UserInfo userInfo = userInfoService.findByUsername(username);
    
            if (userInfo == null) {
                //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
                return null;
            }
    
      //验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户信息
                    userInfo.getPassword(), //密码
                    getName() //realm name
            );
            authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userInfo.getCredentialsSalt())); //设置盐
    
            return authenticationInfo;
        }
    
    //当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
            System.out.println("开始权限配置");
    
            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;
        }
    }
    
    

    重载以上两个方法来配置用户身份验证和权限验证。

    别忘了在Service包下新建个UserInfoService和它的实现类:

    public interface UserInfoService {
    
        public UserInfo findByUsername(String username);
    }
    
    @Service
    public class UserInfoServiceImpl implements UserInfoService {
    
        @Resource
        private UserInfoRepository userInfoRepository;
    
        @Override
        public UserInfo findByUsername(String username) {
    
            System.out.println("UserInfoServiceImpl.findByUsername");
            return userInfoRepository.findByUsername(username);
        }
    }
    

    dao层下新建一个UserInfo的repository

    public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {
    
        public UserInfo findByUsername(String username);
    
        public UserInfo save(UserInfo userInfo);
    }
    
    

    2.接下来配置Shiro的关键部分

    这里要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。

    @Configuration
    public class ShiroConfiguration {
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            filterChainDefinitionMap.put("/logout", "logout");
            filterChainDefinitionMap.put("/favicon.ico", "anon");
            filterChainDefinitionMap.put("/**", "authc");
            //authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。
    
    
            shiroFilterFactoryBean.setLoginUrl("/login");
            shiroFilterFactoryBean.setSuccessUrl("/index");
    //        shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //这里设置403并不会起作用,参考http://www.jianshu.com/p/e03f5b54838c
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
    
        //SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)  
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm()); //将Realm注入到SecurityManager中。
            return securityManager;
        }
    
        @Bean
        public MyShiroRealm myShiroRealm() {
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); //设置解密规则
            return myShiroRealm;
        }
    
       //因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
            return hashedCredentialsMatcher;
        }
    }
    
    

    关于为什么设置filterChainDefinitionMap.put("/favicon.ico", "anon");,请参考Shiro登录后下载favicon.ico问题

    3.修改我们的HomeController中的/login请求

        // 这里如果不写method参数的话,默认支持所有请求,如果想缩小请求范围,还是要添加method来支持get, post等等某个请求。
        @RequestMapping("/login")
        public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
    
            System.out.println("HomeController.login");
    
            // 登录失败从request中获取shiro处理的异常信息。
            // shiroLoginFailure:就是shiro异常类的全类名.
            Object exception = request.getAttribute("shiroLoginFailure");
            String msg = "";
            if (exception != null) {
                if (UnknownAccountException.class.isInstance(exception)) {
                    System.out.println("账户不存在");
                    msg = "账户不存在或密码不正确";
                } else if (IncorrectCredentialsException.class.isInstance(exception)) {
                    System.out.println("密码不正确");
                    msg = "账户不存在或密码不正确";
                } else {
                    System.out.println("其他异常");
                    msg = "其他异常";
                }
            }
    
            map.put("msg", msg);
            // 此方法不处理登录成功,由shiro进行处理.
            return "login";
        }
    

    这里@RequestMapping之所以没加method是因为如果用户没登录,Shiro会调用get方法请求/login,而后面我们在login页面会用post请求发送form表单,所以这里就没设置method(默认支持所有请求)。

    好,启动项目。这时候我们再访问http://localhost:8080/index会跳转到登录页面,因为我们设置了filterChainDefinitionMap.put("/**", "authc");,所以要先验证身份才能访问。同时因为设置了shiroFilterFactoryBean.setLoginUrl("/login");,所以会跳转到登录页面。这时候在登录页面输入正确的用户名密码就可以登录了,登录成功会自动跳转到index页面。

    这一节先到此为止。

    到此的项目结构:

    项目结构

    SpringBoot + Shiro (一)基础工程搭建
    SpringBoot + Shiro (二)身份校验和角色设置
    SpringBoot + Shiro (三)权限
    SpringBoot + Shiro (四)缓存&记住密码
    SpringBoot + Shiro (五)验证码

    最后,感谢几位作者的文章解惑:
    springboot整合shiro-登录认证和权限管理
    Spring Boot Shiro权限管理【从零开始学Spring Boot】
    Spring boot 中使用Shiro

    相关文章

      网友评论

      • 白色灬风车:因为我看了好几个 都是i这种情况 直接把controller的login方法subject的login(token)弄没了
      • 白色灬风车:博主啊。Login登陆为啥没有接收username password呢?也没有subject的lging(token)方法???求解答
      • bc9b09cde488:filterChainDefinitionMap.put("/**", "authc");这句话加上就报错
        忧郁的小码仔:@陌乐儿 你要访问什么静态文件,被拦截了?
        bc9b09cde488:所有的静态文件也被拦截了,
        忧郁的小码仔:@陌乐儿 报什么错?

      本文标题:SpringBoot + Shiro (二)身份校验和角色设置

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