1. 技術選型:
前后端分離的權限檢驗 + SpringBoot2.x + Mysql + Mybatis + Shiro + Redis + IDEA + JDK8
2. RBAC權限控制設計
- 用戶
- 角色(一個用戶可以具有多種角色,一個角色可以被多個用戶使用,用戶與角色為多對多關系)
- 權限(一個角色可以擁有多種權限,一個權限可以歸屬到多種角色,也是N:N關系)
想要獲取一個用戶所能訪問的資源,根據用戶查詢用戶角色,根據用戶角色查詢權限,理清了這個關系,就可以創建數據庫了:
CREATE TABLE `permission` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL COMMENT '名稱', `url` varchar(128) DEFAULT NULL COMMENT '接口路徑', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; INSERT INTO `permission` VALUES (1,'video_update','/api/video/update'),(2,'video_delete','/api/video/delete'),(3,'video_add','/api/video/add'),(4,'order_list','/api/order/list'),(5,'user_list','/api/user/list'); CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(128) DEFAULT NULL COMMENT '用戶名', `password` varchar(256) DEFAULT NULL COMMENT '密碼', `create_time` datetime DEFAULT NULL, `salt` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `user` VALUES (1,'二當家小D','4280d89a5a03f812751f504cc10ee8a5',NULL,NULL),(2,'大當家','123456789',NULL,NULL),(3,'jack','d022646351048ac0ba397d12dfafa304',NULL,NULL); CREATE TABLE `role_permission` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL, `permission_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; INSERT INTO `role_permission` VALUES (1,3,1),(2,3,2),(3,3,3),(4,2,1),(5,2,2),(6,2,3),(7,2,4); CREATE TABLE `role` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL COMMENT '名稱', `description` varchar(64) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `role` VALUES (1,'admin','普通管理員'),(2,'root','超級管理員'),(3,'editor','審核人員'); CREATE TABLE `user_role` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, `remarks` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `user_role` VALUES (1,3,1,'二當家小D是editor'),(2,1,3,'jack是admin'),(3,2,3,'jack是root'),(4,3,3,'jack是editor'),(5,1,2,'大當家是admin'),(6,1,1,'二當家小D是root');
3.新建springboot項目,添加如下項目依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!-- 下面這個runtime需要去掉!!! --> <!-- <scope>runtime</scope>--> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <!-- spring整合shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <!-- 熱部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!-- shiro+redis緩存插件 --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies>
4. application.properties配置數據庫連接
#數據庫配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/xdclass_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&userSSL=false spring.datasource.username=root spring.datasource.password=lchadmin #使用druid數據源 #spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #開啟控制台打印sql mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #開啟數據庫字段下划線自動轉Java駝峰命名 mybatis.configuration.map-underscore-to-camel-case=true
5. 用戶-角色-權限 多對多關聯查詢SQL
* 第一步 查詢用戶對應的角色映射關系
select * from user u left join user_role ur on u.id=ur.user_id where u.id=3
* 第二步 查詢用戶對應的角色信息
select * from user u left join user_role ur on u.id=ur.user_id left join role r on ur.role_id = r.id where u.id=3
* 第三步 查詢角色和權限的關系
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
where u.id=1
* 第四步 查詢角色對應的權限信息(某個用戶具備的角色和權限集合)
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
left join permission p on rp.permission_id=p.id
where u.id=1
6. POJO定義
數據庫查詢接口:

1 package net.xdclass.xdclassshiro.dao; 2 3 import net.xdclass.xdclassshiro.domain.User; 4 import org.apache.ibatis.annotations.Param; 5 import org.apache.ibatis.annotations.Select; 6 7 import javax.websocket.server.ServerEndpoint; 8 9 public interface UserMapper { 10 11 @Select("select * from user where username= #{username}") 12 User findByUserName(@Param("username") String username); 13 14 @Select("select * from user where id = #{userId}") 15 User findById(@Param("userId") int id); 16 17 @Select("select * from user where username=#{username} and password=#{pwd}") 18 User findByUsernameAndPwd(@Param("username") String userName,@Param("pwd")String pwd); 19 20 }

1 package net.xdclass.xdclassshiro.dao; 2 3 import net.xdclass.xdclassshiro.domain.Role; 4 import org.apache.ibatis.annotations.*; 5 import org.apache.ibatis.mapping.FetchType; 6 7 import java.util.List; 8 9 public interface RoleMapper { 10 11 @Select("select ur.role_id id,r.name name,r.description description from role r\n" + 12 "left join user_role ur on r.id = ur.role_id where ur.user_id = #{userId}") 13 @Results( 14 value={ 15 @Result(id=true,property = "id",column="id"), 16 @Result(property = "name",column="name"), 17 @Result(property = "description",column="description"), 18 @Result(property = "permissionList",column="id", 19 many = @Many(select = "net.xdclass.xdclassshiro.dao.PermissionMapper.findPermissionListByRoleId",fetchType = FetchType.DEFAULT)) 20 } 21 ) 22 List<Role> findRoleListByUserId(@Param("userId") int userId); 23 }

1 package net.xdclass.xdclassshiro.dao; 2 3 import net.xdclass.xdclassshiro.domain.Permission; 4 import net.xdclass.xdclassshiro.domain.Role; 5 import org.apache.ibatis.annotations.Param; 6 import org.apache.ibatis.annotations.Select; 7 8 import java.util.List; 9 10 public interface PermissionMapper { 11 12 @Select("select p.id id,p.name name,p.url url from permission p left join role_permission rp on rp.permission_id = p.id\n" + 13 "where rp.role_id= #{roleId}") 14 List<Permission> findPermissionListByRoleId(@Param("roleId") int roleId); 15 16 }
主啟動類添加mapper包掃描
package net.xdclass.xdclassshiro; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication //掃描mapper包 @MapperScan("net.xdclass.xdclassshiro.dao") public class XdclassShiroApplication { public static void main(String[] args) { SpringApplication.run(XdclassShiroApplication.class, args); } }
Service接口及實現:

1 package net.xdclass.xdclassshiro.service; 2 3 import net.xdclass.xdclassshiro.domain.User; 4 import org.apache.ibatis.annotations.Select; 5 6 public interface UserService { 7 /** 8 * 獲取用戶全部信息,包含角色權限 9 * @param username 10 * @return 11 */ 12 User findAllUserInfoByUsername(String username); 13 14 /** 15 * 獲取用戶基本信息 16 * @param userId 17 * @return 18 */ 19 User findSimpleUserInfoById(int userId); 20 21 /** 22 * 根據用戶名查找用戶 23 * @param username 24 * @return 25 */ 26 User findSimpleUserInfoByUsername(String username); 27 }

1 package net.xdclass.xdclassshiro.service.impl; 2 3 import net.xdclass.xdclassshiro.dao.RoleMapper; 4 import net.xdclass.xdclassshiro.dao.UserMapper; 5 import net.xdclass.xdclassshiro.domain.Role; 6 import net.xdclass.xdclassshiro.domain.User; 7 import net.xdclass.xdclassshiro.service.UserService; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.stereotype.Service; 10 11 import java.util.List; 12 13 @Service 14 public class UserServiceImpl implements UserService { 15 16 @Autowired 17 private UserMapper userMapper; 18 @Autowired 19 private RoleMapper roleMapper; 20 21 @Override 22 public User findAllUserInfoByUsername(String username) { 23 User user = userMapper.findByUserName(username); 24 List<Role> roleList = roleMapper.findRoleListByUserId(user.getId()); 25 user.setRoleList(roleList); 26 return user; 27 } 28 29 @Override 30 public User findSimpleUserInfoById(int userId) { 31 32 return userMapper.findById(userId); 33 } 34 35 @Override 36 public User findSimpleUserInfoByUsername(String username) { 37 return userMapper.findByUserName(username); 38 } 39 }
自定義realm
1 package net.xdclass.xdclassshiro.config; 2
3 import net.xdclass.xdclassshiro.domain.Permission; 4 import net.xdclass.xdclassshiro.domain.Role; 5 import net.xdclass.xdclassshiro.domain.User; 6 import net.xdclass.xdclassshiro.service.UserService; 7 import org.apache.commons.lang3.StringUtils; 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.authz.AuthorizationInfo; 13 import org.apache.shiro.authz.SimpleAuthorizationInfo; 14 import org.apache.shiro.realm.AuthorizingRealm; 15 import org.apache.shiro.subject.PrincipalCollection; 16 import org.springframework.beans.factory.annotation.Autowired; 17
18 import java.util.ArrayList; 19 import java.util.Collections; 20 import java.util.List; 21
22 /**
23 * 自定義realm,繼承AuthorizingRealm,重寫認證,授權的方法 24 */
25 public class CustomRealm extends AuthorizingRealm { 26 @Autowired 27 private UserService userService; 28
29 /**
30 * 進行授權校驗 31 * @param principalCollection 32 * @return
33 */
34 @Override 35 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 36 User principal = (User) principalCollection.getPrimaryPrincipal(); 37 User user = userService.findAllUserInfoByUsername(principal.getUsername()); 38 List<String> roleList = new ArrayList<>(); 39 List<String> permissionList = new ArrayList<>(); 40 List<Role> userRoleList = user.getRoleList(); 41 // 把用戶的角色,權限放到對應的list中
42 if(userRoleList.size() >0 && userRoleList.get(0) != null){ 43 for (Role role : userRoleList){ 44 roleList.add(role.getName()); 45 for (Permission p: role.getPermissionList()){ 46 if(null != p.getName()){ 47 permissionList.add(p.getName()); 48 } 49 } 50 } 51 } 52
53 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 54 simpleAuthorizationInfo.addRoles(roleList); 55 simpleAuthorizationInfo.addStringPermissions(permissionList); 56 // 返回simpleAuthorizationInfo對象,交給shiro框架去做權限校驗 57 return simpleAuthorizationInfo; 58 } 59
60 /**
61 * 自定義shiro認證 62 * @param authenticationToken 63 * @return
64 * @throws AuthenticationException 65 */
66 @Override 67 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 68 //從token中獲取用戶信息,token代表用戶輸入
69 String username = (String) authenticationToken.getPrincipal(); 70 User user = userService.findAllUserInfoByUsername(username); 71
72 String pwd = user.getPassword(); 73 if(StringUtils.isBlank(pwd)){ 74 return null; 75 } 76 //org.apache.shiro.authc.SimpleAuthenticationInfo.SimpleAuthenticationInfo(java.lang.Object, java.lang.Object, java.lang.String) 77 // return new SimpleAuthenticationInfo(username,pwd,this.getClass().getName()); 78 // 使用shiro-reids插件,該插件默認會把SimpleAuthenticationInfo的第一個參數作為redis的key來使用,這里需要把username改為userc才能保證key的唯一性
79 return new SimpleAuthenticationInfo(user,pwd,this.getClass().getName()); 80 } 81 }
shiro配置類:
1 package net.xdclass.xdclassshiro.config; 2
3 import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4 import org.apache.shiro.mgt.SecurityManager; 5 import org.apache.shiro.session.mgt.SessionManager; 6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 8 import org.crazycake.shiro.RedisCacheManager; 9 import org.crazycake.shiro.RedisManager; 10 import org.crazycake.shiro.RedisSessionDAO; 11 import org.springframework.context.annotation.Bean; 12 import org.springframework.context.annotation.Configuration; 13
14 import javax.servlet.Filter; 15 import java.util.LinkedHashMap; 16 import java.util.Map; 17
18 /**
19 * ShiroFilterFactoryBean配置,要加@Configuration注解 20 * 21 */
22 @Configuration 23 public class ShiroConfig { 24
25
26 /**
27 * @param securityManager 28 * @return
29 * @Bean是一個方法級別上的注解,主要用在@Configuration注解的類里,也可以用在@Component注解的類里。添加的bean的id為方法名 等同於xml配置中的<bean id = " shiroFilter " class = " org.apache.shiro.spring.web.ShiroFilterFactoryBean " /> 30 */
31 @Bean 32 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 33 System.out.println("ShiroFilterFactoryBean.shiroFilter"); 34 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 35 // 設置securityManager
36 shiroFilterFactoryBean.setSecurityManager(securityManager); 37 // 如果訪問配置的這個接口時用戶還未登錄,則調用該接口登錄
38 shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); 39 // 登錄成功后重定向url 前后端分離的,沒有這個調用
40 shiroFilterFactoryBean.setSuccessUrl("/"); 41 // 沒有頁面訪問權限(登錄了,未授權),調用該接口
42 shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); 43
44 // 設置自定義Filter
45 Map<String, Filter> filterMap = new LinkedHashMap<>(); 46 filterMap.put("roleOrFilter", new CustomRolesAuthorizationFilter()); 47 // 綁定 48 shiroFilterFactoryBean.setFilters(filterMap); 49
50 // 注意,這里必須使用有序的LinkedHashMap,過濾器鏈是從上往下順序執行,一般將/**放在最后,否則部分路徑無法攔截
51 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); 52 // 登出過濾器
53 filterChainDefinitionMap.put("/logout", "logout"); 54 // 匿名訪問過濾器(游客訪問)
55 filterChainDefinitionMap.put("/pub/**", "anon"); 56 // 登陸用戶訪問
57 filterChainDefinitionMap.put("/authc/**", "authc"); 58 // 設置自定義Filter,只要是管理員角色或者root角色就能訪問
59 filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin,root]"); 60 // 配置roles[admin,root]表示必須同時具備admin和root角色才能訪問,實際應用不會這么用,使用自定義filter 61 // filterChainDefinitionMap.put("/admin/**", "roles[admin,root]"); 62 // 有編輯權限才可以訪問 /video/update是數據庫配置的權限路徑
63 filterChainDefinitionMap.put("/video/update", "perms[video_update]"); 64 // authc: 定義的url必須通過認證才可以訪問 anon: url可以匿名訪問
65 filterChainDefinitionMap.put("/**", "authc"); 66 // 這里沒設置,過濾器不生效!!!
67 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 68 return shiroFilterFactoryBean; 69 } 70
71 @Bean 72 public SecurityManager securityManager() { 73 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 74 // 錯誤寫法:securityManager.setRealm(new CustomRealm()); 75 // 不是前后端分離項目,這里可以不用設置sessionmanager
76 securityManager.setSessionManager(sessionManager()); 77 // 使用自定義的cacheManager 78 securityManager.setCacheManager(cacheManager()); 79 // 必須通過spring實例化CustomRealm的實例之后,這里再通過customRealm()進行注入
80 securityManager.setRealm(customRealm()); 81 return securityManager; 82 } 83
84 /**
85 * 注入realm 86 * @return
87 */
88 @Bean 89 public CustomRealm customRealm(){ 90 CustomRealm customRealm = new CustomRealm(); 91 // 設置密碼驗證器 使用明文密碼進行登錄測試時,需要將這里注釋掉,或者在數據庫存儲new SimpleHash("md5",pwd,null,2)加密過的密碼 92 //數據庫修改二當家小D的密碼為4280d89a5a03f812751f504cc10ee8a5,這里密碼驗證注釋放開,使用明文密碼訪問http://localhost:8080/pub/login,能夠登錄成功
93 customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 94 return customRealm; 95 } 96
97 /**
98 * 設置自定義sessionManager 99 * @return
100 */
101 @Bean 102 public SessionManager sessionManager(){ 103 CustomSessionManager sessionManager = new CustomSessionManager(); 104 // 設置session過期時間,不設置默認是30分鍾,單位ms
105 sessionManager.setGlobalSessionTimeout(200000); 106 // 設置session持久化到redis中,這樣服務器重啟后,用戶還可以通過之前的session進行操作
107 sessionManager.setSessionDAO(redisSessionDAO()); 108 return sessionManager; 109 } 110
111 /**
112 * 密碼加解密規則 113 * @return
114 */
115 @Bean 116 public HashedCredentialsMatcher hashedCredentialsMatcher(){ 117 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); 118 // 設置散列算法,進行加解密
119 credentialsMatcher.setHashAlgorithmName("md5"); 120 // 設置散列迭代次數,2 表示 hash之后再次hash
121 credentialsMatcher.setHashIterations(2); 122 return credentialsMatcher; 123 } 124
125
126 /**
127 * 配置redisManager,使用redis來存數session數據 128 * @return
129 */
130 public RedisManager getRedisManager(){ 131 RedisManager redisManager = new RedisManager(); 132 // redisManager.setHost("192.168.5.112"); 133 redisManager.setHost("192.168.0.114"); //單機redis地址 134 redisManager.setPort(8007); //單機redis端口 135 return redisManager; 136 } 137
138 /**
139 * 配置緩存管理器具體實現類,然后添加到securityManager里面 140 * @return
141 */
142 public RedisCacheManager cacheManager(){ 143 RedisCacheManager redisCacheManager = new RedisCacheManager(); 144 redisCacheManager.setRedisManager(getRedisManager()); 145 // 設置緩存過期時間,單位秒 146 redisCacheManager.setExpire(1800); 147 return redisCacheManager; 148 } 149
150 /**
151 * 1.通過shiro redis插件自定義session持久化 152 * 2.在shiro的sessionManager中配置session持久化 153 * @return
154 */
155 public RedisSessionDAO redisSessionDAO(){ 156 RedisSessionDAO redisSessionDao = new RedisSessionDAO(); 157 redisSessionDao.setRedisManager(getRedisManager()); 158 // session持久化的過期時間,單位s,如果不設置,默認使用session的過期時間,如果設置了,則使用這里設置的過期時間
159 redisSessionDao.setExpire(1800); 160
161 //設置自定義sessionId生成
162 redisSessionDao.setSessionIdGenerator(new CustomSessionIdGenerator()); 163 return redisSessionDao; 164 } 165 }
shiro配置類中使用到的自定義的內容:
(1)前后端分離情況下自定義SessionManager
1 package net.xdclass.xdclassshiro.config; 2 3 import org.apache.commons.lang3.StringUtils; 4 import org.apache.shiro.session.mgt.SessionKey; 5 import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 6 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 7 import org.apache.shiro.web.util.WebUtils; 8 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import java.io.Serializable; 12 13 /** 14 * 自定義sessionManager 15 * 繼承DefaultWebSessionManager並重寫該類的getSessionId(javax.servlet.ServletRequest, javax.servlet.ServletResponse)方法 16 * 意義: 17 * 前后端分離時,用戶訪問接口時,接口生成一個token保存到map中, 18 * 下次用戶訪問接口時帶上這個token,后端進行校驗 19 */ 20 public class CustomSessionManager extends DefaultWebSessionManager { 21 22 /** 23 * 請求頭中token的key名 24 */ 25 private static final String AUTHORIZATION = "token"; 26 27 public CustomSessionManager() { 28 super(); 29 } 30 31 @Override 32 protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 33 String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION); 34 if(StringUtils.isBlank(sessionId)){ 35 return super.getSessionId(request,response); 36 } else { 37 //org.apache.shiro.web.session.mgt.DefaultWebSessionManager.getReferencedSessionId id不為null時的分支處理 38 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); 39 // 校驗sessionId是否有效 40 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); 41 // automatically mark it valid here,if it is invalid, the onUnknowSesson method 42 // below will be invoked and we`ll remove the attribute at that time. 43 // 標記當前sessionId有效 44 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 45 return sessionId; 46 } 47 48 49 } 50 }
(2) 自定義Shiro Filter過濾器, 配置不同的角色,驗證自定義過濾器是否有效
1 package net.xdclass.xdclassshiro.config; 2 3 import org.apache.shiro.subject.Subject; 4 import org.apache.shiro.util.CollectionUtils; 5 import org.apache.shiro.web.filter.authz.AuthorizationFilter; 6 7 import javax.servlet.ServletRequest; 8 import javax.servlet.ServletResponse; 9 import java.util.Set; 10 11 /** 12 * 1.自定義filter 13 * 2. 把自定義filter加入到ShiroConfig中去 14 */ 15 public class CustomRolesAuthorizationFilter extends AuthorizationFilter { 16 @Override 17 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 18 Subject subject = this.getSubject(request, response); 19 String[] rolesArray = (String[]) ((String[]) mappedValue); 20 if (rolesArray != null && rolesArray.length != 0) { 21 Set<String> roles = CollectionUtils.asSet(rolesArray); 22 /*RolesAuthorizationFilter 中是return subject.hasAllRoles(roles),同時具備所有角色才能夠訪問 23 這里改為遍歷角色 ,當前subject是roles中的任意一個,則有權限訪問 24 * */ 25 for (String role : roles) { 26 if (subject.hasRole(role)) { 27 return true; 28 } 29 } 30 // return subject.hasAllRoles(roles); 31 } 32 // 沒有角色限制,可以直接訪問 33 return true; 34 } 35 }
(3)Redis整合CacheManager ,見shiroConfig類 78行, 130-136行,142-148行
shiro+redis緩存插件依賴:
<dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>
(4)Redis整合SessionManager 管理Session會話
為什么session也要持久化?
-——應用保活,重啟應用,用戶無感知,可以繼續以原先的狀態繼續訪問:
如果不做session持久化,假如一個已經登錄的用戶正在做新增操作,在還沒有提交之前,后台服務器進行了重啟,然后用戶編輯完數據進行提交,這時服務器因為重啟,
之前的session丟失,后台檢測到用戶成了未登錄狀態,就跳轉到用戶登錄頁面,這樣的用戶體驗很不友好,因此session需要進行持久化
怎么做session持久化:參考shiroconfig類的配置,需要注意的是,DO對象需要實現序列化接口 Serializable
//配置session持久化 customSessionManager.setSessionDAO(redisSessionDAO()); /** * 自定義session持久化 * @return‘ */ public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(getRedisManager()); return redisSessionDAO; }
(5)自定義sessionId
1 package net.xdclass.xdclassshiro.config; 2 3 4 import org.apache.shiro.session.Session; 5 import org.apache.shiro.session.mgt.eis.SessionIdGenerator; 6 7 import java.io.Serializable; 8 import java.util.UUID; 9 10 /** 11 * 自定義sessionId 12 */ 13 public class CustomSessionIdGenerator implements SessionIdGenerator { 14 @Override 15 public Serializable generateId(Session session) { 16 return UUID.randomUUID().toString().replaceAll("-", ""); 17 } 18 }
接口開發:
(1)游客訪問接口:
package net.xdclass.xdclassshiro.controller; import net.xdclass.xdclassshiro.domain.JsonData; import net.xdclass.xdclassshiro.domain.UserQuery; import net.xdclass.xdclassshiro.service.UserService; import org.apache.commons.collections.MapUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.ToLongBiFunction; @RestController @RequestMapping("/pub") public class PublicController { @Autowired private UserService userService; //http://localhost:8080/pub/find_user_info?username=jack @GetMapping("/find_user_info") public Object queryUserInfo(@RequestParam("username") String username) { return userService.findAllUserInfoByUsername(username); } @RequestMapping("/need_login") public JsonData needLogin(){ return JsonData.buildError("請登錄",-2); } // shiroconfig配置類里面配置的路徑 @RequestMapping("/not_permit") public JsonData noPermit(){ return JsonData.buildError("沒有權限訪問",-3); } //首頁,任何角色的都可以訪問 @RequestMapping("/index") public JsonData index(){ List<String> videoList = new ArrayList<>(); videoList.add("Mysql零基礎入門到實戰 數據庫教程"); videoList.add("Redis高並發高可用集群百萬級秒殺實戰"); videoList.add("Zookeeper+Dubbo視頻教程 微服務教程分布式教程"); videoList.add("2019年新版本RocketMQ4.X教程消息隊列教程"); videoList.add("微服務SpringCloud+Docker入門到高級實戰"); return JsonData.buildSuccess(videoList); } /** * 登錄接口 * @param userQuery * @param request * @param response * @return */ @PostMapping("/login") public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response){ Map<String,Object> info = new HashMap<>(); try { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userQuery.getName(),userQuery.getPwd()); subject.login(token); info.put("msg", "登錄成功"); info.put("session_id",subject.getSession().getId()); return JsonData.buildSuccess(info); } catch (Exception e){ e.printStackTrace(); return JsonData.buildError("登錄失敗"); } } }
(2)admin角色才能訪問的接口
package net.xdclass.xdclassshiro.controller; import net.xdclass.xdclassshiro.domain.JsonData; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * 只有admin管理員角色才能訪問的接口 */ @RestController @RequestMapping("admin") public class AdminController { /** * 視頻播放列表接口 * @return */ @PostMapping("/video/order") public JsonData findPlayRecord(){ Map<String ,String> recordMap = new HashMap<>(); recordMap.put("SpringBoot入門到高級實戰","300元"); recordMap.put("Cloud微服務入門到高級實戰","100元"); recordMap.put("分布式緩存Redis","90元"); return JsonData.buildSuccess(recordMap); } }
(3)只有登錄后才能訪問到的接口
1 package net.xdclass.xdclassshiro.controller; 2 3 import net.xdclass.xdclassshiro.domain.JsonData; 4 import org.springframework.web.bind.annotation.PostMapping; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RestController; 7 8 import java.util.HashMap; 9 import java.util.Map; 10 11 /** 12 * 登錄才能訪問的接口,使用@RestController,返回json數據到前端 13 */ 14 @RestController 15 @RequestMapping("authc") 16 public class OrderController { 17 18 /** 19 * 視頻播放列表接口 20 * @return 21 */ 22 @PostMapping("/video/play_record") 23 public JsonData findPlayRecord(){ 24 Map<String ,String> recordMap = new HashMap<>(); 25 recordMap.put("SpringBoot入門到高級實戰","第8章第1集"); 26 recordMap.put("Cloud微服務入門到高級實戰","第4章第10集"); 27 recordMap.put("分布式緩存Redis","第10章第3集"); 28 return JsonData.buildSuccess(recordMap); 29 } 30 }
(4)具有指定資源訪問權限(video/update)的用戶才能訪問的接口
1 package net.xdclass.xdclassshiro.controller; 2 3 import net.xdclass.xdclassshiro.domain.JsonData; 4 import org.springframework.web.bind.annotation.PostMapping; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RestController; 7 8 import java.util.HashMap; 9 import java.util.Map; 10 11 /** 12 * 只有具有video/update權限才能訪問的接口 13 * select u.username,u.password,ur.remarks,r.name,r.description,rp.role_id,p.name from user u 14 * left join user_role ur on u.id = ur.user_id 15 * left join role r on r.id = ur.role_id 16 * left join role_permission rp on rp.role_id = r.id 17 * left join permission p on p.id = rp.permission_id 18 * where p.id = 1 19 * 查詢出來的用戶才能訪問該接口 20 */ 21 @RestController 22 @RequestMapping("video") 23 public class VideoController { 24 @PostMapping("/update") 25 public JsonData findPlayRecord(){ 26 return JsonData.buildSuccess("video更新成功"); 27 } 28 }
代碼地址:https://github.com/liuch0228/shiro-learn.git
參考資料:
Shiro權限控制+整合shiro https://blog.csdn.net/qq_43652509/article/details/88074832