shiro是apache的一款开元安全框架,可以进行用户身份的认证,授权,session管理,加密等。相对于springsecurity来说比较简单,容易理解和使用。下来我们实现在springboot项目中使用shiro框架。
我们使用RBAC(基于角色的访问控制)来设计角色权限关系,有3个实体:用户,角色,权限,在RBAC中用户拥有某一种角色,角色拥有一个或者多个资源,所以表结构设计如下:
1 用户信息 2 DROP TABLE IF EXISTS `user_info`; 3 CREATE TABLE `user_info` ( 4 `id` int(11) NOT NULL AUTO_INCREMENT, 5 `username` varchar(50) DEFAULT NULL, 6 `password` varchar(32) DEFAULT NULL, 7 `email` varchar(50) DEFAULT NULL, 8 `usertype` char(1) DEFAULT NULL, 9 PRIMARY KEY (`id`)10 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;11 12 角色信息13 DROP TABLE IF EXISTS `role_info`;14 CREATE TABLE `role_info` (15 `id` int(11) NOT NULL AUTO_INCREMENT,16 `rolename` varchar(50) DEFAULT NULL,17 `roledesc` varchar(100) DEFAULT NULL,18 PRIMARY KEY (`id`)19 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;20 21 用户角色关系22 DROP TABLE IF EXISTS `user_role`;23 CREATE TABLE `user_role` (24 `id` int(11) NOT NULL AUTO_INCREMENT,25 `userid` int(11) NOT NULL,26 `roleid` int(11) NOT NULL,27 PRIMARY KEY (`id`)28 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;29 30 资源(权限)信息31 DROP TABLE IF EXISTS `resource_info`;32 CREATE TABLE `resource_info` (33 `id` int(11) NOT NULL AUTO_INCREMENT,34 `resourceName` varchar(50) NOT NULL,35 `resourceUrl` varchar(50) NOT NULL,36 `resourceType` char(1) NOT NULL COMMENT '1-菜单;2-按钮',37 `resourcePid` int(11) DEFAULT NULL COMMENT '上级,按钮上级为菜单',38 PRIMARY KEY (`id`)39 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;40 41 角色资源关系42 DROP TABLE IF EXISTS `role_resource`;43 CREATE TABLE `role_resource` (44 `id` int(11) NOT NULL AUTO_INCREMENT,45 `roleId` int(11) NOT NULL,46 `resourceId` int(11) NOT NULL,47 PRIMARY KEY (`id`)48 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
下来实现springboot项目与shiro的整合,创建springboot项目并使用mybatis做数据库访问,不清楚springboot整合mybatis的朋友可以看我之前的笔记,我们假设已经有了项目,这里只说整合shiro。
1 在pom文件中添加对shiro的依赖
org.apache.shiro shiro-spring 1.4.0
2 创建shiro配置信息类,完成配置
1 @Configuration 2 public class ShiroConfig { 3 4 /** 5 * shiro 过滤器 6 * @param securityManager 7 * @return 8 */ 9 @Bean 10 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ 11 ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); 12 shiroFilter.setSecurityManager(securityManager); 13 shiroFilter.setLoginUrl("/login");//登录失败后默认跳转到登录地址去 14 shiroFilter.setSuccessUrl("/main");//登录成功后跳转地址 15 16 /** 17 * 过滤器链, 从前往后 顺序判断 18 */ 19 MapfilterChain = new LinkedHashMap (); 20 //记住我或者认证登录通过可以访问 21 filterChain.put("/main", "user"); 22 23 filterChain.put("/js/**", "anon");//设置静态资源可以匿名访问 24 filterChain.put("/logout", "logout");//logout shiro已经做了实现 25 filterChain.put("/login", "anon");//可匿名访问 26 27 filterChain.put("/**", "authc");// 除了上边配置的路径及资源外,其他访问都需要认证,此项必须放在最后 28 shiroFilter.setFilterChainDefinitionMap(filterChain); 29 30 shiroFilter.setUnauthorizedUrl("/unAuthPage");//没有授权的页面 31 return shiroFilter; 32 } 33 34 /** 35 * 安全管理器 36 * @return 37 */ 38 @Bean 39 public DefaultWebSecurityManager securityManager(){ 40 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 41 securityManager.setRealm(getShiroRealm());//自定义realm 42 securityManager.setRememberMeManager(rememberMeManager());//记住我管理器 43 return securityManager; 44 } 45 46 /** 47 * 自定义的realm 48 * @return 49 */ 50 @Bean 51 public ShiroRealm getShiroRealm(){ 52 ShiroRealm shiroRealm = new ShiroRealm(); 53 shiroRealm.setCredentialsMatcher(credentialsMatcher());//凭证匹配器 54 return shiroRealm; 55 } 56 57 /** 58 * 凭证匹配器 (密码的校验交给了shiro的SimpleAuthenticationInfo处理) 59 * @return 60 */ 61 @Bean 62 public HashedCredentialsMatcher credentialsMatcher(){ 63 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); 64 credentialsMatcher.setHashAlgorithmName("MD5");//散列算法名称,如:md5 65 credentialsMatcher.setHashIterations(2);//散列算法的次数 66 return credentialsMatcher; 67 } 68 69 /** 70 * 记住我管理器 71 * @return 72 */ 73 @Bean 74 public CookieRememberMeManager rememberMeManager(){ 75 CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); 76 rememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag==")); 77 rememberMeManager.setCookie(cookie()); 78 return rememberMeManager; 79 } 80 81 /** 82 * 记住我 cookie 83 * @return 84 */ 85 @Bean 86 public SimpleCookie cookie(){ 87 SimpleCookie rememberMeCookie = new SimpleCookie("rememberMe"); 88 rememberMeCookie.setHttpOnly(true); 89 rememberMeCookie.setMaxAge(60);//记住我生效时间,单位 秒 90 return rememberMeCookie; 91 } 92 93 /** 94 * shiro生命周期 95 * @return 96 */ 97 @Bean 98 public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { 99 return new LifecycleBeanPostProcessor();100 }101 102 }
2 自定义的realm,实现认证和授权
1 public class ShiroRealm extends AuthorizingRealm { 2 3 @Autowired 4 private UserInfoService userInfoService; 5 @Autowired 6 private UserRoleService userRoleService; 7 @Autowired 8 private ResourceInfoService resourceInfoService; 9 10 /**11 * 授权12 */13 @Override14 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {15 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();16 SetpermissionsSet = new HashSet ();//用来存放用户所拥有的权限资源17 //当前用户18 UserInfoEntity userInfo = (UserInfoEntity) principals.getPrimaryPrincipal();//如果认证方法中保存的是用户账号,此处获取的是用户的账号,如果存的是用户对象,则此处获取的是用户对象19 //当前用户角色20 UserRole userRole = userRoleService.selectByUserId(userInfo.getId());21 if(userRole != null){22 List resourceList = resourceInfoService.selectByRoleId(userRole.getRoleId());23 if(resourceList != null && resourceList.size()>0){24 for(int i=0; i
3 登录实现
1 @Controller 2 public class LoginController { 3 4 private static final Logger logger = LogManager.getLogger(LoginController.class); 5 6 @RequestMapping("/login") 7 public String login(){ 8 logger.info("跳转至登录页。。。"); 9 return "login";10 }11 12 @RequestMapping(value="/login",method=RequestMethod.POST)13 @ResponseBody14 public String doLogin(@RequestParam("username") String username, @RequestParam("password") String password, 15 @RequestParam(value = "rememberMeParam", required = false) String rememberMeParam, Mapmap){16 UsernamePasswordToken token = new UsernamePasswordToken(username, password);17 18 //记住我19 if("Y".equals(rememberMeParam)){20 token.setRememberMe(true);21 }22 23 Subject subject = SecurityUtils.getSubject();24 try {25 subject.login(token);26 } catch (UnknownAccountException e) {27 //e.printStackTrace();28 logger.info("账号不存在");29 } catch (IncorrectCredentialsException e) {30 //e.printStackTrace();31 logger.info("密码不正确");32 }33 34 if(subject.isAuthenticated()){35 map.put("result", "success");36 logger.info("登录成功。。。");37 }else{38 map.put("result", "faile");39 map.put("msg", "账号或密码错误");40 }41 return JSON.toJSONString(map);42 }43 44 @RequestMapping("/main")45 public String main(Model model){46 Subject subject = SecurityUtils.getSubject();47 UserInfoEntity userInfo = (UserInfoEntity) subject.getPrincipal();48 model.addAttribute("currentUser", userInfo);49 return "main";50 }51 52 @RequestMapping("/unAuthPage")53 public String unAuthPage(){54 return "unAuth";55 }56 57 @RequestMapping("/logout")58 public String logout(){59 Subject subject = SecurityUtils.getSubject();60 subject.logout();61 return "redirect:/login";62 }63 64 }
4 退出 退出功能shiro已经为我做了实现,我们只需要调用Subject的logout()方法即可实现用户退出。
5 测试
在没有登录的情况下,如果我们访问main方法,会被shiroFilter拦截,并将请求转向loginUrl方法引导用户登录;登录成功后会跳转至successUrl;
在页面上我们使用shiro的标签来做权限过滤,需要在页面引入shiro标签库:<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>,可以在菜单或者按钮上加上权限过滤标签如:
在加载页面时,遇到shiro标签时会去后台查询用户权限信息,如果用户拥有当前标签中name属性指定的权限信息时会加载并显示当前shiro标签包含的页面元素信息,否则不会显示