Backend Development 26 min read

Designing Microservice Permission Architecture with Shiro and Redis

This article explains how to design and implement a microservice permission system using Apache Shiro, Spring Boot, Dubbo, and Redis, covering challenges of integrating Shiro with gateways, shared session management, custom Cache and SessionDAO implementations, and provides complete code examples and configuration details.

Top Architect
Top Architect
Top Architect
Designing Microservice Permission Architecture with Shiro and Redis

The author presents a comprehensive solution for fine‑grained permission control in a microservice environment, where each component (user service, video service, etc.) requires authentication and authorization.

Initially, two naive approaches are examined: placing Shiro and the API gateway in the same service, and sharing a single Shiro configuration module across all services. Both approaches fail because Shiro’s Realm needs to query user data from the user service, creating a circular dependency.

The final design separates the Shiro module for the user service from a shared Shiro module used by other services, while sharing the session data via Redis. This allows independent authentication in the user service and consistent authorization across all services.

Project Structure

The project consists of several Maven modules:

common-core : common utilities and constants.

common-cache : Redis‑based cache and session DAO implementation.

common-auth : shared Shiro configuration, custom realm, and authentication logic.

gateway-service : API gateway entry point.

user-api , user-provider-service , user-consumer-service : user‑related services.

video-api , video-provider , video-consumer : video‑related services.

Redis‑Based Session Sharing

A custom EnterpriseCacheSessionDAO is configured to use Redis instead of the default in‑memory MemorySessionDAO . The JedisClient class provides low‑level Redis operations (set, get, delete) with connection pooling.

public class JedisClient {
    private static JedisPool jedisPool;
    public static Jedis getJedis() { ... }
    public static void setValue(byte[] key, byte[] value, int seconds) { ... }
    public static byte[] getValue(byte[] key) { ... }
    // other utility methods
}

The custom cache implementation serializes Shiro SimpleSession objects to byte arrays before storing them in Redis:

public class MyCache implements Cache
{
    public Object get(Object key) {
        byte[] bytes = JedisClient.getValue(objectToBytes(key));
        return bytes == null ? null : (SimpleSession) bytesToObject(bytes);
    }
    public Object put(Object key, Object value) {
        JedisClient.setValue(objectToBytes(key), objectToBytes(value), (int) cacheExpireTime.getSeconds());
        return key;
    }
    // serialization helpers omitted for brevity
}

Custom Realm

Each service defines its own UserRealm extending AuthorizingRealm . The realm fetches roles and permissions from the user service via Dubbo. Authentication is performed only in the user service; other services return null for doGetAuthenticationInfo because authentication has already occurred.

@Component
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) {
        return null; // authentication handled by user service
    }
}

Shiro Configuration

The ShiroConfig class wires the custom realm, the shared cache manager, and a DefaultWebSessionManager that uses the Redis‑backed EnterpriseCacheSessionDAO . It also defines a ShiroFilterFactoryBean that protects all URLs with the authc filter.

@Configuration
public class ShiroConfig {
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@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 securityManager(@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;
    }
    // other beans omitted for brevity
}

Login Flow

The UserController receives login requests, creates a UsernamePasswordToken , and calls SecurityUtils.getSubject().login(token) . Upon successful login, Shiro creates a session that is stored in Redis, making it accessible to all microservices.

@RestController
@RequestMapping("/user")
public class UserController {
    @Reference(version = "0.0.1")
    private UserService userService;

    @PostMapping("/login")
    public R login(@RequestBody User user) {
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return R.ok();
        } catch (Exception e) {
            return R.failed();
        }
    }
    // endpoint for unauthorized access omitted
}

Testing and Results

After logging in, the user can call protected endpoints such as /user/testFunc (requires the admin role) or /video/getVideo . When the user lacks the required role, Shiro redirects to the login page or the custom unauthorized URL. Adding the appropriate role in the database enables access, demonstrating that session data and authorization information are correctly shared across services via Redis.

The article concludes that the shared‑cache approach using Redis and custom Shiro components provides a clean, scalable way to enforce permissions in a microservice architecture.

JavaMicroservicesRedisSpring Bootauthorizationshiro
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.