7. 整合shiro,搭建粗粒度权限管理

时间:2022-09-23 21:37:02

shiro是一个易用的权限管理框架,只需提供一个Realm即可在项目中使用,本文就将结合上一篇中搭建的权限模块、角色模块和用户模块来搭建一个粗粒度的权限管理系统,具体如下:
1. 添加shiro依赖和与thymeleaf集成的依赖,并更新项目:

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
 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> 
pom.xml

2. 添加自定义Realm类,主要完成用户校验和授权的功能

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
 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 }
ShiroRealm

3. 添加ShiroConfiguration类,完成shiro的配置

7. 整合shiro,搭建粗粒度权限管理7. 整合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 }
ShiroConfiguration

因为添加了filterChainDefinitionMap.put("/**", "authc"),所以shiro会对所有请求进行验证,包括静态资源,所以需要通过如: filterChainDefinitionMap.put("/css/**", "anon");这样的来使静态资源请求不被拦截。通过这三个步骤就已经把shiro配置好了,下面就完成登录部分:
4. 添加登录action

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
 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 }
AdminController

5. 添加登录页

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
 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>
login.html

其中在css中使用url可以按照正常的使用,不用按照html中添加资源的方式。

7. 整合shiro,搭建粗粒度权限管理

登录成功后就会跳转到/admin/页

7. 整合shiro,搭建粗粒度权限管理

6. 因为在上一篇文章中以及添加好了角色授权和给用户赋角色,而现在要做的就是使用shiro来对界面做粗粒度权限管理,shiro在验证授权时需要获得用户的角色列表和权限列表,所以首先就修改User.java,增加获取角色和权限的接口,代码如下:

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
  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 }
User.java

使用时通过getRolesAndPermissions方法获得用户的角色集合和权限集合,已经在ShiroRealm中添加好了。然后就是在页面上使用权限管理;
7. 这儿以不同角色用户登录后,在用户管理界面上展示对操作权限的限制为例,用户管理界面的代码如下:

7. 整合shiro,搭建粗粒度权限管理7. 整合shiro,搭建粗粒度权限管理
  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>
index.html

首先要在HTML标记中添加<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">,然后在需要控制权限的界面元素上通过shiro:hasPermission="xxx"来验证当前用户是否有权限操作该元素,如上就完成了粗粒度的权限管理,下面是分别有所有权限和只有查询和刷新权限的界面:

7. 整合shiro,搭建粗粒度权限管理

7. 整合shiro,搭建粗粒度权限管理

代码下载路径:https://pan.baidu.com/s/1VqcDk9BIM10-HVyfmrjMHg, 密码:vxtg