我在做毕设的时候,使用了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
}
}
上述代码中的多Realm配置,可以查看我的上一篇博文进行配置:Spring Boot 集成Shiro的多realm配置
下面是上述代码的一些解释:
实体类
这里我只给出了其中的一个权限实体类:
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
最后,感谢几位作者的文章解惑,需要学习更过关于Spring Boot和Shiro的知识可以参考:
SpringBoot+shiro整合学习之登录认证和权限控制
SpringBoot + Shiro (一)基础工程搭建
SpringBoot + Shiro (二)身份校验和角色设置
SpringBoot + Shiro (三)权限
SpringBoot + Shiro (四)缓存&记住密码
SpringBoot + Shiro (五)验证码
springboot整合shiro-登录认证和权限管理
Spring Boot Shiro权限管理【从零开始学Spring Boot】
Spring boot 中使用Shiro
更多关于权限注解使用的可以查看:
shiro注解权限控制-5个权限注解