什么是shiro?
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
shiro官方网站:http://shiro.apache.org/
springboot整合shiro
第一步:在pom文件当中引入对应的jar
<shiro.version>1.4.0</shiro.version>
<!-- shiro 相关包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--引入thymeleaf对shiro标签的支持-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- End -->
第二步:配置ehcache-shiro.xml 使用ehcache作为缓存,存权限信息
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<diskStore path="java.io.tmpdir" />
<!-- <diskStore>==========当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)
* <diskStore path="">==用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index
* name=================缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里)
* maxElementsOnDisk====磁盘缓存中最多可以存放的元素数量,0表示无穷大
* maxElementsInMemory==内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况
* 1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中
* 2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素
* eternal==============缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds
* timeToIdleSeconds====缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性
* 即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除
* timeToLiveSeconds====缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大
* 即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除
* overflowToDisk=======内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中)
* 会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data
* diskPersistent=======是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件
* 这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存
* 要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法
* diskExpiryThreadIntervalSeconds==磁盘缓存的清理线程运行间隔,默认是120秒
* diskSpoolBufferSizeMB============设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB
* memoryStoreEvictionPolicy========内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存
* 共有三种策略,分别为LRU(最近最少使用)、LFU(最常用的)、FIFO(先进先出) -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="180"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
第三步:配置ShiroConfig类
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
/**
* @Auther: HJLJY
* 1.springboot取消了xml 所以之前在xml当中的配置信息通过配置类进行加载
* 2.使用@Configuration注解表明这个类是一个配置类,springboot在启动时会自动加载这个类里面的配置
* 3.使用@bean注解,表明这个方法对应xml里面的<bean>标签
*/
@Configuration
public class ShiroConfig {
/**
* ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
*
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* 加载缓存配置器
* @return
*/
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
/**
* 加载自定义realm 使用EhCache缓存
* @param cacheManager
* @return
*/
@Bean(name = "AccountRealm")
public AccountRealm accountRealm(EhCacheManager cacheManager) {
AccountRealm realm = new AccountRealm();
realm.setCacheManager(cacheManager);
return realm;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 自动创建代理
* @DependsOn 注解设置bean创建顺序
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
*设置默认的安全策略
* @param authRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(AccountRealm authRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(authRealm);
// <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
defaultWebSecurityManager.setCacheManager(getEhCacheManager());
return defaultWebSecurityManager;
}
/**
* 使用shiro注解方式加载需要验证的资源url
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//跳转到登录界面的url
shiroFilterFactoryBean.setLoginUrl("/login");
//登录成功后跳转的URL
shiroFilterFactoryBean.setSuccessUrl("/system/index");
//没有权限的URL
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/* 静态资源放行 */
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/html/**", "anon");
filterChainDefinitionMap.put("/index.html", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/plugin/**", "anon");
/* 登陆请求放行*/
filterChainDefinitionMap.put("/loginIn", "anon");
/* 这里由于上面是采用的注解方式,所以没有将需要拦截的url依次添加到map里面进行拦截验证。 拦截所有资源 一定要放最后*/
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
第四步:配置自定义reaelm
import com.hjljy.blog.common.ApplicationContextRegister;
import com.hjljy.blog.common.utils.ShiroSessionUtil;
import com.hjljy.blog.entity.system.Account;
import com.hjljy.blog.service.system.account.AccountService;
import com.hjljy.blog.service.system.resources.ResourcesService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.Set;
/**
* @Auther: HJLJY
* @Date: 2018/12/21 0021 17:30
* @Description: 自定义认证类,负责自己的认证跳转
*/
public class AccountRealm extends AuthorizingRealm {
/**
* 进行授权操作,获取到对应用户的权限进行放行
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
ResourcesService service = ApplicationContextRegister.getBean(ResourcesService.class);
Set<String> perms = service.getPermsByUserId(ShiroSessionUtil.getAccount().getId());
//权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
return info;
}
/**
* 进行认证操作 将输入的用户信息和数据库用户信息进行对比
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = String.valueOf(token.getPassword());
Account user = new Account(username,password);
//通过spring上下文获取到service对象 也可以通过自动注入@AutoWird注入service
AccountService service = ApplicationContextRegister.getBean(AccountService.class);
user = service.findByAccount(user);
// 账号不存在
if (user == null) {
throw new UnknownAccountException("账号或密码不正确");
}
// 账号锁定
if (!user.getStatus()) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
ShiroSessionUtil.setSession(user);
return info;
}
}
上述完成之后,shiro整合springboot就完成了。
注意事项:
1 这里shiro权限拦截使用的是注解方式进行拦截。需要在对应的controller方法上面配置对应的shiro注解才会进行拦截。
2 在加载shiro权限的时候,从数据库里面查询出的权限信息如果有空值需要去掉。不然会报错:
Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.
shiro注解使用方式:https://blog.csdn.net/w_stronger/article/details/73109248
同时支持在html上添加shiro注解达到对按钮的控制。
html界面上添加:
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" 声明。用于在thymeleaf模板当中支持shiro注解。
在按钮当中添加shiro注解达到控制效果
<button shiro:hasPermission="sys:user:add" class="layui-btn layui-btn-radius" lay-event="add">
<i class="layui-icon"></i> 添加
</button>
如果没有权限:sys:user:add 这个按钮就不会再界面上显示出来。