Backend Development 24 min read

Microservice Permission Design and Implementation with Shiro in Spring Boot

This article presents a comprehensive guide to designing and implementing fine‑grained permission control for microservices using Apache Shiro, covering the architectural design, shared session handling with Redis, custom cache and session managers, realm implementation, and practical testing across user and video services.

Architect
Architect
Architect
Microservice Permission Design and Implementation with Shiro in Spring Boot

The article begins by explaining the need for a permission framework in a microservice project and compares Shiro with Spring Security, ultimately choosing Shiro for its lightweight nature.

Design Scheme : Two problematic approaches are discussed—placing Shiro and the gateway in the same service, and sharing a Shiro configuration module across services—both of which lead to tight coupling. The final solution separates the user service and Shiro modules, sharing a common session cache.

Project Structure (simplified):

common-core
common-cache
common-auth
gateway-service
user-api
user-provider-service
user-consumer-service
video-api
video-provider
video-consumer

Shared Session : To share sessions across services, the default in‑memory MemorySessionDAO is replaced with a custom EnterpriseCacheSessionDAO backed by Redis. The article shows how to configure a custom CacheManager and SessionDAO to store SimpleSession objects as byte arrays.

Key code for the custom cache manager:

@Component("myCacheManager")
public class MyCacheManager implements CacheManager {
    @Override
    public
Cache
getCache(String name) throws CacheException {
        return new MyCache();
    }
}

Redis client based on Jedis is implemented to set, get, and delete byte‑array values, with connection pooling and retry logic:

public static JedisPool initialPool() {
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxIdle(MAX_IDLE);
    config.setMaxTotal(MAX_ACTIVE);
    config.setMaxWaitMillis(MAX_WAIT);
    return new JedisPool(config, HOST, PORT, TIMEOUT, PASSWORD, DATABASE);
}

The cache implementation serializes objects to bytes and stores them in Redis:

public Object get(Object key) throws CacheException {
    byte[] bytes = JedisClient.getValue(objectToBytes(key));
    return bytes == null ? null : (SimpleSession) bytesToObject(bytes);
}
public Object put(Object key, Object value) throws CacheException {
    JedisClient.setValue(objectToBytes(key), objectToBytes(value), (int) cacheExpireTime.getSeconds());
    return key;
}

Shiro Configuration (Spring Boot):

@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager securityManager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    bean.setSecurityManager(securityManager);
    Map
filterMap = new LinkedHashMap<>();
    filterMap.put("/**", "authc");
    bean.setFilterChainDefinitionMap(filterMap);
    return bean;
}

@Bean(name = "SecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm,
        @Qualifier("myDefaultWebSessionManager") DefaultWebSessionManager sessionManager,
        @Qualifier("myCacheManager") MyCacheManager cacheManager) {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setRealm(userRealm);
    manager.setSessionManager(sessionManager);
    manager.setCacheManager(cacheManager);
    return manager;
}

Custom Realm for user service:

public class UserRealm extends AuthorizingRealm {
    @Reference(version = "0.0.1")
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(userService.selectRolesByUsername(userName));
        info.setStringPermissions(userService.selectPermissionByUsername(userName));
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String) token.getPrincipal();
        User user = userService.selectByUsername(userName);
        return user != null ? new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm") : null;
    }
}

The article also shows how to configure a custom DefaultWebSessionManager with a SimpleCookie and the EnterpriseCacheSessionDAO to enable session sharing.

Testing : The author demonstrates logging in via the /user/login endpoint, accessing a protected /user/testFunc endpoint that requires the admin role, and then adding the role in the database to verify access. The same session is used to call the video service /video/getVideo , confirming that the shared Redis session works across microservices.

Finally, the article notes that an API gateway is needed to route unauthenticated requests to a common /user/unAuth page, completing the end‑to‑end permission solution for a distributed system.

JavamicroservicesRedisSpring BootAuthorizationSession Managementshiro
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.