Information Security 21 min read

Integrating Apache Shiro with Spring Boot: Configuration, Session Management, and Permission Control

This guide demonstrates how to integrate the lightweight Apache Shiro security framework into a Spring Boot application, covering environment setup, Maven dependencies, Redis-backed session and cache configuration, utility classes, core Shiro components, annotation‑based permission control, and Postman testing for token handling and dynamic permission updates.

Java Captain
Java Captain
Java Captain
Integrating Apache Shiro with Spring Boot: Configuration, Session Management, and Permission Control

Shiro is a lightweight security framework used for authentication, authorization, encryption, and session management in Spring Boot projects, offering a simpler alternative to Spring Security while still meeting most business requirements.

The project environment includes MyBatis‑Plus 3.1.0, Spring Boot 2.1.5, JDK 1.8, Shiro 1.4, and the Shiro‑Redis plugin 3.1.0.

Maven dependencies required for the project are listed below:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- AOP dependency, required for permission interception -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Lombok plugin -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- MyBatis‑Plus core library -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!-- Alibaba Druid connection pool -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.6</version>
    </dependency>
    <!-- Shiro core dependency -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- Shiro‑Redis plugin -->
    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!-- Apache Commons Lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
    </dependency>
</dependencies>

The application configuration (YAML) defines the server port, MySQL datasource, Redis connection, and MyBatis‑Plus settings such as mapper locations, primary key strategy, field strategy, and SQL logging.

# Configuration port
server:
  port: 8764
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
  redis:
    host: localhost
    port: 6379
    timeout: 6000
    password: 123456
    jedis:
      pool:
        max-active: 1000
        max-wait: -1
        max-idle: 10
        min-idle: 5
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  global-config:
    db-config:
      id-type: auto
      field-strategy: NOT_EMPTY
      db-type: MYSQL
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Utility classes are created to simplify Spring context access and Shiro operations. SpringUtil implements ApplicationContextAware to provide a static method for retrieving beans. ShiroUtils offers methods to obtain the current session, logout, fetch user info, and delete cached authentication data.

/**
 * Spring context utility class
 */
@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    public static
T getBean(Class
beanClass) {
        return context.getBean(beanClass);
    }
}

/**
 * Shiro utility class
 */
public class ShiroUtils {
    private ShiroUtils() {}
    private static final RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
    public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }
    public static void logout() {
        SecurityUtils.getSubject().logout();
    }
    public static SysUserEntity getUserInfo() {
        return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
    }
    public static void deleteCache(String username, boolean isRemoveSession) {
        // Implementation omitted for brevity
    }
}

The core Shiro components include a custom ShiroRealm for authentication and authorization, a ShiroConfig class that defines beans for the security manager, filter chain, credential matcher, Redis cache manager, session manager, and session ID generator, and a ShiroSessionManager that uses the Redis‑backed RedisSessionDAO .

/**
 * Shiro Realm for authentication and authorization
 */
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private SysMenuService sysMenuService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        SysUserEntity user = (SysUserEntity) principalCollection.getPrimaryPrincipal();
        Long userId = user.getUserId();
        Set
roles = new HashSet<>();
        Set
perms = new HashSet<>();
        List
roleList = sysRoleService.selectSysRoleByUserId(userId);
        for (SysRoleEntity role : roleList) {
            roles.add(role.getRoleName());
            List
menuList = sysMenuService.selectSysMenuByRoleId(role.getRoleId());
            for (SysMenuEntity menu : menuList) {
                perms.add(menu.getPerms());
            }
        }
        info.setRoles(roles);
        info.setStringPermissions(perms);
        return info;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        SysUserEntity user = sysUserService.selectUserByName(username);
        if (user == null) {
            throw new AuthenticationException();
        }
        if (user.getState() == null || "PROHIBIT".equals(user.getState())) {
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()),
                getName()
        );
        ShiroUtils.deleteCache(username, true);
        return info;
    }
}

@Configuration
public class ShiroConfig {
    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";
    private final int EXPIRE = 1800;
    @Value("${spring.redis.host}") private String host;
    @Value("${spring.redis.port}") private int port;
    @Value("${spring.redis.timeout}") private int timeout;
    @Value("${spring.redis.password}") private String password;
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        Map
map = new LinkedHashMap<>();
        map.put("/static/**", "anon");
        map.put("/userLogin/**", "anon");
        map.put("/**", "authc");
        bean.setLoginUrl("/userLogin/unauth");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setSessionManager(sessionManager());
        manager.setCacheManager(cacheManager());
        manager.setRealm(shiroRealm());
        return manager;
    }
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
        matcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
        return matcher;
    }
    @Bean
    public RedisManager redisManager() {
        RedisManager manager = new RedisManager();
        manager.setHost(host);
        manager.setPort(port);
        manager.setTimeout(timeout);
        manager.setPassword(password);
        return manager;
    }
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager cm = new RedisCacheManager();
        cm.setRedisManager(redisManager());
        cm.setKeyPrefix(CACHE_KEY);
        cm.setPrincipalIdFieldName("userId");
        return cm;
    }
    @Bean
    public ShiroSessionIdGenerator sessionIdGenerator() {
        return new ShiroSessionIdGenerator();
    }
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO dao = new RedisSessionDAO();
        dao.setRedisManager(redisManager());
        dao.setSessionIdGenerator(sessionIdGenerator());
        dao.setKeyPrefix(SESSION_KEY);
        dao.setExpire(EXPIRE);
        return dao;
    }
    @Bean
    public SessionManager sessionManager() {
        ShiroSessionManager manager = new ShiroSessionManager();
        manager.setSessionDAO(redisSessionDAO());
        return manager;
    }
}

Permission control is demonstrated using Shiro annotations such as @RequiresRoles , @RequiresPermissions , @RequiresUser , and logical operators (AND/OR). Example controller methods show role‑restricted endpoints, permission‑restricted endpoints, and logout functionality.

@RestController
@RequestMapping("/role")
public class UserRoleController {
    @Autowired private SysUserService sysUserService;
    @Autowired private SysRoleService sysRoleService;
    @Autowired private SysMenuService sysMenuService;
    @Autowired private SysRoleMenuService sysRoleMenuService;
    @RequestMapping("/getAdminInfo")
    @RequiresRoles("ADMIN")
    public Map
getAdminInfo(){
        Map
map = new HashMap<>();
        map.put("code",200);
        map.put("msg","Only ADMIN role can access this endpoint");
        return map;
    }
    @RequestMapping("/getUserInfo")
    @RequiresRoles("USER")
    public Map
getUserInfo(){
        Map
map = new HashMap<>();
        map.put("code",200);
        map.put("msg","Only USER role can access this endpoint");
        return map;
    }
    @RequestMapping("/getRoleInfo")
    @RequiresRoles(value={"ADMIN","USER"}, logical=Logical.OR)
    @RequiresUser
    public Map
getRoleInfo(){
        Map
map = new HashMap<>();
        map.put("code",200);
        map.put("msg","ADMIN or USER role can access this endpoint");
        return map;
    }
    @RequestMapping("/getLogout")
    @RequiresUser
    public Map
getLogout(){
        ShiroUtils.logout();
        Map
map = new HashMap<>();
        map.put("code",200);
        map.put("msg","Logged out");
        return map;
    }
}

Postman tests illustrate token generation after successful login, cache behavior for permissions, dynamic permission updates (adding a new permission to the ADMIN role), and the effect of clearing the Redis cache to force re‑authorization.

The complete source code for the demo project is available at the following repositories:

https://gitee.com/liselotte/spring-boot-shiro-demo

https://github.com/xuyulong2017/my-java-demo

JavaRedisSpring BootsecurityAuthenticationauthorizationshiro
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.