shiro是一个易用的权限管理框架,只需提供一个Realm即可在项目中使用,本文就将结合上一篇中搭建的权限模块、角色模块和用户模块来搭建一个粗粒度的权限管理系统,具体如下:
1. 添加shiro依赖和与thymeleaf集成的依赖,并更新项目:
1 <dependency> 2 <groupId>org.apache.shiro</groupId> 3 <artifactId>shiro-spring</artifactId> 4 <version>1.4.0</version> 5 </dependency> 6 <dependency> 7 <groupId>com.github.theborakompanioni</groupId> 8 <artifactId>thymeleaf-extras-shiro</artifactId> 9 <version>1.0.2</version> 10 </dependency>
2. 添加自定义Realm类,主要完成用户校验和授权的功能
1 package com.lvniao.blog.config; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 import javax.annotation.Resource; 7 8 import org.apache.shiro.authc.AuthenticationException; 9 import org.apache.shiro.authc.AuthenticationInfo; 10 import org.apache.shiro.authc.AuthenticationToken; 11 import org.apache.shiro.authc.SimpleAuthenticationInfo; 12 import org.apache.shiro.authc.UsernamePasswordToken; 13 import org.apache.shiro.authz.AuthorizationInfo; 14 import org.apache.shiro.authz.SimpleAuthorizationInfo; 15 import org.apache.shiro.realm.AuthorizingRealm; 16 import org.apache.shiro.subject.PrincipalCollection; 17 import org.apache.shiro.util.ByteSource; 18 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.stereotype.Component; 20 21 import com.lvniao.blog.mapper.UserMapper; 22 import com.lvniao.blog.model.User; 23 24 @Component 25 public class ShiroRealm extends AuthorizingRealm { 26 27 @Autowired 28 private UserMapper userMapper; 29 30 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 31 String username = (String)principals.getPrimaryPrincipal(); 32 User user = userMapper.getUserWithRoleByName(username); 33 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 34 Set<String> roles = new HashSet<String>(); 35 Set<String> permissions = new HashSet<String>(); 36 user.getRolesAndPermissions(roles, permissions); 37 authorizationInfo.setRoles(roles); 38 authorizationInfo.setStringPermissions(permissions); 39 return authorizationInfo; 40 } 41 42 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 43 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token; 44 String username = (String)usernamePasswordToken.getPrincipal(); 45 String password = new String(usernamePasswordToken.getPassword()); 46 if(userMapper.check(username, password) != null) { 47 User user = userMapper.getUserByName(username); 48 49 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getName(), 50 user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName()); 51 52 return authenticationInfo; 53 } else { 54 throw new AuthenticationException(); 55 } 56 } 57 }
3. 添加ShiroConfiguration类,完成shiro的配置
1 package com.lvniao.blog.config; 2 3 import java.util.LinkedHashMap; 4 import java.util.List; 5 import java.util.Map; 6 7 import org.apache.shiro.mgt.SessionsSecurityManager; 8 import org.apache.shiro.realm.Realm; 9 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 10 import org.apache.shiro.web.mgt.CookieRememberMeManager; 11 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 12 import org.springframework.beans.factory.annotation.Qualifier; 13 import org.springframework.context.annotation.Bean; 14 import org.springframework.context.annotation.Configuration; 15 import org.apache.shiro.mgt.SecurityManager; 16 17 @Configuration 18 public class ShiroConfiguration{ 19 20 @Bean 21 public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { 22 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 23 24 // 必须设置 SecurityManager 25 shiroFilterFactoryBean.setSecurityManager(securityManager); 26 27 shiroFilterFactoryBean.setLoginUrl("/admin/login"); 28 29 shiroFilterFactoryBean.setSuccessUrl("/admin/"); 30 31 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); 32 filterChainDefinitionMap.put("/css/**", "anon"); 33 filterChainDefinitionMap.put("/js/**", "anon"); 34 filterChainDefinitionMap.put("/images/**", "anon"); 35 filterChainDefinitionMap.put("/fonts/**", "anon"); 36 filterChainDefinitionMap.put("/admin/loginaction", "anon"); 37 38 filterChainDefinitionMap.put("/**", "authc"); 39 40 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 41 System.out.println("Shiro拦截器工厂类注入成功"); 42 return shiroFilterFactoryBean; 43 } 44 45 @Bean 46 public SecurityManager securityManager() { 47 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 48 // 设置realm. 49 securityManager.setRealm(ShiroRealm()); 50 return securityManager; 51 } 52 53 @Bean 54 public ShiroRealm ShiroRealm() { 55 ShiroRealm shiroRealm = new ShiroRealm(); 56 return shiroRealm; 57 } 58 59 @Bean 60 public ShiroDialect shiroDialect() { 61 return new ShiroDialect(); 62 } 63 }
因为添加了filterChainDefinitionMap.put("/**", "authc"),所以shiro会对所有请求进行验证,包括静态资源,所以需要通过如: filterChainDefinitionMap.put("/css/**", "anon");这样的来使静态资源请求不被拦截。通过这三个步骤就已经把shiro配置好了,下面就完成登录部分:
4. 添加登录action
1 package com.lvniao.blog.admin.controller; 2 3 import java.util.List; 4 5 import org.apache.shiro.SecurityUtils; 6 import org.apache.shiro.authc.IncorrectCredentialsException; 7 import org.apache.shiro.authc.UnknownAccountException; 8 import org.apache.shiro.authc.UsernamePasswordToken; 9 import org.apache.shiro.crypto.hash.Md5Hash; 10 import org.apache.shiro.subject.Subject; 11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.stereotype.Controller; 13 import org.springframework.ui.Model; 14 import org.springframework.web.bind.annotation.RequestMapping; 15 import org.springframework.web.servlet.ModelAndView; 16 17 import com.lvniao.blog.mapper.MenuMapper; 18 import com.lvniao.blog.mapper.UserMapper; 19 import com.lvniao.blog.model.Menu; 20 import com.lvniao.blog.model.User; 21 import com.lvniao.blog.util.Constant; 22 23 @Controller 24 @RequestMapping("/admin") 25 public class AdminController { 26 27 @Autowired 28 private UserMapper userMapper; 29 30 @Autowired 31 private MenuMapper menuMapper; 32 33 @RequestMapping("/") 34 public String index(Model model) { 35 model.addAttribute("menus", menuMapper.getParentMenu()); 36 return "admin/index"; 37 } 38 39 @RequestMapping("/menu") 40 public String menu(Model model) { 41 return "admin/menu1"; 42 } 43 44 @RequestMapping("/login") 45 public String login() { 46 return "admin/login"; 47 } 48 49 @RequestMapping("/loginaction") 50 public ModelAndView loginAction(User user) { 51 Subject subject = SecurityUtils.getSubject(); 52 if(!subject.isAuthenticated()) { 53 54 try { 55 User u = userMapper.getUserByName(user.getName()); 56 if(u != null) { 57 String password = new Md5Hash(user.getPassword(), u.getSalt()).toString(); 58 UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), password); 59 token.setRememberMe(false); 60 subject.login(token); 61 User currentUser = userMapper.getUserByName(user.getName()); 62 subject.getSession().setAttribute(Constant.CurrentUser, currentUser); 63 return new ModelAndView("redirect:/admin/"); 64 } 65 66 } catch(UnknownAccountException ex) { 67 68 } catch (IncorrectCredentialsException ex) { 69 70 } catch (Exception ex) { 71 72 } 73 } 74 return new ModelAndView("redirect:/admin/login"); 75 } 76 }
5. 添加登录页
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>lvniao</title> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6 <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/> 7 <link rel="stylesheet" th:href="@{/css/base.css}"/> 8 <link rel="stylesheet" th:href="@{/css/font-awesome.css}"/> 9 <link rel="stylesheet" th:href="@{/css/login.css}"/> 10 <script th:src="@{/js/jquery-3.2.1.js}"></script> 11 <script th:src="@{/statics/js/base.js}"></script> 12 <style> 13 *{ 14 margin:0px; 15 padding:0px; 16 } 17 body{ 18 background-image:url("/images/loginbg.jpg") ; 19 background-size:100% 100%; 20 } 21 </style> 22 </head> 23 <body> 24 <form class="login" th:action="@{/admin/loginaction}" th:method="post"> 25 <div class="header"> 26 <h1>登录</h1> 27 </div> 28 <div class="text name"> 29 <input type="text" class="input" name="name" placeholder="用户名"/> 30 </div> 31 <div class="text password"> 32 <input type="password" class="input" name="password" placeholder="密码"/> 33 </div> 34 <button type="submit" class="button">登录</button> 35 <div class="foot"> 36 </div> 37 </form> 38 </body> 39 </html>
其中在css中使用url可以按照正常的使用,不用按照html中添加资源的方式。
登录成功后就会跳转到/admin/页
6. 因为在上一篇文章中以及添加好了角色授权和给用户赋角色,而现在要做的就是使用shiro来对界面做粗粒度权限管理,shiro在验证授权时需要获得用户的角色列表和权限列表,所以首先就修改User.java,增加获取角色和权限的接口,代码如下:
1 package com.lvniao.blog.model; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Set; 6 7 import com.lvniao.blog.model.Permission; 8 import com.lvniao.blog.model.Role; 9 import com.lvniao.blog.util.StringUtil; 10 11 public class User { 12 13 private Long id; 14 15 private String name; 16 17 private String password; 18 19 private String salt; 20 21 private Boolean locked; 22 23 private List<Role> roles = new ArrayList<Role>(); 24 25 private String rolesStr = null; 26 27 public Long getId() { 28 return id; 29 } 30 31 public void setId(Long id) { 32 this.id = id; 33 } 34 35 public String getName() { 36 return name; 37 } 38 39 public void setName(String name) { 40 this.name = name; 41 } 42 43 public String getPassword() { 44 return password; 45 } 46 47 public void setPassword(String password) { 48 this.password = password; 49 } 50 51 public String getSalt() { 52 return salt; 53 } 54 55 public void setSalt(String salt) { 56 this.salt = salt; 57 } 58 59 public Boolean isLocked() { 60 return locked; 61 } 62 63 public void setLocked(Boolean locked) { 64 this.locked = locked; 65 } 66 67 public List<Role> getRoles() { 68 return roles; 69 } 70 71 public void setRoles(List<Role> roles) { 72 this.roles = roles; 73 } 74 75 public String getRolesStr() { 76 if(StringUtil.isNullOrEmpty(rolesStr)) { 77 if(getRoles() != null && getRoles().size() > 0) { 78 StringBuffer sb = new StringBuffer(); 79 80 for(Role r : getRoles()) { 81 if(r != null) { 82 sb.append(r.getId()); 83 sb.append(","); 84 } 85 } 86 if(sb.length() > 0) { 87 rolesStr = sb.toString().substring(0, sb.length() - 1); 88 } 89 } 90 } 91 return rolesStr; 92 } 93 94 public void setRolesStr(String rolesStr) { 95 this.rolesStr = rolesStr; 96 } 97 98 public void getRolesAndPermissions(Set<String> roleres, Set<String> permissionres) { 99 getRolesAndPermissions(getRoles(), roleres, permissionres); 100 } 101 102 private void getRolesAndPermissions(List<Role> roles, Set<String> roleres, Set<String> permissionres) { 103 for(Role role : roles) { 104 roleres.add(role.getAlias()); 105 for(Permission p : role.getPermissions()) { 106 permissionres.add(p.getAlias()); 107 } 108 getRolesAndPermissions(role.getChildren(), roleres, permissionres); 109 } 110 } 111 }
使用时通过getRolesAndPermissions方法获得用户的角色集合和权限集合,已经在ShiroRealm中添加好了。然后就是在页面上使用权限管理;
7. 这儿以不同角色用户登录后,在用户管理界面上展示对操作权限的限制为例,用户管理界面的代码如下:
1 <!DOCTYPE html> 2 <html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" 3 xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> 4 <head> 5 <meta charset="UTF-8"/> 6 <title></title> 7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 8 <link rel="stylesheet" th:href="@{/css/page.css}"/> 9 <link rel="stylesheet" type="text/css" th:href="@{/easyui/themes/default/easyui.css}"/> 10 <link rel="stylesheet" type="text/css" th:href="@{/easyui/themes/icon.css}"/> 11 <link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}"/> 12 <link rel="stylesheet" type="text/css" th:href="@{/css/grid.css}"/> 13 <script type="text/javascript" th:src="@{/easyui/jquery.min.js}"></script> 14 <script type="text/javascript" th:src="@{/easyui/jquery.easyui.min.js}"></script> 15 <script type="text/javascript" th:src="@{/layui/layui.js}"></script> 16 <script type="text/javascript" th:src="@{/layui/layui.all.js}"></script> 17 <script type="text/javascript" th:src="@{/js/form.js}"></script> 18 <script> 19 $(function(){ 20 $("#grid").treegrid({url: '/user/list', 21 method: 'get', 22 rownumbers: true, 23 idField: 'id', 24 fit:true, 25 treeField: 'name', 26 pagination: true, 27 pageSize: 10, 28 pageList: [10, 50, 100]}); 29 $("#btnSearch").click(function(){ 30 $("#grid").treegrid({queryParams:form2Json("query")}); 31 }); 32 33 $(".lv-btn-group .lv-btn").click(function(){ 34 var type = $(this).attr("type"); 35 var addr = $(this).attr("addr"); 36 if(type == "refresh"){ 37 $(".lv-txtSearch").val(""); 38 $("#grid").treegrid({queryParams:form2Json("query")}); 39 return; 40 } 41 if(type == "add"){ 42 layer.open({ 43 type: 2, 44 title: $(this).attr("title"), 45 shadeClose: true, 46 shade: 0.8, 47 area: ['330px', '270px'], 48 content: addr, 49 success:function(layero){ 50 $("#grid").treegrid({queryParams:form2Json("query")}); 51 } 52 }); 53 return; 54 } 55 56 57 var row = $("#grid").treegrid("getSelected"); 58 if(row == null){ 59 layer.alert("请选择待操作的数据"); 60 return; 61 } 62 if(type == "assign"){ 63 layer.open({ 64 type: 2, 65 title: $(this).attr("title"), 66 shadeClose: true, 67 shade: 0.8, 68 area: ['330px', '300px'], 69 content: addr + "/" + row.id, 70 success:function(layero){ 71 $("#grid").treegrid({queryParams:form2Json("query")}); 72 } 73 }); 74 return; 75 } 76 if(type == "modify"){ 77 layer.open({ 78 type: 2, 79 title: $(this).attr("title"), 80 shadeClose: true, 81 shade: 0.8, 82 area: ['330px', '270px'], 83 content: addr + "/" + row.id, 84 success:function(layero){ 85 $("#grid").treegrid({queryParams:form2Json("query")}); 86 } 87 }); 88 return; 89 } 90 if(type == "delete"){ 91 $.ajax({ 92 url:addr + "/" + row.id, 93 type:"get", 94 success:function(data){ 95 $("#grid").treegrid({queryParams:form2Json("query")}); 96 } 97 }); 98 return; 99 } 100 101 }); 102 }); 103 </script> 104 </head> 105 <body> 106 <div class="lv-option-container"> 107 <form id="query" shiro:hasPermission="user:query" > 108 <input type="text" class="lv-txtSearch" name="query"/><div class="lv-btnSearch" id="btnSearch">查询</div> 109 </form> 110 111 <div class="lv-btn-group"> 112 <div class="lv-btn" type="refresh" shiro:hasPermission="user:refresh">刷新</div> 113 <div class="lv-btn" type="add" addr="/user/add" shiro:hasPermission="user:add" title="添加">添加</div> 114 <div class="lv-btn" type="modify" addr="/user/modify" shiro:hasPermission="user:modify" title="修改">修改</div> 115 <div class="lv-btn" type="assign" addr="/user/assign" shiro:hasPermission="user:assign" style="width:100px;" title="删除">分配角色</div> 116 <div class="lv-btn" type="delete" addr="/user/delete" shiro:hasPermission="user:delete" title="删除">删除</div> 117 </div> 118 </div> 119 <div class="lv-grid-container"> 120 <table id="grid"> 121 <thead> 122 <tr> 123 <th data-options="field:'name'" width="220">用户名</th> 124 <th data-options="field:'locked', formatter:booleanFormat" width="220">是否锁定</th> 125 </tr> 126 </thead> 127 </table> 128 </div> 129 </body> 130 </html>
首先要在HTML标记中添加<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">,然后在需要控制权限的界面元素上通过shiro:hasPermission="xxx"来验证当前用户是否有权限操作该元素,如上就完成了粗粒度的权限管理,下面是分别有所有权限和只有查询和刷新权限的界面:
代码下载路径:https://pan.baidu.com/s/1VqcDk9BIM10-HVyfmrjMHg, 密码:vxtg