Mastering Apache Shiro: Core Concepts, Real‑World Usage, and JWT Microservice Integration

This article explains Apache Shiro’s fundamental components—Subject, SecurityManager, Realm, Session, authentication, authorization, and cryptography—then walks through a complete configuration example, a custom Realm, JWT integration, and a Spring Boot microservice scenario to illustrate secure, stateless authentication and fine‑grained permission control.

Java Architecture Stack
Java Architecture Stack
Java Architecture Stack
Mastering Apache Shiro: Core Concepts, Real‑World Usage, and JWT Microservice Integration

Introduction

Apache Shiro is a lightweight Java security framework that provides authentication, authorization, session management, and cryptographic utilities.

Core Concepts

Subject Represents the current user or system entity. It is obtained via SecurityUtils.getSubject() and holds authentication state and permission data.

SecurityManager The central component that coordinates all security operations, including authentication, authorization, and session handling.

Realm Connects Shiro to data sources (e.g., databases, LDAP) to retrieve user, role, and permission information. Custom realms are created by extending AuthorizingRealm .

Session Shiro’s own session management works independently of the servlet container, making it usable in both web and non‑web environments.

Authentication Validates a user’s identity, typically via subject.login(token) . Supports username/password, OAuth2, JWT, etc.

Authorization Checks roles and permissions using subject.hasRole(...) or subject.isPermitted(...) .

Cryptography Provides hashing, salting, and encryption utilities for protecting sensitive data.

Basic Usage Example

Configure Shiro via shiro.ini (or programmatically), define a custom realm, and perform authentication and authorization.

[main]
# Configure SecurityManager
securityManager = org.apache.shiro.mgt.DefaultSecurityManager

# Configure Realm
myRealm = com.wg.MyCustomRealm
securityManager.realms = $myRealm

Custom realm implementation (simplified):

public class MyCustomRealm extends AuthorizingRealm {
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // Retrieve username/password from DB and return AuthenticationInfo
        return new SimpleAuthenticationInfo(username, password, getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole("admin");
        info.addStringPermission("user:read");
        return info;
    }
}

Authentication and authorization flow:

Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
    UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
    try {
        currentUser.login(token);
        System.out.println("Authentication succeeded");
    } catch (AuthenticationException e) {
        System.out.println("Authentication failed");
    }
}
if (currentUser.hasRole("admin")) {
    System.out.println("User has admin role");
}
if (currentUser.isPermitted("user:read")) {
    System.out.println("User has user:read permission");
}

Microservice Scenario with JWT

Implement stateless authentication for a distributed order‑management system using Shiro and JWT.

Key Requirements

User authentication via username/password.

Role‑based access: only admins can delete orders.

Stateless token handling using JWT.

Cross‑service permission checks.

Technology Stack

Spring Boot

Apache Shiro

JWT (io.jsonwebtoken)

Spring Data JPA

Implementation Steps

1. Maven Dependencies

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

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

2. JWT Filter

public class JwtFilter extends BasicHttpAuthenticationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String token = httpRequest.getHeader("Authorization");
        if (StringUtils.isBlank(token)) {
            return false;
        }
        try {
            JwtToken jwtToken = new JwtToken(token);
            getSubject(request, response).login(jwtToken);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

3. Custom JWT Realm

public class JwtRealm extends AuthorizingRealm {
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String jwt = (String) token.getPrincipal();
        String username = JwtUtil.getUsernameFromToken(jwt);
        if (username == null) {
            throw new AuthenticationException("Invalid token");
        }
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new AuthenticationException("User not found");
        }
        return new SimpleAuthenticationInfo(jwt, jwt, getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JwtUtil.getUsernameFromToken(principals.toString());
        User user = userService.findByUsername(username);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole(user.getRole());
        info.addStringPermission(user.getPermission());
        return info;
    }
}

4. JWT Utility Class

public class JwtUtil {
    private static final String SECRET_KEY = "YOUR_SECRET_KEY"; // replace with a strong secret

    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public static String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    public static boolean isTokenExpired(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getExpiration().before(new Date());
    }
}

5. User Service API

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody User user) {
        userService.save(user);
        return ResponseEntity.ok("User registered successfully");
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody User user) {
        User dbUser = userService.findByUsername(user.getUsername());
        if (dbUser != null && dbUser.getPassword().equals(user.getPassword())) {
            String token = JwtUtil.generateToken(user.getUsername());
            return ResponseEntity.ok(token);
        }
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
    }
}

6. Order Service API

@RestController
@RequestMapping("/order")
public class OrderController {
    @GetMapping("/{orderId}")
    public ResponseEntity<?> getOrder(@PathVariable Long orderId) {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isPermitted("order:read")) {
            return ResponseEntity.ok("Order details");
        }
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("No permission to view order");
    }

    @DeleteMapping("/{orderId}")
    public ResponseEntity<?> deleteOrder(@PathVariable Long orderId) {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.hasRole("admin")) {
            return ResponseEntity.ok("Order deleted");
        }
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("No permission to delete order");
    }
}

Conclusion

The example demonstrates how to combine Apache Shiro, JWT, and Spring Boot to build a stateless authentication and authorization mechanism suitable for lightweight distributed microservices. Shiro’s simple API and independent session management make it an attractive alternative when fine‑grained permission control and easy integration are required.

microservicesSpring BootAuthenticationJWTAuthorizationApache ShiroJava Security
Java Architecture Stack
Written by

Java Architecture Stack

Dedicated to original, practical tech insights—from skill advancement to architecture, front‑end to back‑end, the full‑stack path, with Wei Ge guiding you.

0 followers
Reader feedback

How this landed with the community

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.