侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 192 篇文章
  • 累计创建 18 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Shiro:常见面试题和答案

拾荒的小海螺
2024-12-26 / 0 评论 / 0 点赞 / 6 阅读 / 19894 字

1. 什么是 Apache Shiro?它的核心组件有哪些?

答:Apache Shiro 是一个强大且灵活的 Java 安全框架,用于处理认证、授权、会话管理和加密。

核心组件:

  • Subject:当前操作的用户或系统进程。
  • SecurityManager:Shiro 的核心,用于协调内部组件。
  • Realm:用于连接应用程序的数据源(如数据库、LDAP),执行认证和授权逻辑。

1735179920674.jpg


2. Shiro 的认证和授权流程是什么?

认证流程:

  • 用户提交认证信息(如用户名和密码)。
  • SecurityManager 调用 Realm 验证用户身份。
  • 验证通过后,创建用户的 Subject。

授权流程:

  • SecurityManager 调用 Realm 查询用户角色和权限。
  • 根据配置的权限策略判断用户是否有权限访问资源。

3. 什么是 Realm?如何自定义 Realm?

答:Realm 是 Shiro 用于连接数据源(如数据库)的组件,主要负责认证和授权。

自定义 Realm 的步骤:

  • 继承 AuthorizingRealm 类。
  • 重写 doGetAuthenticationInfo 方法实现认证逻辑。
  • 重写 doGetAuthorizationInfo 方法实现授权逻辑。
public class CustomRealm extends AuthorizingRealm {
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        String password = "从数据库中查询密码";
        if (password == null) {
            throw new UnknownAccountException("用户不存在");
        }
        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;
    }
}

4. 如何在 Shiro 中实现 Remember Me 功能?

答:Remember Me 功能允许用户在关闭浏览器后仍保持登录状态。

实现步骤:

  • 配置 CookieRememberMeManager
  • 在登录时设置 RememberMe 属性:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
subject.login(token);

5. Shiro 中如何配置加密密码?

答:使用 HashedCredentialsMatcher 进行密码加密,数据库中的密码需使用相同的算法加密存储:

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256");
matcher.setHashIterations(1024);
customRealm.setCredentialsMatcher(matcher);

6. 如何使用注解实现权限控制?

开启注解支持:

@Configuration
public class ShiroConfig {
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

使用注解进行权限控制:

  • @RequiresRoles("admin")
  • @RequiresPermissions("user:read")
@RequiresRoles("admin")
@RequiresPermissions("user:read")
public void getUserDetails() {
    // 仅 admin 角色且拥有 user:read 权限的用户可调用
}

7. Shiro 如何实现会话管理?

答:Shiro 提供了默认的会话管理机制,支持分布式会话管理。

配置会话管理:

DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000); // 30 分钟

自定义会话 DAO:

public class CustomSessionDAO extends EnterpriseCacheSessionDAO {
    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = super.doCreate(session);
        // 自定义存储逻辑
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        // 自定义读取逻辑
        return super.doReadSession(sessionId);
    }
}

8. Shiro 中的过滤器有哪些?

答:Shiro 提供了多个内置过滤器:

  • anon:匿名访问。
  • authc:需要认证。
  • user:登录过即可访问。
  • perms:需要指定权限。
  • roles:需要指定角色。

配置示例:

shiroFilterFactoryBean.setFilterChainDefinitionMap(Map.of(
    "/login", "anon",
    "/admin/**", "roles[admin]",
    "/user/**", "perms[user:read]",
    "/**", "authc"
));

9. Shiro 的线程绑定是如何实现的?

答:Shiro 使用 ThreadContext 绑定 Subject,使得在同一线程内可以随时获取当前用户。

Subject currentUser = SecurityUtils.getSubject();
ThreadContext.bind(currentUser);

多线程场景下需手动绑定 Subject:

Subject subject = SecurityUtils.getSubject();
Runnable task = subject.associateWith(() -> {
    // 新线程内的操作
});
new Thread(task).start();

10. 如何在 Shiro 中实现分布式会话?

答:分布式会话需要将会话存储到共享介质(如 Redis)中。自定义会话 DAO,使用 Redis 存储会话,配置 DefaultWebSessionManager 使用自定义 DAO:

public class RedisSessionDAO extends AbstractSessionDAO {
    @Override
    protected Serializable doCreate(Session session) {
        // Redis 存储逻辑
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        // Redis 读取逻辑
    }
}

11. Shiro 如何动态加载权限?

答:动态加载权限指从数据库动态查询用户权限。

实现方式:

  • doGetAuthorizationInfo 方法中,从数据库查询权限:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = (String) principals.getPrimaryPrincipal();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roleService.getRolesByUsername(username));
    info.addStringPermissions(permissionService.getPermissionsByUsername(username));
    return info;
}
  • 配置缓存,减少频繁查询。

12. Shiro 和 Spring Security 的区别是什么?

特性 Shiro Spring Security
学习曲线 简单,轻量级 较复杂,功能全面
功能 专注于认证、授权、加密等核心功能 支持全面的安全功能(如 OAuth2、SSO 等)
扩展性 高,开发者需实现自定义组件 功能齐全,但扩展性稍差
集成性 无内置与 Spring 深度集成的功能 与 Spring 全面集成

13. Shiro 如何实现分布式会话共享?

答:分布式会话共享需要将会话信息存储在共享存储介质中,例如 Redis、数据库等。

实现步骤:

  • 自定义会话 DAO,使用 Redis 或其他分布式存储介质存储会话信息。
  • 配置 Shiro 使用自定义的 SessionDAO 和 DefaultWebSessionManager。
  • 确保多个节点访问同一个存储介质,从而实现会话共享。

14. Shiro 中如何实现动态权限?

答:动态权限指的是从数据库动态加载或更新用户权限,而非硬编码在配置文件中。

实现方式:

  • 在 doGetAuthorizationInfo 方法中,从数据库动态查询用户的角色和权限信息。
  • 配置缓存(如 Redis、Ehcache),以减少频繁查询数据库的开销。
  • 实现权限动态更新:
    监听权限表或角色表的变化。
    使用 Shiro 的缓存 API(clearCachedAuthorizationInfo)清除缓存。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = (String) principals.getPrimaryPrincipal();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

    // 从数据库加载角色和权限
    List<String> roles = roleService.getRolesByUsername(username);
    List<String> permissions = permissionService.getPermissionsByUsername(username);

    info.addRoles(roles);
    info.addStringPermissions(permissions);
    return info;
}


15. Shiro 如何处理多线程中的 Subject?

答:Shiro 的 Subject 是线程绑定的,但在多线程场景下需要显式地将 Subject 绑定到新线程。

解决方式:使用 Subject.associateWith

Subject currentSubject = SecurityUtils.getSubject();
Runnable task = currentSubject.associateWith(() -> {
    // 在新线程中使用 Subject
    System.out.println("当前用户:" + currentSubject.getPrincipal());
});
new Thread(task).start();


16. 如何在 Shiro 中集成 OAuth 认证?

答:Shiro 本身不直接支持 OAuth,可以通过自定义 Realm 或结合第三方库(如 spring-security-oauth)实现。

实现方式:

  • 自定义 OAuthRealm,实现 OAuth 认证逻辑,例如使用 OAuth 提供商(如 Google、GitHub)的 Access Token 验证用户身份。
  • 在 doGetAuthenticationInfo 方法中校验 Access Token 并加载用户信息。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    OAuthToken oAuthToken = (OAuthToken) token;
    String accessToken = oAuthToken.getToken();

    // 调用 OAuth 提供商的 API 验证 Token
    UserInfo userInfo = oAuthService.getUserInfo(accessToken);
    if (userInfo == null) {
        throw new AuthenticationException(\"Invalid OAuth Token\");
    }

    return new SimpleAuthenticationInfo(userInfo.getUsername(), accessToken, getName());
}


17. Shiro 如何实现无状态的 REST API 权限校验?

答:在无状态 REST API 场景下,通常不会使用会话管理,而是通过 Token 进行权限校验。

实现步骤:

  • 自定义过滤器,提取请求中的 Token:
public class StatelessAuthFilter extends BasicHttpAuthenticationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        String token = getToken(request);
        if (token != null && verifyToken(token)) {
            return true;
        }
        return false;
    }

    private String getToken(ServletRequest request) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        return httpRequest.getHeader("Authorization");
    }

    private boolean verifyToken(String token) {
        // 验证 Token 的逻辑
        return true;
    }
}
  • 配置过滤器到 Shiro 的 FilterChain。

18. Shiro 的并发会话限制如何实现?

答:可以通过会话管理器和自定义监听器实现。

实现步骤:

  • 自定义会话监听器,跟踪用户会话:
public class SessionLimitListener implements SessionListener {
    private final Map<String, Session> activeSessions = new ConcurrentHashMap<>();

    @Override
    public void onStart(Session session) {
        String username = (String) session.getAttribute("username");
        if (activeSessions.containsKey(username)) {
            activeSessions.get(username).stop();
        }
        activeSessions.put(username, session);
    }

    @Override
    public void onStop(Session session) {
        activeSessions.remove(session.getAttribute("username"));
    }

    @Override
    public void onExpiration(Session session) {
        onStop(session);
    }
}
  • 配置自定义监听器到 DefaultWebSessionManager

19. 如何解决 Shiro 在分布式系统中的会话共享问题?

答:通过 Redis 或其他共享存储实现会话共享。

实现步骤:

  • 使用 Redis 实现自定义会话 DAO:
public class RedisSessionDAO extends AbstractSessionDAO {
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        redisTemplate.opsForValue().set(sessionId.toString(), session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        return (Session) redisTemplate.opsForValue().get(sessionId.toString());
    }

    @Override
    public void update(Session session) {
        redisTemplate.opsForValue().set(session.getId().toString(), session);
    }

    @Override
    public void delete(Session session) {
        redisTemplate.delete(session.getId().toString());
    }
}
  • 配置 DefaultWebSessionManager 使用 RedisSessionDAO。

20. 如何动态修改用户权限?

答:动态修改权限可以通过缓存实现。

实现方式:

  • 在 Realm 的授权方法中启用缓存:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = (String) principals.getPrimaryPrincipal();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roleService.getRoles(username));
    info.addStringPermissions(permissionService.getPermissions(username));
    return info;
}
  • 动态修改权限时清除缓存:
public void clearAuthorizationCache(String username) {
    Subject subject = SecurityUtils.getSubject();
    if (subject.getPrincipal().equals(username)) {
        ((AuthorizingRealm) securityManager.getRealm()).clearCachedAuthorizationInfo(subject.getPrincipals());
    }
}

21. 如何防止 Shiro 的权限配置中 URL 被绕过?

答:配置默认的过滤器链规则,确保所有未匹配的 URL 都需要认证。

Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/api/**", "authc");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

22. 如何扩展 Shiro 的权限模型?

答:可以通过自定义 Permission 接口实现复杂的权限控制。

示例代码:

  • 自定义权限类:
public class CustomPermission implements Permission {
    private final String resource;
    private final String action;

    public CustomPermission(String permissionString) {
        String[] parts = permissionString.split(":");
        this.resource = parts[0];
        this.action = parts[1];
    }

    @Override
    public boolean implies(Permission p) {
        if (!(p instanceof CustomPermission)) {
            return false;
        }
        CustomPermission cp = (CustomPermission) p;
        return this.resource.equals(cp.resource) && this.action.equals(cp.action);
    }
}
  • 在 Realm 中使用自定义权限:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addObjectPermission(new CustomPermission("file:read"));
    return info;
}

23. 如何使用 Shiro 实现多租户架构?

答:多租户架构需要在认证和授权时区分不同的租户。

实现方式:

  • 扩展 Token,添加租户信息:
public class MultiTenantToken extends UsernamePasswordToken {
    private String tenantId;

    public MultiTenantToken(String username, char[] password, String tenantId) {
        super(username, password);
        this.tenantId = tenantId;
    }

    public String getTenantId() {
        return tenantId;
    }
}
  • 在 Realm 中处理租户信息:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    MultiTenantToken tenantToken = (MultiTenantToken) token;
    String tenantId = tenantToken.getTenantId();
    String username = tenantToken.getUsername();

    // 根据租户 ID 和用户名查询用户信息
    User user = userService.getUserByTenantAndUsername(tenantId, username);
    if (user == null) {
        throw new UnknownAccountException("用户不存在");
    }

    return new SimpleAuthenticationInfo(username, user.getPassword(), getName());
}

24. Shiro 中的权限缓存如何优化?

答:通过引入分布式缓存(如 Redis)优化权限缓存。

实现方式:

  • 使用 Redis 作为缓存实现:
@Bean
public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
    return new RedisCacheManager(redisTemplate);
}
  • 配置到 SecurityManager:
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setCacheManager(cacheManager);

25. Shiro常见的权限控制方式有哪几种?

  • 方法注解权限控制
  • 页面标签权限控制
  • 代码级别权限控制
  • URL级别权限控制

26. Shiro的优点有哪些?

  • 简单的身份认证,支持多种数据源
  • 非常简单的加密API
  • 对角色的简单的授权,支持细粒度的授权(方法级)
  • 支持一级缓存,以提升应用程序的性能
  • 内置的基于POJO企业会话管理,适用于Web以及非Web的环境
  • 不跟任何的框架或者容器捆绑,可以独立运行

25. Shiro 如何集成 Spring Boot 并实现自动化配置?

答:通过自定义配置类实现 Shiro 的自动化集成。

@Configuration
public class ShiroConfig {
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
          shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        return shiroFilterFactoryBean;
    }
  
    @Bean
    public Realm customRealm() {
        return new CustomRealm();
    }
}
0

评论区