Master Shiro Security: From Basics to CAS SSO Integration with SpringBoot
This comprehensive guide walks you through Shiro's core concepts, authentication and authorization mechanisms, various realm implementations, session management, distributed session handling with Redis, caching strategies, and step‑by‑step integration of CAS single sign‑on using SpringBoot and pac4j, complete with code samples.
1. Existing Problems
Authentication (login) requires repetitive business‑code implementation; coarse‑grained authorization is easy but fine‑grained control is cumbersome; distributed session management needs manual Redis integration; single sign‑on (SSO) is not covered.
2. Shiro Framework Introduction
Shiro is a Java security library whose core features are authentication and authorization. Official site: http://shiro.apache.org.
3. Basic Usage
3.1 SimpleAccountRealm
Authentication flow:
Authorization flow:
@Test
public void authen() {
// 1. Prepare Realm with in‑memory user
SimpleAccountRealm realm = new SimpleAccountRealm();
realm.addAccount("admin","admin","超级管理员","商家");
// 2. Build SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
// 3. Bind SecurityManager
SecurityUtils.setSecurityManager(securityManager);
// 4. Obtain Subject
Subject subject = SecurityUtils.getSubject();
// 5. Perform authentication
subject.login(new UsernamePasswordToken("admin","admin"));
System.out.println(subject.isAuthenticated());
// 6. Role and permission checks
System.out.println("是否拥有超级管理员角色:" + subject.hasRole("超级管理员"));
subject.checkRole("商家");
System.out.println(subject.isPermitted("user:add"));
subject.checkPermission("user:delete");
}3.2 IniRealm
Configuration stored in an .ini file:
[users]
admin=admin,超级管理员,运营
[roles]
超级管理员=user:add,user:update,user:delete @Test
public void authen() {
IniRealm realm = new IniRealm("classpath:shiro.ini");
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken("admin","admin"));
System.out.println(subject.hasRole("超级管理员"));
subject.checkRole("运营");
System.out.println(subject.isPermitted("user:update"));
subject.checkPermission("user:delete");
}3.3 JdbcRealm
Database tables for users, roles, permissions (five classic tables) and example SQL for table creation and data insertion.
@Test
public void authen() {
JdbcRealm realm = new JdbcRealm();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///shiro");
dataSource.setUsername("root");
dataSource.setPassword("root");
realm.setDataSource(dataSource);
realm.setPermissionsLookupEnabled(true);
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken("admin","admin"));
System.out.println(subject.hasRole("超级管理员"));
System.out.println(subject.isPermitted("user:add"));
}4. Shiro Web Integration
4.1 SSM (Spring MVC) Approach
Configure Shiro filter, realm, and security manager in web.xml and Spring XML, define filter chain for login, anonymous resources, and authenticated paths.
4.2 SpringBoot Approach
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm realm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(realm);
return manager;
}
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
Map<String,String> map = new LinkedHashMap<>();
map.put("/login.html","anon");
map.put("/user/**","anon");
map.put("/**","authc");
definition.addPathDefinitions(map);
return definition;
}
}5. Authorization Methods
5.1 Filter Chain
filterChainDefinitionMap.put("/item/select","roles[超级管理员,运营]");
filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");5.2 Custom Filter (RolesOrAuthorizationFilter)
public class RolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for (String role : rolesArray) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}5.3 Annotations
Use @RequiresRoles and @RequiresPermissions on controller methods; logical OR can be specified.
@GetMapping("/update")
@RequiresRoles({"超级管理员","运营"})
public String update() {
return "item Update!!!";
}6. Distributed Session Handling
6.1 Default Session Management
Shiro can use HttpSession or its own MemorySessionDAO.
6.2 Redis Session DAO
Implement RedisSessionDAO extending AbstractSessionDAO, store sessions in Redis with a "session:" prefix, and set expiration to 30 minutes.
@Component
public class RedisSessionDAO extends AbstractSessionDAO {
@Resource
private RedisTemplate redisTemplate;
private final String SHIRO_SESSION = "session:";
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(SHIRO_SESSION + sessionId, session, 30, TimeUnit.MINUTES);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) return null;
Session session = (Session) redisTemplate.opsForValue().get(SHIRO_SESSION + sessionId);
if (session != null) {
redisTemplate.expire(SHIRO_SESSION + sessionId, 30, TimeUnit.MINUTES);
}
return session;
}
@Override
public void update(Session session) throws UnknownSessionException {
if (session == null) return;
redisTemplate.opsForValue().set(SHIRO_SESSION + session.getId(), session, 30, TimeUnit.MINUTES);
}
@Override
public void delete(Session session) {
if (session == null) return;
redisTemplate.delete(SHIRO_SESSION + session.getId());
}
@Override
public Collection<Session> getActiveSessions() {
Set keys = redisTemplate.keys(SHIRO_SESSION + "*");
Set<Session> sessionSet = new HashSet<>();
for (Object key : keys) {
Session s = (Session) redisTemplate.opsForValue().get(key);
sessionSet.add(s);
}
return sessionSet;
}
}6.3 Optimized Session Retrieval
Extend DefaultWebSessionManager to first check the request attribute before hitting Redis, reducing I/O latency.
public class DefaultRedisWebSessionManager extends DefaultWebSessionManager {
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
if (sessionKey instanceof WebSessionKey) {
ServletRequest request = ((WebSessionKey) sessionKey).getServletRequest();
Session session = (Session) request.getAttribute(sessionId + "");
if (session != null) {
return session;
}
}
Session session = retrieveSessionFromDataSource(sessionId);
if (session == null) {
throw new UnknownSessionException("Could not find session with ID [" + sessionId + "]");
}
if (sessionKey instanceof WebSessionKey) {
((WebSessionKey) sessionKey).getServletRequest().setAttribute(sessionId + "", session);
}
return session;
}
}7. Authorization Cache
Implement RedisCache implementing org.apache.shiro.cache.Cache and RedisCacheManager to cache role and permission data for 15 minutes, reducing database load.
@Component
public class RedisCache<K,V> implements Cache<K,V> {
@Autowired
private RedisTemplate redisTemplate;
private final String CACHE_PREFIX = "cache:";
@Override
public V get(K k) {
V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
if (v != null) {
redisTemplate.expire(CACHE_PREFIX + k, 15, TimeUnit.MINUTES);
}
return v;
}
@Override
public V put(K k, V v) {
redisTemplate.opsForValue().set(CACHE_PREFIX + k, v, 15, TimeUnit.MINUTES);
return v;
}
@Override
public V remove(K k) {
V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
if (v != null) {
redisTemplate.delete(CACHE_PREFIX + k);
}
return v;
}
@Override
public void clear() {
Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
redisTemplate.delete(keys);
}
// size, keys, values omitted for brevity
} @Component
public class RedisCacheManager implements CacheManager {
@Autowired
private RedisCache redisCache;
@Override
public <K,V> Cache<K,V> getCache(String name) {
return redisCache;
}
}8. CAS Single Sign‑On Integration
8.1 Overview
CAS provides centralized authentication (SSO). Two deployment models: centralized (CAS server) and decentralized (JWT). This guide uses the centralized model.
8.2 CAS Server Setup
Download CAS 4.1.10, configure HTTP support, modify Apereo‑10000002.json, HTTPSandIMAPS‑10000001.json, ticketGrantingTicketCookieGenerator.xml, warnCookieGenerator.xml, and deployerConfigContext.xml. Build with Maven war:war and deploy to Tomcat.
8.3 Shiro + pac4j + CAS
Use buji‑pac4j and pac4j‑cas libraries. Define a CasRealm extending Pac4jRealm, configure Pac4j Config with CasClient, set callback URL, and register Shiro filters: security, callback, and logout.
@Configuration
public class Pac4jConfig {
@Value("${cas.server.url:http://localhost:8080/cas}")
private String casServerUrl;
@Value("${cas.project.url:http://localhost:81}")
private String casProjectUrl;
@Value("${cas.clientName:test}")
private String clientName;
@Bean
public Config config(CasClient casClient) {
return new Config(casClient);
}
@Bean
public CasClient casClient(CasConfiguration casConfiguration) {
CasClient client = new CasClient(casConfiguration);
client.setCallbackUrl(casProjectUrl + "/callback?client_name=" + clientName);
client.setName(clientName);
return client;
}
@Bean
public CasConfiguration casConfiguration() {
CasConfiguration cfg = new CasConfiguration();
cfg.setLoginUrl(casServerUrl + "/login");
cfg.setProtocol(CasProtocol.CAS20);
cfg.setPrefixUrl(casServerUrl + "/");
cfg.setAcceptAnyProxy(true);
return cfg;
}
}Shiro filter registration includes SecurityFilter, CallbackFilter, and LogoutFilter with appropriate URLs.
The guide provides end‑to‑end code, configuration, and diagrams to enable secure authentication, fine‑grained authorization, distributed session management, caching, and CAS SSO integration using Shiro.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer Null's Self-Cultivation
Focused on backend development technologies and sharing knowledge from programming project experiences.
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.
