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.
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 = $myRealmCustom 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.
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.
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.
