Shiro权限框架深度解析

Shiro权限框架深度解析

Apache Shiro 是 Java 领域最经典的安全框架之一,本文档深入剖析 Shiro 的认证、授权、Session管理、加密机制,以及与Spring Boot的深度集成方案。

一、Shiro核心概念

1.1 三大核心组件

Apache Shiro
Subject 主体
SecurityManager 安全管理器
Realm 域
当前用户身份信息
登录/登出操作
权限检查
认证器 Authenticator
授权器 Authorizer
会话管理器 SessionManager
加密组件 CryptoSupport
JdbcRealm 数据库认证
IniRealm INI配置认证
TextConfigurationRealm
自定义Realm

1.2 Shiro架构图

Subject
+login(AuthenticationToken)
+logout()
+hasRole(String)
+isPermitted(String)
+getPrincipal()
SecurityManager
+authenticate(AuthenticationToken)
+authorize(PrincipalCollection)
+getSession()
Authenticator
+authenticate(AuthenticationToken)
Authorizer
+checkRole(PrincipalCollection, String)
+checkPermission(PrincipalCollection, String)
+hasRole(PrincipalCollection, String)
+isPermitted(PrincipalCollection, String)
SessionManager
+getSession()
+createSession()
Realm
+getAuthenticationInfo(AuthenticationToken)
+getAuthorizationInfo(PrincipalCollection)
SubjectAdapter
+SecurityManager securityManager

1.3 Shiro 与 Spring Security 对比

特性 Shiro Spring Security
学习曲线 平缓 陡峭
文档完整性 一般 完善
社区活跃度 一般 活跃
OAuth2支持 需要扩展 原生支持
Session管理 自带 需Spring Session
缓存支持 Shiro-Cache Spring Cache
过滤器链 简单 复杂但强大
适用场景 中小型项目 企业级项目

二、认证流程深度解析

2.1 认证流程图

Realm Authenticator SecurityManager Subject 客户端 Realm Authenticator SecurityManager Subject 客户端 alt [认证成功] [认证失败] login(username, password) authenticate(token) authenticate(token) getAuthenticationInfo(token) SimpleAuthenticationInfo AuthenticationInfo 认证成功,存储SubjectContext 创建Session 抛出AuthenticationException 异常穿透 抛出AuthenticationException 认证失败

2.2 认证核心接口

java 复制代码
// org.apache.shiro.authc.AuthenticationToken
public interface AuthenticationToken extends Serializable {
    
    /**
     * 获取主体(用户名)
     */
    PrincipalCollection getPrincipal();
    
    /**
     * 获取凭证(密码)
     */
    Object getCredentials();
}
java 复制代码
// org.apache.shiro.authc.AuthenticationInfo
public interface AuthenticationInfo extends Serializable {
    
    /**
     * 获取主身份集合
     */
    PrincipalCollection getPrincipals();
    
    /**
     * 获取凭证(已加密)
     */
    Object getCredentials();
    
    /**
     * 凭证是否匹配
     */
    default boolean isCredentialsMatch(AuthenticationToken token) {
        return getCredentials.equals(token.getCredentials());
    }
}

2.3 UsernamePasswordToken

java 复制代码
// org.apache.shiro.authc.UsernamePasswordToken
public class UsernamePasswordToken implements HostAuthenticationToken {
    
    private String username;
    private char[] password;
    private boolean rememberMe;
    private String host;
    
    public UsernamePasswordToken(String username, char[] password) {
        this(username, password, false, null);
    }
    
    public UsernamePasswordToken(String username, String password, 
                                 boolean rememberMe, String host) {
        this.username = username;
        this.password = password != null ? password.toCharArray() : null;
        this.rememberMe = rememberMe;
        this.host = host;
    }
    
    // getters and setters
}

2.4 认证流程源码解析

java 复制代码
// org.apache.shiro.mgt.ExecutingSecurityManager
public interface Authenticator {
    
    /**
     * 执行认证
     * @param token 认证令牌
     * @return 认证信息
     * @throws AuthenticationException 认证异常
     */
    AuthenticationInfo authenticate(AuthenticationToken token) 
        throws AuthenticationException;
    
    /**
     * 是否支持该Token类型
     */
    boolean supports(AuthenticationToken token);
}
java 复制代码
// org.apache.shiro.mgt.ModularRealmAuthenticator 核心实现
public class ModularRealmAuthenticator extends AbstractAuthenticator {
    
    private Collection<Realm> realms;
    private AuthenticationStrategy authenticationStrategy;
    
    @Override
    protected AuthenticationInfo doAuthenticate(
            AuthenticationToken token) throws AuthenticationException {
        
        // 1. 确保只有一个Realm被调用(单Realm场景)
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthenticate(token, realms.iterator().next());
        }
        
        // 2. 多Realm场景
        return doMultiRealmAuthenticate(token, realms);
    }
    
    private AuthenticationInfo doSingleRealmAuthenticate(
            AuthenticationToken token, Realm realm) {
        
        if (!realm.supports(token)) {
            throw new UnsupportedTokenException(
                "Realm [" + realm + "] does not support authentication token [" + 
                token + "]");
        }
        
        // 调用Realm获取认证信息
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        
        return info;
    }
    
    private AuthenticationInfo doMultiRealmAuthenticate(
            AuthenticationToken token, Collection<Realm> realms) {
        
        AuthenticationStrategy strategy = getAuthenticationStrategy();
        
        // 使用策略进行多Realm认证
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(
            realms, token);
        
        for (Realm realm : realms) {
            aggregate = strategy.beforeAttempt(realm, token, aggregate);
            
            if (realm.supports(token)) {
                AuthenticationInfo info = realm.getAuthenticationInfo(token);
                aggregate = strategy.afterAttempt(realm, token, info, aggregate);
            }
        }
        
        return strategy.afterAllAttempts(token, aggregate);
    }
}

三、授权流程深度解析

3.1 授权核心接口

java 复制代码
// org.apache.shiro.authz.Authorizer
public interface Authorizer {
    
    /**
     * 检查是否拥有指定角色
     */
    boolean hasRole(PrincipalCollection principals, String roleIdentifier);
    
    /**
     * 检查是否拥有所有指定角色
     */
    boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers);
    
    /**
     * 检查是否拥有指定权限
     */
    boolean isPermitted(PrincipalCollection principals, String permission);
    
    /**
     * 检查是否拥有所有指定权限
     */
    boolean isPermittedAll(PrincipalCollection principals, String... permissions);
    
    /**
     * 权限检查,若无权限则抛出异常
     */
    void checkPermission(PrincipalCollection principals, String permission)
        throws AuthorizationException;
    
    /**
     * 角色检查,若无角色则抛出异常
     */
    void checkRole(PrincipalCollection principals, String roleIdentifier)
        throws AuthorizationException;
}

3.2 授权流程图

权限缓存 Realm Authorizer SecurityManager Subject 权限缓存 Realm Authorizer SecurityManager Subject alt [缓存命中] [缓存未命中] hasRole/isPermitted 权限检查 获取AuthorizationInfo 直接返回权限 查询数据库 存入缓存 返回权限 true/false

3.3 ModularRealmAuthorizer 源码解析

java 复制代码
// org.apache.shiro.mgt.ModularRealmAuthorizer
public class ModularRealmAuthorizer implements Authorizer, 
        ConfigurableSecurityManager {
    
    private Collection<Realm> realms;
    
    @Override
    public boolean hasRole(PrincipalCollection principals, String role) {
        assertRealmsConfigured();
        
        for (Realm realm : realms) {
            if (!(realm instanceof Authorizer)) {
                continue;
            }
            
            Authorizer authorizer = (Authorizer) realm;
            
            if (authorizer.hasRole(principals, role)) {
                return true;
            }
        }
        
        return false;
    }
    
    @Override
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        assertRealmsConfigured();
        
        for (Realm realm : realms) {
            if (!(realm instanceof Authorizer)) {
                continue;
            }
            
            Authorizer authorizer = (Authorizer) realm;
            
            if (authorizer.isPermitted(principals, permission)) {
                return true;
            }
        }
        
        return false;
    }
}

四、Realm体系深度解析

4.1 Realm接口定义

java 复制代码
// org.apache.shiro.realm.Realm
public interface Realm {
    
    /**
     * Realm的唯一标识
     */
    String getName();
    
    /**
     * 是否支持该认证Token
     */
    boolean supports(AuthenticationToken token);
    
    /**
     * 获取认证信息(密码验证)
     */
    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
        throws AuthenticationException;
}
java 复制代码
// org.apache.shiro.realm.AuthorizingRealm
public abstract class AuthorizingRealm extends AuthenticatingRealm 
        implements Authorizer {
    
    /**
     * 获取授权信息(角色和权限)
     */
    protected abstract AuthorizationInfo doGetAuthorizationInfo(
        PrincipalCollection principals);
    
    /**
     * 密码匹配器
     */
    private CredentialsMatcher credentialsMatcher;
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        
        // 子类实现,从数据源获取认证信息
        return null;
    }
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        
        // 子类实现,从数据源获取授权信息
        return null;
    }
}

4.2 自定义Realm实现

java 复制代码
// 自定义用户Realm
public class CustomRealm extends AuthorizingRealm {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private RoleService roleService;
    
    @Autowired
    private PermissionService permissionService;
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        
        // 1. 获取登录用户名
        String username = principals.getPrimaryPrincipal().toString();
        
        // 2. 查询用户信息
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("User not found: " + username);
        }
        
        // 3. 查询角色和权限
        Set<String> roles = roleService.findRoleNamesByUserId(user.getId());
        Set<String> permissions = permissionService.findPermissionsByUserId(user.getId());
        
        // 4. 构建AuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        
        return info;
    }
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        
        // 1. 查询用户
        User user = userService.findByUsername(upToken.getUsername());
        if (user == null) {
            throw new UnknownAccountException("User not found");
        }
        
        // 2. 检查账户状态
        if (user.isLocked()) {
            throw new LockedAccountException("Account is locked");
        }
        if (!user.isEnabled()) {
            throw new DisabledAccountException("Account is disabled");
        }
        
        // 3. 返回认证信息(包含加密密码)
        return new SimpleAuthenticationInfo(
            user.getUsername(),
            user.getPassword(),
            ByteSource.Util.bytes(user.getSalt()),
            getName()
        );
    }
    
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }
}

4.3 JdbcRealm数据库认证

java 复制代码
// JdbcRealm配置示例
public class JdbcRealmConfig {
    
    @Bean
    public JdbcRealm jdbcRealm(DataSource dataSource) {
        JdbcRealm realm = new JdbcRealm();
        realm.setDataSource(dataSource);
        realm.setPermissionsLookupEnabled(true);
        
        // 认证查询
        realm.setAuthenticationQuery(
            "SELECT password, salt FROM sys_user WHERE username = ? AND enabled = 1");
        
        // 角色查询
        realm.setUserRolesQuery(
            "SELECT r.role_name FROM sys_role r " +
            "INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
            "INNER JOIN sys_user u ON u.id = ur.user_id " +
            "WHERE u.username = ?");
        
        // 权限查询
        realm.setPermissionsQuery(
            "SELECT p.permission FROM sys_permission p " +
            "INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
            "INNER JOIN sys_role r ON r.id = rp.role_id " +
            "INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
            "INNER JOIN sys_user u ON u.id = ur.user_id " +
            "WHERE u.username = ?");
        
        return realm;
    }
}

4.4 IniRealm配置认证

ini 复制代码
[users]
# 用户名=密码,角色1,角色2
admin=admin123,admin,user
user1=user123,user
guest=guest123,guest

[roles]
# 角色名=权限1,权限2
admin=system:*,user:*,role:*
user=user:view,user:edit
guest=guest:view

[urls]
# URL路径=角色
/admin/**=authc,admin
/user/**=authc,user
/guest/**=authc
/public/**=anon

五、权限标识WildcardPermission

5.1 权限格式

Shiro使用WildcardPermission支持通配符权限:

复制代码
resource:action:instance
示例 说明
user:view view权限,作用于所有user实例
user:view:1 view权限,作用于id=1的user
user:*:1 所有权限,作用于id=1的user
system:* system模块的所有权限
: 全局所有权限

5.2 权限匹配规则

java 复制代码
@Test
public void testWildcardPermission() {
    
    // 精确匹配
    Permission p1 = new WildcardPermission("user:view:1");
    assertTrue(p1.implies(new WildcardPermission("user:view:1")));
    
    // 通配符匹配
    Permission p2 = new WildcardPermission("user:view:*");
    assertTrue(p2.implies(new WildcardPermission("user:view:1")));
    assertTrue(p2.implies(new WildcardPermission("user:view:2")));
    
    // 角色权限验证
    Permission adminPermission = new WildcardPermission("system:user:*,role:*");
    
    // 验证:用户管理权限
    assertTrue(adminPermission.implies(
        new WildcardPermission("system:user:create")));
    assertTrue(adminPermission.implies(
        new WildcardPermission("system:user:delete:5")));
    
    // 验证:角色管理权限
    assertTrue(adminPermission.implies(
        new WildcardPermission("system:role:assign")));
    
    // 验证:不匹配的权限
    assertFalse(adminPermission.implies(
        new WildcardPermission("system:config:update")));
}

5.3 多级权限配置

java 复制代码
@Configuration
public class ShiroConfig {
    
    @Bean
    public Realm customRealm() {
        CustomRealm realm = new CustomRealm();
        
        // 设置权限匹配器
        realm.setPermissionResolver(new WildcardPermissionResolver());
        
        // 设置角色匹配器
        realm.setRolePermissionResolver(new ChainablePermissionResolver(
            new WildcardPermissionResolver(),
            new SimplePermissionResolver()));
        
        // 启用缓存
        realm.setCachingEnabled(true);
        realm.setAuthorizationCachingEnabled(true);
        
        return realm;
    }
}

六、Session管理

6.1 Session接口

java 复制代码
// org.apache.shiro.session.Session
public interface Session {
    
    /**
     * 获取会话ID
     */
    Serializable getId();
    
    /**
     * 获取会话开始时间
     */
    Date getStartTimestamp();
    
    /**
     * 获取最后访问时间
     */
    Date getLastAccessTime();
    
    /**
     * 超时时间(毫秒)
     */
    long getTimeout();
    
    /**
     * 设置超时时间
     */
    void setTimeout(long maxIdleTimeInMillis);
    
    /**
     * 停止会话
     */
    void stop();
    
    /**
     * 获取属性
     */
    Object getAttribute(Object key);
    
    /**
     * 设置属性
     */
    void setAttribute(Object key, Object value);
    
    /**
     * 删除属性
     */
    Object removeAttribute(Object key);
}

6.2 SessionManager体系

<<interface>>
SessionManager
+start()
+getSession()
<<abstract>>
ValidatingSessionManager
+start()
+getSession()
ServletContainerSessionManager
// 使用Servlet容器的Session
DefaultSessionManager
// 默认的Session管理
EnterpriseCacheSessionManager
// 企业缓存Session管理

6.3 Shiro与Spring Session集成

java 复制代码
@Configuration
public class ShiroSessionConfig {
    
    @Autowired
    private RedisConnectionFactory connectionFactory;
    
    @Bean
    public SessionDAO sessionDAO() {
        // RedisSessionDAO使用Redis存储Session
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        sessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
        
        // 设置过期时间
        sessionDAO.setExpireSeconds(1800);  // 30分钟
        
        return sessionDAO;
    }
    
    @Bean
    public RedisManager redisManager() {
        RedisManager manager = new RedisManager();
        manager.setHost("localhost:6379");
        manager.setTimeout(3000);
        manager.setPassword("redis-password");
        return manager;
    }
    
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(sessionDAO());
        
        // Session验证间隔
        sessionManager.setSessionValidationScheduler(
            new ExecutorServiceSessionValidationScheduler(sessionManager));
        sessionManager.setSessionValidationInterval(3600000);  // 1小时
        
        // 全局Session超时
        sessionManager.setGlobalSessionTimeout(1800000);  // 30分钟
        
        // 删除无效Session
        sessionManager.setDeleteInvalidSessions(true);
        
        return sessionManager;
    }
}

七、加密机制

7.1 Hash算法

java 复制代码
// 常用Hash加密
@Test
public void testHashAlgorithm() {
    
    String password = "admin123";
    String salt = "random-salt";
    
    // MD5加密(已不推荐)
    Md5Hash md5Hash = new Md5Hash(password, salt, 2);
    System.out.println("MD5: " + md5Hash.toHex());
    
    // SHA-256加密
    Sha256Hash sha256Hash = new Sha256Hash(password, salt, 1024);
    System.out.println("SHA-256: " + sha256Hash.toHex());
    
    // SHA-512加密
    Sha512Hash sha512Hash = new Sha512Hash(password, salt, 4096);
    System.out.println("SHA-512: " + sha512Hash.toHex());
    
    // 通用Hash请求
    Hash hash = new SimpleHash("SHA-256", password, salt, 1024);
    System.out.println("Generic: " + hash.toHex());
}

7.2 密码加密服务

java 复制代码
@Service
public class PasswordHashService {
    
    private final HashService hashService;
    
    @PostConstruct
    public void init() {
        // 配置Hash服务
        DefaultHashService hashService = new DefaultHashService();
        hashService.setHashAlgorithmName("SHA-256");
        hashService.setPrivateSalt(Bytes.random(16));  // 私盐
        hashService.setGeneratePublicSalt(true);  // 生成公盐
        hashService.setHashIterations(1024);
        
        this.hashService = hashService;
    }
    
    /**
     * 加密密码
     */
    public String hashPassword(String plaintext) {
        HashRequest request = new HashRequest.Builder()
            .setSource(plaintext)
            .build();
        
        return hashService.computeHash(request).toHex();
    }
    
    /**
     * 验证密码
     */
    public boolean verifyPassword(String plaintext, String hashed) {
        HashRequest request = new HashRequest.Builder()
            .setSource(plaintext)
            .setHash(hashed)
            .build();
        
        HashResponse response = hashService.computeHash(request);
        return response.isHashValid();
    }
}

7.3 CredentialsMatcher

java 复制代码
// 自定义凭证匹配器
public class CustomCredentialsMatcher extends HashMatcher {
    
    public CustomCredentialsMatcher() {
        super();
    }
    
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, 
                                     AuthenticationInfo info) {
        
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        SimpleAuthenticationInfo simpleInfo = (SimpleAuthenticationInfo) info;
        
        // 1. 获取提交的密码
        String submittedPassword = new String(upToken.getPassword());
        
        // 2. 获取存储的密码
        String storedPassword = simpleInfo.getCredentials().toString();
        
        // 3. 使用BCrypt验证
        return BCrypt.checkpw(submittedPassword, storedPassword);
    }
}

八、Spring Boot集成

8.1 依赖配置

xml 复制代码
<!-- pom.xml -->
<dependencies>
    <!-- Shiro核心 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.13.0</version>
    </dependency>
    
    <!-- Shiro Spring集成 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.13.0</version>
    </dependency>
    
    <!-- Shiro Web支持 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.13.0</version>
    </dependency>
    
    <!-- Shiro Cache -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-cache</artifactId>
        <version>1.13.0</version>
    </dependency>
    
    <!-- Shiro Session Redis -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>4.5.3</version>
    </dependency>
    
    <!-- ehcache缓存 -->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>
</dependencies>

8.2 ShiroConfig配置类

java 复制代码
@Configuration
public class ShiroConfig {
    
    @Bean
    public SecurityManager securityManager(
            Realm customRealm,
            SessionManager sessionManager,
            CacheManager cacheManager) {
        
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(customRealm);
        manager.setSessionManager(sessionManager);
        manager.setCacheManager(cacheManager);
        
        // 设置SubjectDAO
        SubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        manager.setSubjectDAO(subjectDAO);
        
        return manager;
    }
    
    @Bean
    public DefaultWebSessionStorageEvaluator sessionStorageEvaluator() {
        DefaultWebSessionStorageEvaluator evaluator = 
            new DefaultWebSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);  // 禁用Session存储
        return evaluator;
    }
    
    @Bean
    public Realm customRealm(CredentialsMatcher credentialsMatcher) {
        CustomRealm realm = new CustomRealm();
        realm.setName("customRealm");
        realm.setCachingEnabled(true);
        realm.setAuthorizationCachingEnabled(true);
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }
    
    @Bean
    public CredentialsMatcher credentialsMatcher() {
        // 使用Hash凭证匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256");
        matcher.setHashIterations(1024);
        matcher.setStoredCredentialsHexEncoded(false);
        return matcher;
    }
}

8.3 ShiroFilter配置

java 复制代码
@Configuration
public class ShiroFilterConfig {
    
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = 
            new DefaultShiroFilterChainDefinition();
        
        // 匿名访问路径
        definition.addPathDefinition("/public/**", "anon");
        definition.addPathDefinition("/login", "anon");
        definition.addPathDefinition("/api/auth/**", "anon");
        
        // 需要认证的路径
        definition.addPathDefinition("/user/**", "authc");
        definition.addPathDefinition("/admin/**", "authc,roles[admin]");
        
        // 登出
        definition.addPathDefinition("/logout", "logout");
        
        // 默认跳转
        definition.addPathDefinition("/**", "authc");
        
        return definition;
    }
}

8.4 Controller登录实现

java 复制代码
@RestController
@RequestMapping("/auth")
public class AuthController {
    
    @Autowired
    private SecurityManager securityManager;
    
    @PostMapping("/login")
    public Result login(@RequestBody LoginRequest request) {
        
        // 1. 创建Token
        UsernamePasswordToken token = new UsernamePasswordToken(
            request.getUsername(),
            request.getPassword().toCharArray()
        );
        token.setRememberMe(request.isRememberMe());
        token.setHost(request.getIp());
        
        // 2. 获取Subject
        Subject subject = SecurityUtils.getSubject();
        
        try {
            // 3. 执行登录
            subject.login(token);
            
            // 4. 返回结果
            return Result.success(Map.of(
                "username", subject.getPrincipal(),
                "sessionId", subject.getSession().getId()
            ));
            
        } catch (UnknownAccountException e) {
            return Result.fail("用户名不存在");
        } catch (IncorrectCredentialsException e) {
            return Result.fail("密码错误");
        } catch (LockedAccountException e) {
            return Result.fail("账户已被锁定");
        } catch (AuthenticationException e) {
            return Result.fail("认证失败: " + e.getMessage());
        }
    }
    
    @PostMapping("/logout")
    public Result logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return Result.success("已退出登录");
    }
}

8.5 注解式权限控制

java 复制代码
@Configuration
@EnableAspectJAutoProxy
public class AopAnnotationsConfig {
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            SecurityManager securityManager) {
        
        AuthorizationAttributeSourceAdvisor advisor = 
            new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

@Service
public class UserService {
    
    /**
     * 需要admin角色
     */
    @RequiresRoles("admin")
    public void deleteUser(Long userId) {
        userRepository.deleteById(userId);
    }
    
    /**
     * 需要user:view权限
     */
    @RequiresPermissions("user:view")
    public User getUser(Long userId) {
        return userRepository.findById(userId)
            .orElseThrow(() -> new UserNotFoundException(userId));
    }
    
    /**
     * 需要admin和user角色
     */
    @RequiresRoles(value = {"admin", "user"}, logical = Logical.AND)
    public void updateUser(Long userId, UserUpdateRequest request) {
        // 更新逻辑
    }
    
    /**
     * 需要通过权限检查
     */
    @RequiresAuthentication
    public UserProfile getProfile() {
        Subject subject = SecurityUtils.getSubject();
        String username = subject.getPrincipal().toString();
        return userService.findByUsername(username);
    }
}

8.6 全局异常处理

java 复制代码
@RestControllerAdvice
public class ShiroExceptionHandler {
    
    @ExceptionHandler(UnknownAccountException.class)
    public Result handleUnknownAccount(UnknownAccountException e) {
        return Result.fail("账户不存在");
    }
    
    @ExceptionHandler(IncorrectCredentialsException.class)
    public Result handleIncorrectCredentials(IncorrectCredentialsException e) {
        return Result.fail("密码错误");
    }
    
    @ExceptionHandler(LockedAccountException.class)
    public Result handleLockedAccount(LockedAccountException e) {
        return Result.fail("账户已被锁定");
    }
    
    @ExceptionHandler(UnauthorizedException.class)
    public Result handleUnauthorized(UnauthorizedException e) {
        return Result.fail("无权限访问");
    }
    
    @ExceptionHandler(AuthorizationException.class)
    public Result handleAuthorization(AuthorizationException e) {
        return Result.fail("权限不足");
    }
}

九、缓存配置

9.1 Ehcache配置

java 复制代码
@Configuration
public class ShiroCacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        // 使用Ehcache
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFilePath("classpath:ehcache.xml");
        return cacheManager;
    }
}
xml 复制代码
<!-- ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="false">
    
    <!-- 认证缓存 -->
    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"/>
    
    <!-- 授权缓存 -->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"/>
    
    <!-- Session缓存 -->
    <cache name="shiro-activeSessionCache"
           maxEntriesLocalHeap="10000"
           eternal="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="0"
           overflowToDisk="false"/>
</ehcache>

9.2 Redis缓存配置

java 复制代码
@Configuration
public class ShiroRedisCacheConfig {
    
    @Bean
    public CacheManager shiroRedisCacheManager(
            RedisTemplate<String, Object> redisTemplate) {
        
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisManager(redisManager());
        
        // 缓存过期时间
        cacheManager.setExpire(1800);  // 30分钟
        
        // 缓存键前缀
        cacheManager.setKeyPrefix("shiro:cache:");
        
        return cacheManager;
    }
    
    @Bean
    public RedisManager redisManager() {
        RedisManager manager = new RedisManager();
        manager.setHost("localhost:6379");
        manager.setTimeout(3000);
        return manager;
    }
}

十、过滤器链

10.1 常用过滤器

过滤器 说明 用法
anon 匿名访问 anon
authc 需要认证 authc
authcBasic HTTP Basic认证 authcBasic
user 认证或记住我 user
logout 登出 logout
perms 权限检查 perms[user:view]
roles 角色检查 roles[admin,user]
ssl 需要HTTPS ssl
port 端口检查 port[8080]

10.2 自定义过滤器

java 复制代码
// JWT认证过滤器
public class JwtAuthFilter extends AuthenticatingFilter {
    
    @Override
    protected AuthenticationToken createToken(
            HttpServletRequest request, 
            HttpServletResponse response) throws Exception {
        
        String token = getToken(request);
        
        if (token == null || token.isEmpty()) {
            return null;
        }
        
        // 解析JWT获取用户信息
        Claims claims = jwtService.parseToken(token);
        
        return new JwtToken(
            claims.getSubject(),
            token,
            claims.getIssuedAt().getTime()
        );
    }
    
    @Override
    protected boolean onAccessDenied(
            HttpServletRequest request, 
            HttpServletResponse response) throws Exception {
        
        String token = getToken(request);
        
        if (token == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        
        // 尝试登录
        return executeLogin(request, response);
    }
    
    private String getToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            return header.substring(7);
        }
        return null;
    }
}

10.3 过滤器配置

java 复制代码
@Configuration
public class ShiroFilterConfig {
    
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = 
            new DefaultShiroFilterChainDefinition();
        
        // API路径 - JWT认证
        definition.addPathDefinition("/api/**", "jwtAuth,noSessionCreation,authc");
        
        // 静态资源 - 匿名访问
        definition.addPathDefinition("/static/**", "anon");
        definition.addPathDefinition("/public/**", "anon");
        
        // 登录相关 - 匿名访问
        definition.addPathDefinition("/login", "anon");
        definition.addPathDefinition("/auth/login", "anon");
        
        // 登出
        definition.addPathDefinition("/logout", "logout");
        
        // 管理后台 - 角色控制
        definition.addPathDefinition("/admin/**", "authc,roles[admin]");
        
        // 其他 - 需认证
        definition.addPathDefinition("/**", "authc");
        
        return definition;
    }
    
    @Bean
    public JwtAuthFilter jwtAuthFilter() {
        return new JwtAuthFilter();
    }
}

十一、RememberMe功能

11.1 RememberMe配置

java 复制代码
@Configuration
public class ShiroConfig {
    
    @Bean
    public SecurityManager securityManager(..., RememberMeManager rememberMeManager) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // ...
        manager.setRememberMeManager(rememberMeManager);
        return manager;
    }
    
    @Bean
    public RememberMeManager rememberMeManager() {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setMaxAge(86400 * 7);  // 7天
        cookie.setHttpOnly(true);
        cookie.setSameSite("Strict");
        
        manager.setCookie(cookie);
        
        // 设置加密密钥
        manager.setCipherKey(Base64.decode(
            "shiroRememberMeCipherKey=="));
        
        return manager;
    }
}

11.2 使用RememberMe

java 复制代码
// 登录时启用RememberMe
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
    
    UsernamePasswordToken token = new UsernamePasswordToken(
        request.getUsername(),
        request.getPassword().toCharArray()
    );
    
    // 启用RememberMe
    token.setRememberMe(true);
    
    Subject subject = SecurityUtils.getSubject();
    subject.login(token);
    
    return Result.success();
}

// 检查是否RememberMe登录
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated() || subject.isRemembered()) {
    // 已登录或RememberMe登录
}

十二、多Realm认证

12.1 多Realm配置

java 复制代码
@Configuration
public class ShiroConfig {
    
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        
        // 配置多个Realm
        List<Realm> realms = new ArrayList<>();
        realms.add(jdbcRealm());
        realms.add(oauth2Realm());
        realms.add(ldapRealm());
        
        // 使用认证策略
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setRealms(realms);
        
        // 认证策略:第一个成功的优先
        authenticator.setAuthenticationStrategy(
            new FirstSuccessfulAuthenticationStrategy());
        
        // 或者:至少一个成功
        // authenticator.setAuthenticationStrategy(
        //     new AtLeastOneSuccessfulAuthenticationStrategy());
        
        // 或者:全部成功
        // authenticator.setAuthenticationStrategy(
        //     new AllSuccessfulAuthenticationStrategy());
        
        manager.setAuthenticator(authenticator);
        
        return manager;
    }
}

12.2 认证策略对比

策略 说明 使用场景
FirstSuccessfulAuthenticationStrategy 第一个成功即返回 单一数据源
AtLeastOneSuccessfulAuthenticationStrategy 至少一个成功 多数据源,任一可认证
AllSuccessfulAuthenticationStrategy 全部成功 高安全要求

十三、总结

13.1 核心流程

anon
authc
用户名密码
OAuth
JWT
成功
失败
命中
未命中
请求到达
路径匹配
匿名访问
创建Subject
认证Token
JdbcRealm / 自定义Realm
OAuth2Realm
JwtRealm
密码匹配
获取AuthorizationInfo
抛出异常
权限缓存
返回缓存
查询数据库
权限验证
返回错误
通过/拒绝

13.2 配置检查清单

检查项 说明
Realm配置 确保实现正确的Realm
CredentialsMatcher 密码加密算法配置
SessionManager 分布式Session配置
CacheManager 权限缓存配置
FilterChain 路径过滤规则
RememberMe Cookie安全配置

13.3 常见问题

问题 解决方案
认证失败无响应 检查Realm的supports()方法
权限缓存无效 检查CacheManager配置
Session丢失 检查SessionManager配置
RememberMe不生效 检查Cookie的HttpOnly配置

Shiro 作为轻量级安全框架,以其简洁的API和灵活的配置,成为中小型项目安全认证的首选。掌握其核心组件和配置方法,能够快速构建可靠的安全体系。

相关推荐
1.14(java)4 小时前
Spring AOP核心概念与实战指南
java·后端·spring
亚历克斯神4 小时前
Java 安全最佳实践:构建安全的 Java 应用
java·spring·微服务
jieyucx5 小时前
# Go 语言指针零基础入门详解
开发语言·后端·golang
橙子圆1235 小时前
java之拦截器和适配器模式
java·开发语言
时空系5 小时前
第3篇:数据的运算——让数据动起来 Rust中文编程
开发语言·后端·rust
Shadow(⊙o⊙)5 小时前
智能指针、循环引用、锁、删除器
开发语言·c++·后端·visual studio
星浩AI5 小时前
OpenAI 大神 Karpathy 开源:用 Obsidian 实现 LLM Wiki 知识库管理方法
后端·openai·agent
lifewange5 小时前
Claude Code可以安装在IDEA和Pycharm中么
java·pycharm·intellij-idea
lifewange5 小时前
OpenCode可以安装在IDEA和Pycharm中么
java·pycharm·intellij-idea