一、前言
由于之前没有使用过 Shiro,最近开始使用,故对其部分流程和源码进行了阅读,大体总结了一些内容记录下来。本系列并不会完完全全分析 Shiro 的全部代码,仅把主(我)要(用)流(到)程(的) 简单分析一下。由于本系列大部分为个人内容理解 并且 个人学艺实属不精,故难免出现 “冤假错乱”。如有发现,感谢指正,不胜感激。
Shiro 源码分析全集:
本文会搭建一个最简单的Springboot + Shiro项目,目的在于为后续的Shiro 源码分析做准备,故不会详细介绍搭建过程。下面先介绍Shiro 中两个比较关键的类的实现。
1. SessionDao
- SessionDAO :定义了从持久层操作session的标准;
- AbstractSessionDAO :提供了SessionDAO的基础实现,如生成会话ID等;
- CachingSessionDAO :提供了对开发者透明的session缓存的功能,只需要设置相应的 CacheManager 即可;
- MemorySessionDAO :直接在内存中进行session维护;
- EnterpriseCacheSessionDAO :提供了缓存功能的session维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。
2. SessionManager
- DefaultSessionManager :JavaSE环境
- ServletContainerSessionManager: Web环境,直接使用servlet容器会话。DefaultWebSecurityManager中默认使用的是ServletContainerSessionManager
- DefaultWebSessionManager:用于Web环境的实现,可以替第二个,自己维护着会话,直接废弃了Servlet容器的会话管理
3. 各种过滤器
二、项目搭建
1. ShiroConfig
这是Shiro 的主要配置类
@Configuration
public class ShiroConfig {
/**
* 配置Shiro生命周期处理器。
* 在 Bean创建时调用 init 方法,在 销毁时调用 destroy 方法
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 定制的Realm,来完成认证和鉴权的部分功能
*
* @return
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
/**
* SecurityManager :核心类,具有极高的扩展性
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置定制的 Realm
securityManager.setRealm(customRealm());
// 配置 Session管理器
securityManager.setSessionManager(sessionManager());
// // 设置缓存管理器
// securityManager.setCacheManager();
// // 设置 RememberMeManager 来管理 RememberMeManager
// securityManager.setRememberMeManager();
return securityManager;
}
/**
* 设置 ShiroFilter
* 这里可以配置 Shiro 对接口的控制权限
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 使用Map映射,key是接口路径,value为指定的Filter 名称,具体对应名称参考上图
// 这里的配置是有顺序的,如果一个路径匹配多个过滤,那么只有先配置的过滤器生效
Map<String, String> map = new HashMap<>();
//配置登出接口
map.put("/logout", "logout");
// 放行登录接口
map.put("/shiro/login", "anon");
// 拦截其他接口
map.put("/**", "authc");
//设置登录路径,session失效会跳转该页面
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
//登录成功页面
shiroFilterFactoryBean.setSuccessUrl("/success");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 这里指定了动态代理的方式使用了 Cglib
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 设置是否直接代理目标类,而不是仅代理特定的接口。 默认值为“ false”。
// 将此设置为“ true”可强制代理TargetSource的公开目标类。 如果该目标类是接口,则将为给定接口创建一个JDK代理。 如果该目标类是任何其他类,则将为给定类创建CGLIB代理。
// 注意:如果未指定接口(并且未激活接口自动检测),则根据具体代理工厂的配置,也会应用代理目标类行为
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 开启注解支持,包括 RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class。
* 和Aop相同的逻辑,通过注入 Advisor 来增强一些类的和方法
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 在 DefaultWebSessionManager 情况下可以自定义Cookies 的一些信息
* @return
*/
public SimpleCookie sessionIdCookie() {
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* 设置SessionManager,由我们自己来控制Session
* @return
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//配置监听
// listeners.add(sessionListener());
// sessionManager.setSessionListeners(listeners);
// sessionManager.setSessionIdCookie(sessionIdCookie());
// sessionManager.setSessionDAO(sessionDAO());
// sessionManager.setCacheManager(ehCacheManager());
//全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(3600000);
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
}
2. CustomRealm
CustomRealm 继承了AuthorizingRealm 类, 用来进行认证和鉴权的操作
public class CustomRealm extends AuthorizingRealm {
/**
* 权限认证
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
User user = UserConstants.map.get(name);
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//添加权限
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
}
}
return simpleAuthorizationInfo;
}
/**
* 认证认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
User user = UserConstants.map.get(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
return simpleAuthenticationInfo;
}
}
}
3. ShiroDemoController
这里是测试用的控制层,为了简单,这里直接没有业务层,使用模拟数据
/**
* @Author : wlhao
* @Email : [email protected]
* @Data: 2020/11/19 14:04
* @Des:
*/
@RequestMapping("shiro")
@RestController
public class ShiroDemoController {
@PostMapping("admin")
// 需要admin角色,这里就说明只有 “张三”的账号才能访问
@RequiresRoles("admin")
public String admin() {
return "admin";
}
@PostMapping("user")
// 需要user角色, 这里只有“李四的账号才能访问”
@RequiresRoles("user")
public String user(HttpServletRequest request) {
HttpSession session = request.getSession(true);
return "user";
}
@PostMapping("login")
public String login(HttpServletRequest request, HttpServletResponse response) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("张三", "123456");
Subject subject = SecurityUtils.getSubject();
subject.login(usernamePasswordToken);
return "login";
}
}
4. pojo 类
@Data
public class Permissions {
private String id;
private String permissionsName;
}
@Data
public class Role {
private String id;
private String roleName;
/**
* 角色对应权限集合
*/
private Set<Permissions> permissions;
public Role() {
}
public Role(String id, String roleName, Set<Permissions> permissions) {
this.id = id;
this.roleName = roleName;
this.permissions = permissions;
}
}
@Data
public class User {
private String id;
private String userName;
private String password;
/**
* 用户对应的角色集合
*/
private Set<Role> roles;
public User(String id, String userName, String password, Set<Role> roles) {
this.id = id;
this.userName = userName;
this.password = password;
this.roles = roles;
}
public User() {
}
}
5. UserConstants
这里对User 用户进行数据模拟
/**
* @Author : wlhao
* @Email : [email protected]
* @Data : 2021/1/28 10:54
* @Desc : 模拟角色
*/
public class UserConstants {
/**
* 两个角色
* 1. 张三 -》admin :具有 query,add 权限
* 2. 李四 -》user : 具有 query 权限
*/
public static Map<String, User> map = new HashMap<>();
static {
Permissions addPermissions = new Permissions("1", "query");
Permissions queryPermissions = new Permissions("2", "add");
Role adminRole = new Role("1", "admin", Sets.newHashSet(addPermissions, queryPermissions));
Role userRole = new Role("2", "user", Sets.newHashSet(queryPermissions));
User user = new User("1", "张三", "123456", Sets.newHashSet(adminRole));
User user1 = new User("2", "李四", "123456", Sets.newHashSet(userRole));
map.put(user.getUserName(), user);
map.put(user1.getUserName(), user1);
}
}
6. 异常拦截
全局异常拦截,方便看返回
@ControllerAdvice
@Slf4j
public class ShiroExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("没有通过权限验证!", e);
return "没有通过权限验证! " + e.getMessage();
}
}
三、验证
当我们使用张三账号登录后,是无权访问user接口的,但是却可以访问 admin接口
以上:内容部分参考
https://blog.csdn.net/dgh112233/article/details/100083287
https://www.zhihu.com/pin/1105962164963282944
https://blog.csdn.net/qq_30643885/article/details/91886448
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正