一、在pom文件中添加依赖
<!--Shiro核心框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Shiro使用Spring框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Shiro使用EhCache缓存框架 -->
<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>${thymeleaf.extras.shiro.version}</version>
</dependency>
二、配置类shiroConfig--登录认证拦截
1.创建realm配置realm
2.配置安全管理器DefaultWebSecurityManager----核心
3.配置请求过滤器ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl("/static/login.html");
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl("/nopermissioin");
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
// 不需要拦截的访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
// 退出 logout地址,shiro去清除session
filterChainDefinitionMap.put("/logout", "logout");
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
4.登录控制器,使用subject登录
Subject subject= SecurityUtils.getSubject();
UsernamePasswordToken token= new UsernamePasswordToken(username, password);
subject.login(token);
5.shiro内置过滤器介绍
anon: 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”
authc: 表示需要认证(登录)才能使用;示例“/**=authc”
authcBasic:Basic HTTP身份验证拦截器
roles: 角色授权拦截器,验证用户是否拥有资源角色;示例“/admin/**=roles[admin]”
perms: 权限授权拦截器,验证用户是否拥有资源权限;示例“/user/create=perms["user:create"]”
user: 用户拦截器,用户已经身份验证/记住我登录的都可;示例“/index=user”
logout: 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”
port: 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
rest: rest风格拦截器;
ssl: SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;
注: anon,authcBasic,auchc,user是认证过滤器, perms,roles,ssl,rest,port是授权过滤器
三、权限控制(鉴权
1.注解式
①在需要权限的控制器方法上贴注解
-
@RequiresPermissions("权限表达式") ==> 需要权限控制
-
@RequiresRoles() ==> 需要角色控制
②在配置文件中配置两个bean
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
DefaultWebSecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 设置支持CGlib代理
* 详情看DefaultAopProxyFactory#createAopProxy
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
③在realm中完善鉴权逻辑
//授权相关
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Employee employee = (Employee)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据用户的id查询该用户拥有的角色编码
List<Role> roles = roleService.queryByEmployeeId(employee.getId());
for(Role role:roles){
info.addRole(role.getSn());
}
//根据用户的id查询该用户拥有的权限表达式
List<String> permissions = permissionService.queryByEmployeeId(employee.getId());
info.addStringPermissions(permissions);
return info;
}
注:可以添加统一异常处理--针对用户没有权限
@ControllerAdvice
public class CommonControllerAdvice {
@ExceptionHandler(AuthorizationException.class)
public String exceptionHandler(AuthorizationException e){
e.printStackTrace();
return "/nopermission";
}
}
注--超级管理员
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if (employee.isAdmin()) {//是管理员--权限可以使用通配符
info.addStringPermission("*:*");
//角色--不能使用通配符
List<Role> roles = roleService.selAll();
for (Role role : roles) {
info.addRole(role.getSn());
}else{
//余下逻辑与普通鉴权相同
}
return info;
}
2.编程式--以查询为例
@RequestMapping("/list")
public String list(Model model, QueryObject qo) {
//获取subject主体对象
Subject subject = SecurityUtils.getSubject();
PageInfo<Department> pageInfo = null;
//判断此subject是否有这个权限--我猜测这个subject就是从数据库查出来的用户信息,根据id和关联表查出的权限
if(subject.isPermitted("department:list")){
//有权限执行代码
pageInfo = departmentService.query(qo);
}else{
//没有权限返回空页面
pageInfo = new PageInfo<>(Collections.EMPTY_LIST);
}
model.addAttribute("pageInfo", pageInfo);
return "department/list";
}
3.标签式--需要配置Shiro集成ThymeLeaf标签支持(了解)
①配置文件中添加依赖
/**
* thymeleaf模板引擎和shiro框架的整合
*/
@Bean
public ShiroDialect shiroDialect()
{
return new ShiroDialect();
}
②页面中添加头
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
③使用
<a href="#" class="btn btn-success btn-input" style="margin: 10px" shiro:hasPermission="department:saveOrUpdate">
<span class="glyphicon glyphicon-plus"></span> 添加
</a>
四、其他集成
4.1 ehchche--缓存
1.在resource
目录下新建ehcache/ehcache-shiro.xml
文件,内容如下:
-
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
-
配置属性说明
-
参数 | 说明 |
maxElementsInMemory | 缓存对象最大个数 |
eternal | 对象是否永久有效,一但设置了,timeout 将不起作用。 |
timeToIdleSeconds | 对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。 |
timeToLiveSeconds | 对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。 |
memoryStoreEvictionPolicy | 当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。 |
缓存淘汰策略:
策略 | 说明 |
LRU | 默认,最近最少使用,距离现在最久没有使用的元素将被清出缓存 |
FIFO | 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉 |
LFU | 较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存 |
2.添加配置
@Bean
public EhCacheManager getEhCacheManager()
{
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
return em;
}
/**
* 自定义Realm
*/
@Bean
public EmployeeRealm userRealm(EhCacheManager cacheManager)
{
EmployeeRealm employeeRealm = new EmployeeRealm();
employeeRealm.setCacheManager(cacheManager);
return employeeRealm;
}
3.使用自定义的配置
@Bean
//传递的参数名要与自定义realm的bean名一致-userRealm
public DefaultWebSecurityManager securityManager(EmployeeRealm userRealm,DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//添加realm对象
securityManager.setRealm(userRealm);
//添加session管理器对象
securityManager.setSessionManager(sessionManager);
return securityManager;
}
4.2加盐加密
1.在数据库和实体类中分别添加salt字段
2.修改对应的mapper映射文件
3.改realm
/**
* 在返回值中添加第三个参数 ByteSource.Util.bytes(employee.getSalt())
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
Employee employee = employeeService.selUserByUsername(username);
if (employee == null) {
return null;
}
return new SimpleAuthenticationInfo(employee, employee.getPassword(),
ByteSource.Util.bytes(employee.getSalt()), getName());
}
4.改配置类
-
添加凭证匹配器
-
@Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher matcher=new HashedCredentialsMatcher("md5"); matcher.setHashIterations(3); return matcher; }
-
将凭证匹配器设置到realm中
-
@Bean public EmployeeRealm userRealm(EhCacheManager cacheManager, HashedCredentialsMatcher hashedCredentialsMatcher) { EmployeeRealm employeeRealm = new EmployeeRealm(); employeeRealm.setCacheManager(cacheManager); //这里 employeeRealm.setCredentialsMatcher(hashedCredentialsMatcher); return employeeRealm; }
5.注册功能修改--service
//添加用户的service方法
@Override
public void add(Employee employee, Long[] roleIds) {
//生成随机盐
String salt = UUID.randomUUID().toString().substring(0, 4);
//生成加密的密码
Md5Hash pwd=new Md5Hash(employee.getPassword(), salt,3);
//将盐和密码都设置给对象
employee.setPassword(pwd.toString());
employee.setSalt(salt);
//最后进行添加
employeeMapper.insert(employee);
//向关联表批量插入
if (roleIds != null && roleIds.length > 0 && !employee.isAdmin())//合理化校验
{
employeeMapper.insertRelationBatch(employee.getId(), roleIds);
}
}
五、关于重定向出现404--sessionId
1.在配置类中添加session管理器
/**
* session管理器
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
2.在安全管理器中添加
//安全管理器对象
@Bean
public DefaultWebSecurityManager securityManager(EmployeeRealm userRealm,DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//添加realm对象
securityManager.setRealm(userRealm);
//添加session管理器对象
securityManager.setSessionManager(sessionManager);
return securityManager;
}