Spring Boot 整合Shiro實現登陸認證和許可權控制
阿新 • • 發佈:2019-02-15
我在做畢設的時候,使用了Shiro作為專案中的登陸認證和許可權控制。
下面是我專案中如何實現整合shiro的學習記錄。
匯入shiro依賴包到pom.xml
<!-- Shiro依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency >
配置shiro
下面給出了我專案中配置shiro的ShiroConfiguration 類中的主要程式碼:
package com.ciyou.edu.config.shiro.common
@Configuration
class ShiroConfiguration {
@Autowired(required = false)
private PermissionService permissionService
private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class)
/**
* ShiroFilterFactoryBean 處理攔截資原始檔問題。
* 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,因為在
* 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager
* Filter Chain定義說明 1、一個URL可以配置多個Filter,使用逗號分隔 2、當設定多個過濾器時,全部驗證通過,才視為通過
*
* 部分過濾器可指定引數,如perms,roles
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean()
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters()//獲取filters
//將自定義 的許可權驗證失敗的過濾器ShiroFilterFactoryBean注入shiroFilter
filters.put("perms", new ShiroPermissionsFilter())
// 必須設定SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager)
// 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login")
//登入成功後要跳轉的連結
//shiroFilterFactoryBean.setSuccessUrl("/index")
// 許可權控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>()
// 配置退出過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
filterChainDefinitionMap.put("/logout", "logout")
filterChainDefinitionMap.put("/favicon.ico", "anon")
filterChainDefinitionMap.put("/adminLogin", "anon")
filterChainDefinitionMap.put("/teacherLogin", "anon")
//允許訪問靜態資源
filterChainDefinitionMap.put("/static/**", "anon")
// 從資料庫獲取所有的許可權
List<Permission> permissionList = permissionService?.findAllPermission()
permissionList?.each {current_Permission ->
//規則:"roles[admin,user]", "perms[file:edit]"
filterChainDefinitionMap?.put(current_Permission?.getUrl(),"perms[" + current_Permission?.getPermission() + "]")
logger.info("從資料庫載入的攔截器規則:資源路徑: " + current_Permission?.getUrl() + " ,需要許可權:" +current_Permission?.getPermission())
}
filterChainDefinitionMap.put("/admin/**","roles[Admin]")
filterChainDefinitionMap.put("/teacher/**","roles[Teacher]")
filterChainDefinitionMap.put("/student/**","roles[Student]")
// 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊
filterChainDefinitionMap.put("/**", "authc")
//authc表示需要驗證身份才能訪問,還有一些比如anon表示不需要驗證身份就能訪問等。
logger.info("攔截器鏈:" + filterChainDefinitionMap)
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap)
return shiroFilterFactoryBean
}
//SecurityManager 是 Shiro 架構的核心,通過它來連結Realm和使用者(文件中稱之為Subject.)
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager()
//設定realm.
securityManager.setAuthenticator(modularRealmAuthenticator())
List<Realm> realms = new ArrayList<>()
//新增多個Realm
realms.add(adminShiroRealm())
realms.add(teacherShiroRealm())
realms.add(studentShiroRealm())
securityManager.setRealms(realms)
return securityManager
}
/**
* 系統自帶的Realm管理,主要針對多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重寫的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator()
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy())
return modularRealmAuthenticator
}
@Bean
public AdminShiroRealm adminShiroRealm() {
AdminShiroRealm adminShiroRealm = new AdminShiroRealm()
adminShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則
return adminShiroRealm
}
@Bean
public StudentShiroRealm studentShiroRealm() {
StudentShiroRealm studentShiroRealm = new StudentShiroRealm()
studentShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則
return studentShiroRealm
}
@Bean
public TeacherShiroRealm teacherShiroRealm() {
TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm()
teacherShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//設定解密規則
return teacherShiroRealm
}
//因為我們的密碼是加過密的,所以,如果要Shiro驗證使用者身份的話,需要告訴它我們用的是md5加密的,並且是加密了兩次。同時我們在自己的Realm中也通過SimpleAuthenticationInfo返回了加密時使用的鹽。這樣Shiro就能順利的解密密碼並驗證使用者名稱和密碼是否正確了。
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher()
hashedCredentialsMatcher.setHashAlgorithmName("md5")//雜湊演算法:這裡使用MD5演算法;
hashedCredentialsMatcher.setHashIterations(2)//雜湊的次數,比如雜湊兩次,相當於 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 開啟shiro aop註解支援.
* 使用代理方式;所以需要開啟程式碼支援;
* 開啟 許可權註解
* Controller才能使用@RequiresPermissions
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor()
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager)
return authorizationAttributeSourceAdvisor
}
}
下面是上述程式碼的一些解釋:
實體類
這裡我只給出了其中的一個許可權實體類:
package com.ciyou.edu.entity
class Permission implements Serializable{
private static final long serialVersionUID = 1L
//許可權編號(自增長)
private Integer permissionId
//許可權名稱
private String permissionName
//許可權字串
private String permission
//父編號
private Integer parentId
//資源型別
private Integer type
//資源路徑
private String url
Integer getPermissionId() {
return permissionId
}
void setPermissionId(Integer permissionId) {
this.permissionId = permissionId
}
String getPermissionName() {
return permissionName
}
void setPermissionName(String permissionName) {
this.permissionName = permissionName
}
String getPermission() {
return permission
}
void setPermission(String permission) {
this.permission = permission
}
Integer getParentId() {
return parentId
}
void setParentId(Integer parentId) {
this.parentId = parentId
}
String getUrl() {
return url
}
void setUrl(String url) {
this.url = url
}
Integer getType() {
return type
}
void setType(Integer type) {
this.type = type
}
@Override
public String toString() {
return "Permission{" +
"permissionId=" + permissionId +
", permissionName='" + permissionName + '\'' +
", permission='" + permission + '\'' +
", parentId=" + parentId +
", type=" + type +
", url='" + url + '\'' +
'}';
}
}
動態修改許可權
在專案中,可以對許可權進行增刪改操作,然而許可權在shiroConfig中已經配置死了,所以我們要在我們的專案中手動更新,下面給出的是我專案中的程式碼:
package com.ciyou.edu.service.impl
import com.ciyou.edu.entity.Permission
import com.ciyou.edu.mapper.PermissionMapper
import com.ciyou.edu.service.ShiroService
import org.apache.shiro.spring.web.ShiroFilterFactoryBean
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver
import org.apache.shiro.web.servlet.AbstractShiroFilter
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class ShiroServiceImpl implements ShiroService{
private static final Logger logger = LoggerFactory.getLogger(ShiroServiceImpl.class)
@Autowired
private ShiroFilterFactoryBean shiroFilterFactoryBean
@Autowired
private PermissionMapper permissionMapper
/**
* 初始化許可權
*/
public Map<String, String> loadFilterChainDefinitions() {
// 許可權控制map.從資料庫獲取
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>()
filterChainDefinitionMap.put("/logout", "logout")
filterChainDefinitionMap.put("/favicon.ico", "anon")
filterChainDefinitionMap.put("/adminLogin", "anon")
filterChainDefinitionMap.put("/teacherLogin", "anon")
//允許訪問靜態資源
filterChainDefinitionMap.put("/static/**", "anon")
List<Permission> list = permissionMapper?.findAllPermission()
for (Permission permission : list) {
filterChainDefinitionMap.put(permission?.getUrl(),"perms["+ permission?.getPermission() +"]")
}
filterChainDefinitionMap.put("/admin/**","roles[Admin]")
filterChainDefinitionMap.put("/teacher/**","roles[Teacher]")
filterChainDefinitionMap.put("/student/**","roles[Student]")
// 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊
filterChainDefinitionMap.put("/**", "authc")
return filterChainDefinitionMap
}
/**
* 重新載入許可權
*/
@Override
public void updatePermission() {
synchronized (shiroFilterFactoryBean) {
AbstractShiroFilter shiroFilter = null
try {
shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean?.getObject()
} catch (Exception e) {
logger.info("重新載入許可權失敗:" + e.getMessage())
throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!")
}
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter?.getFilterChainResolver()
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver?.getFilterChainManager()
// 清空老的許可權控制
manager?.getFilterChains()?.clear()
shiroFilterFactoryBean?.getFilterChainDefinitionMap()?.clear()
shiroFilterFactoryBean?.setFilterChainDefinitionMap(loadFilterChainDefinitions())
// 重新構建生成
Map<String, String> chains = shiroFilterFactoryBean?.getFilterChainDefinitionMap()
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry?.getKey()
String chainDefinition = entry?.getValue()?.trim()?.replace(" ", "")
manager.createChain(url, chainDefinition)
}
logger.info("更新許可權成功!!")
}
}
}
在Controller中呼叫
到此為止shiro的整合就基本完成了。
本文是根據我一個專案中給出的配置,具體的專案原始碼可以檢視:CIYOU