java代码审计之shiro安全机制研究

xiao1star2026-02-24文章来源:SecHub网络安全社区


前言

在代码审计中,大家肯定都了解Spring或者servlet的过滤器filter以及拦截器listener,这些一般都用于鉴权上,我们可以通过查看这些拦截器或者过滤器对哪些路径进行了拦截进而发现一些未授权的路径。随着本人审计代码数目的增多,发现了有些网站是使用shiro来进行鉴权,因此特写此片来分析一下shiro的鉴权机制,也希望对是师傅们有所帮助。

1.简介

shiro是一款主流的Java安全框架,不依赖任何容器,可以运行在JavaSE和JavaEE项目中,它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。其实类似于Filter过滤器,是一个解决安全管理的系统化框架。

2.shrio核心组件

shiro的运行机制如下所示

image-20260119161253383

  1. UsernamePasswordToken:Shiro用来封装用户登录信息,使用用户的登录信息来创建令牌Token。
  2. SecurityManager:Shiro的核心部分,负责安全认证和授权。
  3. Subject:Shiro的一个抽象概念,包含了用户信息。
  4. Realm:开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm中。
  5. Authenticationinfo:用户的角色信息集合,认证时使用。
  6. Authorzationinfo:角色的权限信息集合,授权时使用。
  7. DefaultWebSecurityDManager:安全管理器,开发者自定义的Realm需要注入到DefaultWebSecurityDManager进行管理才能生效。
  8. ShiroFilterFactoryBean:过滤器工厂,Shiro的基本运行机制是开发者定制规则,Shiro去执行,具体的执行操作就是由ShiroFilterFactoryBean创建的一个个Filter 对象来完成。

3.spring Boot整合shiro

  1. 创建spring boot项目,添加spring-shiro依赖

https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring/2.0.6

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>2.0.6</version>
</dependency>

2.自定义Shiro过滤器

数据库设计

image-20260119163644209

//自定义realm public class AccountRealm extends AuthorizingRealm { AccountService accountService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //身份认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;//包含用户信息 Account account = accountService.findByUsername(usernamePasswordToken.getUsername());//验证用户名是否正确 if(account != null){ return new SimpleAuthenticationInfo(account, account.getPassword(), this.getName());//验证密码是否正确 } return null; } }

3.配置—实现realm的生效

//shiro配置 @Configuration public class shiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManage){ //2.将securityManager放到ShiroFilterFactoryBean最终实现accountrealm的注入 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManage); return shiroFilterFactoryBean; } @Bean public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("accountRealm") AccountRealm accountRealm) { //1.为了使得AccountRealm生效,将自定义的AccountRealm注入到DefaultWebSecurityManager DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(accountRealm); return securityManager; } @Bean public AccountRealm accountRealm() { return new AccountRealm(); } }

4.编写认证和授权规则

认证过滤器:

  • anon:无需认证,
  • authc:必须认证
  • authcBasic:需要通过HTTPBasic认证
  • user:不一定通过认证,只要曾经被shiro记录即可,例如:记住我

授权过滤器:

  • perms:必须拥有某个权限才可访问
  • roles:必须拥有某个角色才能访问
  • port:请求的端口必须是指定值才可以
  • rest:请求必须基于RESTful、POST、GET、DELET
  • ssl:必须是安全的URL请求,协议HTTPS

main.html–必须登录才可以访问

manage.html–当前用户必须拥有manage授权才能访问

administrator.html–当前用户必须拥有administrator角色才能访问

获取当前用户的角色和权限信息添加到SimpleAuthorizationInfo中

@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取当前登录的用户信息 Subject subject = SecurityUtils.getSubject(); Account account=(Account)subject.getPrincipal(); //设置角色 Set<String> roles = new HashSet<>(); roles.add(account.getRole()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); //设置权限 info.addStringPermission(account.getPerms()); return info; }

在config中设置访问权限

@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManage){ //2.将securityManager放到ShiroFilterFactoryBean最终实现accountrealm的注入 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManage); //权限设置 Map<String,String> map = new Hashtable<>(); map.put("/main", "authc"); map.put("/manage","perms[manage]");//权限 map.put("/administrator","roles[administrator]");//角色 shiroFilterFactoryBean.setFilterChainDefinitionMap(map); shiroFilterFactoryBean.setLoginUrl("/login"); return shiroFilterFactoryBean; }

4.shiro验证流程

image-20260122105946199

1.将自定义的AccountRealm注入到DefaultWebSecurityManager,可以看到下面defaultWebSecurityManager中包含了我们自定义的realm信息

image-20260122110155193

2.接着就会来到shiroFilterFactoryBean,将前面的defaultWebSecurityManager注入到其中,其实也会把realm注入到里面

image-20260122110604623

3.接着登录就会来到相关的controller

image-20260122111032845

在这里可以看到会将用户的账号密码生成对应的token中然后将token放入到Subject中

image-20260122111038985

4.接着就会来到我们自定义的realm中进行身份认证,认证我们的账号密码是否正确,最后将用户的角色信息集合放入到SimpleAuthenticationInfo

image-20260122111335377

如果账号或者密码有不正确的地方都会进行相关信息的回显

image-20260122111439280

4.当选择一个需鉴权的页面时,就会来到自定义realmdoGetAuthorizationInfo

image-20260122112700206

接着可以看到Subject就有了当前登录的用户信息,然后回获取到当前用户的角色权限信息,然后放入到SimpleAuthorizationInfo

image-20260122111956193

5.再往后就会来到AuthenticatingRealm(shiro框架自带的realm)查看当前用户的角色权限是否符合标准,如果符合就放行

image-20260122112322918

5.JeecgBoot的shiro过滤器机制研究

版本:JeecgBoot3.5.0

JeecgBoot的shiro鉴权和前面写的小demo有所不同,JeecgBoot没有在登录时直接将用户信息注入到Subject中,而是验证用户的账号密码是否正确之后生成了一个token,然后当访问鉴权页面之后会调用JWTfilter,然后将token放入到Subject中

首先要知道JwtFilter继承了BasicHttpAuthenticationFilter,也就是说JwtFilter是Shiro的一个过滤器,那么就去shiroConfig中查看

image-20260122155947529

1.在ShiroConfig中可以看到也是将自定义的Realm注入到DefaultWebSecurityManager

image-20260122160254855

2.然后就是将SecurityManager注入到shiroFilterFactoryBean中,同时也可以看到filterChainDefinitionMap对一些路径的设置为anon

image-20260122160558760

3.在往下可以看到自定义了一个规则,让所有的路径均经过JwtFilter过滤器

image-20260122160822183

4.回到JwtFilter中,在isAccessAllowed方法中会调用executeLogin方法,注意这个isAccessAllowed方法会自动执行,因为isAccessAllowed 方法的执行是由 AccessControlFilter.doFilterInternal 方法(JwtFilter的某个上层父类)自动触发的

image-20260122161108864

5.接着就会获取到请求中的token,然后调用getSubject.login,将我们的token放入到Subject

image-20260122161635851

6.然后就会来到自定义的Realm中获取token先进行身份认证

image-20260122161856320

7.然后再来到doGetAuthorizationInfo方法中进行权限认证

image-20260122162057887

6.总结

以上就是shiro框架的整个验证流程同时结合真实案例进行了讲解,简述下就是

  1. 将自定义Realm注入到shiro框架中
  2. 用户信息封装到Subject
  3. 获取Subject信息进行身份认证与权限认证

那么在代码审计中重点关注配置文件ShiroConfigShiroFilterFactoryBean中对各个路径的规则配置,重点是规则为anno的路径这是没有鉴权的