官方文档
https://shiro.apache.org/tutorial.html
超详细 Spring Boot 整合 Shiro 教程! - 腾讯云开发者社区-腾讯云 (tencent.com)
shiro
Shiro 的架构有 3 个主要概念Subject:SecurityManager和Realms. 下图是这些组件如何交互的高级概述

Subject
既可以指用户,也可以指第 3 方进程、cron 作业、守护进程。或者说是与软件交互的任何东西

SecurityManager
Realm
充当 Shiro 和应用程序安全数据之间的“桥梁”或“连接器”。
配置 Shiro 时,必须指定至少一个 Realm 用于身份验证或授权。可以配置多个 Realm ,但
SecurityManager至少需要一个。
shiro 详细架构图

shiro过滤器
| 过滤器简称 | 对应的 java 类 | 含义 |
|---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 认证(登录)才能使用 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 指定 rul 需要 form 表单登录,默认会从请求中获取username、password、rememberMe参数并尝试登录。登陆失败会跳转到 login 的 url。可以使用该过滤器做默认的登录逻辑。 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | 指定 url 需要 basic 登录 |
user | org.apache.shiro.web.filter.authc.UserFilter | 已登录或记住我的用户访问 |
port | org.apache.shiro.web.filter.authz.PortFilter | 指定端口才可以访问 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | 将请求方法转化成相应的动词来构造一个权限字符串 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 指定角色访问 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | https 访问 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 指定权限可以访问 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 登出过滤器 |
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc 表示需要认证(登录)才能使用,FormAuthenticationFilter 是表单认证,没有参数
roles:例子/admins/user/=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如 admins/user/=roles["admin,guest"],每个参数通过才算通过,相当于 hasAllRoles()方法。
perms:例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/=perms["user:add:,user:modify:"],当有多个参数时必须每个参数都通过才通过,想当于 isPermitedAll()方法。
rest:例子/admins/user/=rest[user],根据请求的方法,相当于/admins/user/=perms[user:method] ,其中 method 为 post,get,delete 等。
port:例子/admins/user/**=port[8081],当请求的 url 的端口不是 8081 是跳转到 schemal://serverName:8081?queryString,其中 schmal 是协议 http 或 https 等,serverName 是你访问的 host,8081 是 url 配置里 port 的端口,queryString
是你访问的 url 里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic 没有参数表示 httpBasic 认证
ssl:例子/admins/user/**=ssl 没有参数,表示安全的 url 请求,协议为 https
user:例如/admins/user/**=user 没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查
注:
anon,authcBasic,auchc,user 是认证过滤器,
perms,roles,ssl,rest,port 是授权过滤器
认证&授权
登录
//获取用户,其会自动绑定到当前线程
Subject subject = SecurityUtils.getSubject();
//构建待认证 token
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
//登录,即身份验证
subject.login(token);
//判断是否已经认证
subject.isAuthenticated()
//登出 subject.logout(token);
shiro过滤器
| 过滤器简称 | 对应的 java 类 | 含义 |
|---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 认证(登录)才能使用 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 指定 rul 需要 form 表单登录,默认会从请求中获取username、password、rememberMe参数并尝试登录。登陆失败会跳转到 login 的 url。可以使用该过滤器做默认的登录逻辑。 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | 指定 url 需要 basic 登录 |
user | org.apache.shiro.web.filter.authc.UserFilter | 已登录或记住我的用户访问 |
port | org.apache.shiro.web.filter.authz.PortFilter | 指定端口才可以访问 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | 将请求方法转化成相应的动词来构造一个权限字符串 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 指定角色访问 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | https 访问 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 指定权限可以访问 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 登出过滤器 |
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc 表示需要认证(登录)才能使用,FormAuthenticationFilter 是表单认证,没有参数
roles:例子/admins/user/=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如 admins/user/=roles["admin,guest"],每个参数通过才算通过,相当于 hasAllRoles()方法。
perms:例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/=perms["user:add:,user:modify:"],当有多个参数时必须每个参数都通过才通过,想当于 isPermitedAll()方法。
rest:例子/admins/user/=rest[user],根据请求的方法,相当于/admins/user/=perms[user:method] ,其中 method 为 post,get,delete 等。
port:例子/admins/user/**=port[8081],当请求的 url 的端口不是 8081 是跳转到 schemal://serverName:8081?queryString,其中 schmal 是协议 http 或 https 等,serverName 是你访问的 host,8081 是 url 配置里 port 的端口,queryString
是你访问的 url 里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic 没有参数表示 httpBasic 认证
ssl:例子/admins/user/**=ssl 没有参数,表示安全的 url 请求,协议为 https
user:例如/admins/user/**=user 没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查
注:
anon,authcBasic,auchc,user 是认证过滤器,
perms,roles,ssl,rest,port 是授权过滤器
DEMO
设计数据库(RBAC 模型)
分为 5 个表,分别是 mage_user,mage_role,mage_permission,mage_user_role,mage_role_permission 五张表 mage_user,mage_role 是多对多关系,关联表为 mage_user_role。mage_permission,mage_role 是多对多关系,关联表为 mage_role_permission

数据库中共有两个用户张三、李四
张三是 admin 角色,拥有user:add user:delete user:update user:query 四个权限
李四是 consumer 角色 拥有 user:query权限
引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</dependency>
自定义 Realm
package com.mqb.auth.infra.config;
import com.mqb.auth.infra.realm.MageRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @author qingbomy
* @date 2022/8/13 20:42
*/
@Configuration
public class ShiroConfiguration {
/**
* 自定义Realm
* @return
*/
@Bean
public MageRealm mageRealm(){
return new MageRealm();
}
/**
*
* @param mageRealm 自定义Realm
* @param sessionManager session管理器 可以去掉shiro登录时url里的JSESSIONID导致404的问题
* @return SecurityManager 安全管理器
*/
@Bean
@Autowired
public SecurityManager securityManager(MageRealm mageRealm, @Qualifier("sessionManager") DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setSessionManager(sessionManager);
defaultWebSecurityManager.setRealm(mageRealm);
return defaultWebSecurityManager;
}
/**
*
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
@Autowired
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthor");
shiroFilterFactoryBean.setSuccessUrl("/home");
//为url添加权限
HashMap<String, String> filterChainDefinitionMap = new HashMap<>(16);
//后面可以从数据库中查询
//也可以在controller方法上添加
filterChainDefinitionMap.put("/toLogin","anon");
filterChainDefinitionMap.put("/user/add","perms[user:add]");
filterChainDefinitionMap.put("/user/update","perms[user:update]");
// filterChainDefinitionMap.put("/user/query","perms[user:query]");//测试使用注解完成权限设置
filterChainDefinitionMap.put("/user/delete","perms[user:delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
// 去掉shiro登录时url里的JSESSIONID
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* @param SecurityManager 安全管理器
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager SecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(SecurityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
shiro 配置类
package com.mqb.auth.infra.config;
import com.mqb.auth.infra.realm.MageRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @author qingbomy
* @date 2022/8/13 20:42
*/
@Configuration
public class ShiroConfiguration {
/**
* 自定义Realm
* @return
*/
@Bean
public MageRealm mageRealm(){
return new MageRealm();
}
/**
*
* @param mageRealm 自定义Realm
* @param sessionManager session管理器 可以去掉shiro登录时url里的JSESSIONID导致404的问题
* @return SecurityManager 安全管理器
*/
@Bean
@Autowired
public SecurityManager securityManager(MageRealm mageRealm, @Qualifier("sessionManager") DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setSessionManager(sessionManager);
defaultWebSecurityManager.setRealm(mageRealm);
return defaultWebSecurityManager;
}
/**
*
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
@Autowired
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthor");
shiroFilterFactoryBean.setSuccessUrl("/home");
//为url添加权限
HashMap<String, String> filterChainDefinitionMap = new HashMap<>(16);
//后面可以从数据库中查询
//也可以在controller方法上添加
filterChainDefinitionMap.put("/toLogin","anon");
// filterChainDefinitionMap.put("/user/add","perms[user:add]"); //测试使用注解完成权限设置
filterChainDefinitionMap.put("/user/update","perms[user:update]");
filterChainDefinitionMap.put("/user/query","perms[user:query]");
filterChainDefinitionMap.put("/user/delete","perms[user:delete]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
// 去掉shiro登录时url里的JSESSIONID
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* @param SecurityManager 安全管理器
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager SecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(SecurityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
Controller
@PostMapping("/login")
public String login(UserVo userVo, Model model) {
UsernamePasswordToken token = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword());
//获取当前用户
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
model.addAttribute("msg","登陆成功");
return "index";
}catch (UnknownAccountException e){
log.error(e.getMessage());
// model.addAttribute("msg","用户名不存在");
model.addAttribute("msg",e.getMessage());
return "login";
}catch (IncorrectCredentialsException e ){
log.error("密码错误");
model.addAttribute("msg","密码错误");
return "login";
}catch (LockedAccountException e){
log.error("账户被封禁");
model.addAttribute("msg","账户被封禁");
return "login";
}
几个页面的 controller
@GetMapping("/user/add")
public String add(){
return "user/add";
}
@GetMapping("/user/update")
public String update(){
return "user/update";
}
@GetMapping("/user/delete")
public String delete(){
return "/user/delete";
}
//要求当前用户拥有consumer角色身份才可以,并且该身份拥有user:query才可以访问query
@RequiresPermissions("user:query")
@RequiresRoles("consumer")
@GetMapping("/user/query")
public String query(){
return "/user/query";
}
启动项目使用张三登录
发现除了/user/query 其他的 url 都可以访问。原因是 张三虽然拥有user:query权限,但是他没有consumer角色的身份。
使用李四进行 登录
发现除了/user/query能访问之外其他的都无权访问。原因是 李四是consumer角 色,仅仅拥有/user/query的权限
缓存管理
-
配置 Redis 序例化方式
-
实现 shiro 的 Cache 接口
-
实现 shiro 的 CacheManager 接口
-
自定义 Realm 中设置 CacheManager
配置序例化方式
@Bean
public RedisConnectionFactory connectionFactory(){
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setHostName(host);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate( RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
//使用jackson序列化工具(default JDK serialization)序例化value
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
//设置任何属性可见
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//序列化的时候将类名称序列化到json串中
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
//设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//使用redis自带的字符串序列化工具序列化key
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//key
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
//value
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//初始化redisTemplate
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
实现 cache 接口
package com.mqb.auth.infra.config.cache;
import lombok.Getter;
import lombok.Setter;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;
/**
* @author qingbomy
* @date 2022/9/5 23:39
*/
@Setter
@Getter
@Component
public class RedisCache<K, V> implements Cache<K, V> {
@Resource(name = "redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
private String cacheName;
@Override
public V get(K k) throws CacheException {
return (V) redisTemplate.opsForHash().get(cacheName, k.toString());
}
@Override
public V put(K k, V v) throws CacheException {
redisTemplate.opsForHash().put(cacheName, k.toString(), v);
return v;
}
@Override
public V remove(K k) throws CacheException {
V value = (V) redisTemplate.opsForHash().get(cacheName, k.toString());
redisTemplate.opsForHash().delete(cacheName, k.toString());
return value;
}
@Override
public void clear() throws CacheException {
redisTemplate.delete(cacheName);
}
@Override
public int size() {
return redisTemplate.opsForHash().size(cacheName).intValue();
}
@Override
public Set<K> keys() {
return (Set<K>) redisTemplate.opsForHash().keys(cacheName);
}
@Override
public Collection<V> values() {
return (Collection<V>) redisTemplate.opsForHash().values(cacheName);
}
}