Integrating Apache Shiro with Spring Boot for Authentication and Authorization
This guide demonstrates how to integrate Apache Shiro into a Spring Boot application, covering project setup, Maven dependencies, configuration of Redis-backed session and cache management, creation of utility and realm classes, and implementation of role‑based permission controls with example controllers and Postman testing.
This article provides a step‑by‑step tutorial for integrating the Apache Shiro security framework into a Spring Boot project, enabling authentication, authorization, encryption, and session management with Redis as the backing store.
Project Environment
MyBatis‑Plus version: 3.1.0
SpringBoot version: 2.1.5
JDK version: 1.8
Shiro version: 1.4
Shiro‑redis plugin version: 3.1.0
Maven Dependencies
<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 Shiro annotations -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<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 -->
<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>
<!-- Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>Application Configuration (application.yml)
# Configure server port
server:
port: 8764
spring:
# Data source configuration
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 configuration
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 settings
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.StdOutImplUtility Classes
SpringUtil – a helper to obtain Spring beans from static context.
/**
* @Description 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);
}
}ShiroUtils – common Shiro operations such as session retrieval, logout, and cache clearing.
/**
* @Description Shiro utility class
*/
public class ShiroUtils {
private static 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 iterates Redis sessions, removes matching user session and cache
// (code omitted for brevity)
}
}Shiro Core Classes
ShiroRealm – handles authentication and authorization logic.
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 authInfo = new SimpleAuthenticationInfo(
user,
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
ShiroUtils.deleteCache(username, true);
return authInfo;
}
}ShiroConfig – configures Redis manager, cache manager, session manager, credential matcher, and filter chain.
@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 manager = new RedisCacheManager();
manager.setRedisManager(redisManager());
manager.setKeyPrefix(CACHE_KEY);
manager.setPrincipalIdFieldName("userId");
return manager;
}
@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 Examples
Controllers demonstrate role‑based and permission‑based annotations such as @RequiresRoles , @RequiresPermissions , and @RequiresUser . Sample endpoints include admin‑only, user‑only, and combined role access, as well as dynamic permission updates that clear Shiro caches.
@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");
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");
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");
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;
}
}Similar controllers for menu data ( UserMenuController ) and login handling ( UserLoginController ) are provided, illustrating how to obtain the current session token and return JSON responses.
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {
@Autowired private SysUserService sysUserService;
@RequestMapping("/login")
public Map
login(@RequestBody SysUserEntity sysUserEntity){
Map
map = new HashMap<>();
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
subject.login(token);
} catch (IncorrectCredentialsException e) {
map.put("code",500);
map.put("msg","User not found or wrong password");
return map;
} catch (LockedAccountException e) {
map.put("code",500);
map.put("msg","Account locked");
return map;
} catch (AuthenticationException e) {
map.put("code",500);
map.put("msg","Authentication failed");
return map;
}
map.put("code",0);
map.put("msg","Login successful");
map.put("token", ShiroUtils.getSession().getId().toString());
return map;
}
@RequestMapping("/unauth")
public Map
unauth(){
Map
map = new HashMap<>();
map.put("code",500);
map.put("msg","Unauthenticated");
return map;
}
}Testing with Postman
After logging in, the returned token is used as a header for subsequent requests. The article shows how Redis caches permissions after the first authorized call, how to add new permissions dynamically (e.g., assigning a menu to the ADMIN role), clear the cache, and verify that the updated permissions take effect.
Source Code
Full project source is available at:
https://gitee.com/liselotte/spring-boot-shiro-demo
https://github.com/xuyulong2017/my-java-demo
These repositories contain the complete implementation of the tutorial, including entity classes, service layers, and the configuration described above.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.