SpringBoot-Shiro權限控制(2019.12.13)
在《Spring-Boot-shiro用戶認證》中,我們通過繼承AuthorizingRealm抽象類實現了doGetAuthenticationInfo()
方法完成了用戶認證操作。接下來繼續實現doGetAuthorizationInfo()
方法完成Shiro的權限控制功能。
授權也稱爲訪問控制,是管理資源訪問的過程。即根據不同用戶的權限判斷其是否有訪問相應資源的權限。在Shiro中,權限控制有三個核心的元素:權限,角色和用戶。(也稱爲權限3要素)
庫的模型分爲 : 用戶表 角色表 權限表 (3者都是多對多關係通過中間表關聯)
創建了五張表:用戶表USER、角色表ROLE、用戶角色關聯表USER_ROLE、權限表PERMISSION和權限角色關聯表ROLE_PERMISSION。用戶love角色爲admin,用戶tester角色爲test。admin角色擁有用戶的所有權限(user:user,user:add,user:delete),而test角色只擁有用戶的查看權限(user:user)。密碼都是321,沒經過Shiro提供的MD5加密。
1. Dao層
創建兩個實體類,對應用戶角色表Role和用戶權限表Permission:
@Data
public class Role implements Serializable {
private static final long serialVersionUID = -66L;
private Integer id;
private String name;
private String memo;
}
-----------------------------------------------------------------------------
@Data
public class Permission implements Serializable {
private static final long serialVersionUID = -66L;
private Integer id;
/**
* 權限路徑
*/
private String url;
/**
* 權限名稱
*/
private String name;
}
創建兩個dao接口,分別根據用戶名查詢用戶擁有所有角色和用戶擁有的所有權限:
UserRoleMapper.java
@Mapper
public interface UserRoleMapper {
/**
* 根據用戶名查詢用戶擁有所有角色
*
* @param username
* @return java.util.List<com.zhihao.entity.Role>
* @author: zhihao
* @date: 2019/12/13
*/
List<Role> findRoleByUserName(String username);
}
UserRoleMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zhihao.dao.UserRoleMapper">
<select id="findRoleByUserName" resultType="com.zhihao.entity.Role">
SELECT ro.* FROM `user` u
LEFT JOIN user_role uro ON u.id=uro.user_id
RIGHT JOIN role ro ON ro.id = uro.rid WHERE u.username = #{username}
</select>
</mapper>
UserPermissionMapper.java
@Mapper
public interface UserPermissionMapper {
/**
* 根據用戶名 查詢出該用戶擁有的所有權限
*
* @param username 用戶名
* @return java.util.List<com.zhihao.entity.Permission>
* @author: zhihao
* @date: 2019/12/13
* {@link #}
*/
List<Permission> findPermissionByUserName(String username);
}
UserPermissionMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zhihao.dao.UserPermissionMapper">
<select id="findPermissionByUserName" resultType="com.zhihao.entity.Permission">
SELECT per.* FROM `permission` per
LEFT JOIN role__permission rope ON per.id=rope.pid
LEFT JOIN role ro ON ro.id=rope.rid
LEFT JOIN user_role uro ON uro.rid=ro.id
LEFT JOIN `user` u ON u.id=uro.user_id WHERE u.username = #{username};
</select>
</mapper>
2.對ShiroRealm.java
進行改造。
在Shiro中,用戶角色和權限的獲取是在ShiroRealm的doGetAuthorizationInfo()
方法中實現的,所以接下來手動實現該方法:
/**
* 獲取用戶角色和權限
*
* @param principal
* @return org.apache.shiro.authz.AuthorizationInfo
* @author: zhihao
* @date: 2019/12/13
* {@link #}
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//通過shiro工具獲取登錄後的user對象,獲取到用戶名
User user = (User) SecurityUtils.getSubject().getPrincipal();
String username = user.getUsername();
//創建授權對象進行封裝角色和權限信息進去進行返回 注意不是SimpleAuthenticationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//獲取用戶角色集
List<Role> roleList = roleService.findRoleByUserName(username);
Set<String> roleSet = new HashSet<>();
for (Role role : roleList) {
roleSet.add(role.getName());
}
System.out.println("用戶擁有的角色>>>"+roleSet);
//添加角色進角色授權
info.setRoles(roleSet);
List<Permission> permissionList = permissionService.findPermissionByUserName(username);
Set<String> permissionSet = new HashSet<>();
for (Permission permission : permissionList) {
permissionSet.add(permission.getName());
}
System.out.println("用戶擁有的權限>>>"+permissionSet);
//添加權限進權限授權
info.setStringPermissions(permissionSet);
return info;
}
//..下面是之前的登錄認證
在上述代碼中,我們通過方法獲取了當前登錄用戶的角色和權限集,然後保存到SimpleAuthorizationInfo對象中,並返回給Shiro,這樣Shiro中就存儲了當前用戶的角色和權限信息了。
3. 對ShiroConfig.java進行改造配置。
要使用權限相關的註解必須要在配置中開啓註解的使用,需要在ShiroConfig
中添加如下配置:
/**
* 開啓shiro認證註解
*
* @param securityManager
* @return AuthorizationAttributeSourceAdvisor
* @author: zhihao
* @date: 2019/12/13
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
Shiro爲我們提供了一些和權限相關的註解,如下所示:
// 表示當前Subject已經通過login進行了身份驗證;即Subject.isAuthenticated()返回true。
@RequiresAuthentication
// 表示當前Subject已經身份驗證或者通過記住我登錄的。
@RequiresUser
// 表示當前Subject沒有身份驗證或通過記住我登錄過,即是遊客身份。
@RequiresGuest
// 表示當前Subject需要角色admin和user。
@RequiresRoles(value={"admin", "user"}, logical= Logical.AND)
// 表示當前Subject需要權限user:a或user:b。
@RequiresPermissions (value={"user:a", "user:b"}, logical= Logical.OR)
4. 編寫一個權限控制的UserController
來測試權限
@RestController
public class UserController {
/**
* 模擬從數據庫獲取了全部用戶
*
* @return java.lang.String
* @author: zhihao
* @date: 2019/12/13
*/
@RequiresPermissions(value = "user:user") //使用shiro權限註解標明,只能擁有這個user:user權限的用戶訪問
@RequestMapping("/list")
public String getUserList(){
String userList ="擁有獲取全部用戶的權限 ``user:user``";
return userList;
}
@RequiresPermissions(value = "user:add")
@RequestMapping("/add")
public String addUser(){
String userList ="擁有添加用戶權限 ``user:add``";
return userList;
}
@RequiresPermissions(value = "user:delete")
@RequestMapping("/delete")
public String deleteUser(){
String userList ="擁有刪除用戶權限 ``user:delete``";
return userList;
}
}
5. 在LoginController中添加一個/403跳轉:
@GetMapping("/403")
public ModelAndView forbid() {
ModelAndView view = new ModelAndView();
view.setViewName("403");
return view;
}
6. 測試沒有權限用戶後端報AuthorizationException異常,
在測試中發現使用沒有權限的用戶訪問後臺拋出了異常:org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method:…異常!!!
本以爲在ShiroConfig中配置了shiroFilterFactoryBean.setUnauthorizedUrl("/403");
,沒有權限的訪問會自動重定向到/403,結果證明並不是這樣。後來研究發現,該設置只對filterChain起作用,比如在filterChain中設置了filterChainDefinitionMap.put("/user/update", "perms[user:update]");
,如果用戶沒有user:update
權限,那麼當其訪問/user/update
的時候,頁面會被重定向到/403。
對應以上問題可以有2種解決方法:
第一種: 定義一個全局異常處理進行處理:
@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE) //最高精度,拋出的異常符合這個異常就進來
public class GlobalExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
public String handleAuthorizationException() {
return "403";
}
}
第二種在配置類中配置個異常解析器
/**
* 異常解析器
*
* @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
* @author: zhihao
* @date: 2019/12/13
* {@link #}
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
//攔截到AuthorizationException 就跳轉到resources資源下的 如果帶文件夾需要加上文件夾: /xxx/403模板頁面
properties.setProperty("AuthorizationException", "/403");
resolver.setExceptionMappings(properties);
return resolver;
}
最後發現無權限訪問後正常到403頁面了!