shiro 整合 spring 实战及源码详解

打印 上一主题 下一主题

主题 923|帖子 923|积分 2769

序言

前面我们学习了如下内容:
5 分钟入门 shiro 安全框架实战笔记
shiro 整合 spring 实战及源码详解
相信大家对于 shiro 已经有了最基本的认识,这一节我们一起来学习写如何将 shiro 与 spring 进行整合。
spring 整合

maven 依赖
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.apache.shiro</groupId>
  4.         <artifactId>shiro-spring</artifactId>
  5.         <version>1.7.0</version>
  6.     </dependency>
  7.     <dependency>
  8.         <groupId>org.springframework</groupId>
  9.         <artifactId>spring-context</artifactId>
  10.         <version>4.3.13.RELEASE</version>
  11.     </dependency>
  12. </dependencies>
复制代码
服务类定义

定义一个简单的服务类,用于演示 @RequiresPermissions 注解的权限校验。
  1. package com.github.houbb.shiro.inaction02.springalone;
  2. import org.apache.shiro.authz.annotation.RequiresPermissions;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * Simple Service with methods protected with annotations.
  8. */
  9. @Component
  10. public class SimpleService {
  11.     private static Logger log = LoggerFactory.getLogger(SimpleService.class);
  12.     @RequiresPermissions("write")
  13.     public void writeRestrictedCall() {
  14.         log.info("executing method that requires the 'write' permission");
  15.     }
  16.     @RequiresPermissions("read")
  17.     public void readRestrictedCall() {
  18.         log.info("executing method that requires the 'read' permission");
  19.     }
  20. }
复制代码
快速开始

我们对原来的 Quick Start 进行改造如下:
  1. package com.github.houbb.shiro.inaction02.springalone;
  2. import org.apache.shiro.SecurityUtils;
  3. import org.apache.shiro.authc.UsernamePasswordToken;
  4. import org.apache.shiro.authz.AuthorizationException;
  5. import org.apache.shiro.mgt.SecurityManager;
  6. import org.apache.shiro.subject.Subject;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.stereotype.Component;
  11. import javax.annotation.PostConstruct;
  12. /**
  13. * Simple Bean used to demonstrate subject usage.
  14. */
  15. @Component
  16. public class QuickStart {
  17.     private static Logger log = LoggerFactory.getLogger(QuickStart.class);
  18.     @Autowired
  19.     private SecurityManager securityManager;
  20.     @Autowired
  21.     private SimpleService simpleService;
  22.     /**
  23.      * Sets the static instance of SecurityManager. This is NOT needed for web applications.
  24.      */
  25.     @PostConstruct
  26.     private void initStaticSecurityManager() {
  27.         SecurityUtils.setSecurityManager(securityManager);
  28.     }
  29.     public void run() {
  30.         // get the current subject
  31.         Subject subject = SecurityUtils.getSubject();
  32.         // Subject is not authenticated yet
  33.         System.out.println(!subject.isAuthenticated());
  34.         // login the subject with a username / password
  35.         UsernamePasswordToken token = new UsernamePasswordToken("joe.coder", "password");
  36.         subject.login(token);
  37.         // joe.coder has the "user" role
  38.         subject.checkRole("user");
  39.         // joe.coder does NOT have the admin role
  40.         System.out.println(!subject.hasRole("admin"));
  41.         // joe.coder has the "read" permission
  42.         subject.checkPermission("read");
  43.         // current user is allowed to execute this method.
  44.         simpleService.readRestrictedCall();
  45.         try {
  46.             // but not this one!
  47.             simpleService.writeRestrictedCall();
  48.         }
  49.         catch (AuthorizationException e) {
  50.             log.info("Subject was NOT allowed to execute method 'writeRestrictedCall'");
  51.         }
  52.         // logout
  53.         subject.logout();
  54.         System.out.println(!subject.isAuthenticated());
  55.     }
  56. }
复制代码
这里最核心的区别是 SecurityManager 是直接通过 @Autowired 注入得到的。
也没有看到我们以前初始化 SecurityManager 的 ini 文件,这些在下面的配置文件中。
配置类
  1. package com.github.houbb.shiro.inaction02.springalone;
  2. import org.apache.shiro.realm.Realm;
  3. import org.apache.shiro.realm.text.TextConfigurationRealm;
  4. import org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration;
  5. import org.apache.shiro.spring.config.ShiroBeanConfiguration;
  6. import org.apache.shiro.spring.config.ShiroConfiguration;
  7. import org.springframework.context.annotation.*;
  8. /**
  9. * Application bean definitions.
  10. */
  11. @Configuration
  12. @Import({ShiroBeanConfiguration.class,
  13.          ShiroConfiguration.class,
  14.          ShiroAnnotationProcessorConfiguration.class})
  15. @ComponentScan("com.github.houbb.shiro.inaction02.springalone")
  16. public class CliApp {
  17.     /**
  18.      * Example hard coded Realm bean.
  19.      * @return hard coded Realm bean
  20.      */
  21.     @Bean
  22.     public Realm realm() {
  23.         TextConfigurationRealm realm = new TextConfigurationRealm();
  24.         realm.setUserDefinitions("joe.coder=password,user\n" +
  25.                                  "jill.coder=password,admin");
  26.         realm.setRoleDefinitions("admin=read,write\n" +
  27.                                  "user=read");
  28.         realm.setCachingEnabled(true);
  29.         return realm;
  30.     }
  31. }
复制代码
这里通过 @Bean 的方式声明了用户角色等信息,可以简单理解为和 Ini 文件初始化是等价的。
@Import 导入了 3 个配置类,我们后面进行介绍。
启动

spring 应用的启动:
  1. public static void main(String[] args) {
  2.     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CliApp.class);
  3.     context.getBean(QuickStart.class).run();
  4. }
复制代码
测试日志如下:
  1. 十二月 31, 2020 10:33:02 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
  2. 信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6267c3bb: startup date [Thu Dec 31 10:33:02 CST 2020]; root of context hierarchy
  3. 十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
  4. 信息: Bean 'org.apache.shiro.spring.config.ShiroBeanConfiguration' of type [org.apache.shiro.spring.config.ShiroBeanConfiguration$$EnhancerBySpringCGLIB$$fbe016b3] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
  5. ...
  6. 信息: Bean 'org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration' of type [org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration$$EnhancerBySpringCGLIB$$f9d46e86] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
  7. 十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
  8. 信息: Bean 'eventBus' of type [org.apache.shiro.event.support.DefaultEventBus] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
  9. 十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
  10. 信息: Bean 'org.apache.shiro.spring.config.ShiroConfiguration' of type [org.apache.shiro.spring.config.ShiroConfiguration$$EnhancerBySpringCGLIB$$3db21503] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
  11. ...
  12. true
  13. true
  14. true
复制代码
ShiroBeanConfiguration 配置类

@Import 共计导入了 3 个配置类,我们接下来逐一分析下这 3 个配置类。
源码
  1. @Configuration
  2. public class ShiroBeanConfiguration extends AbstractShiroBeanConfiguration {
  3.     @Bean
  4.     @Override
  5.     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  6.         return super.lifecycleBeanPostProcessor();
  7.     }
  8.     @Bean
  9.     @Override
  10.     protected EventBus eventBus() {
  11.         return super.eventBus();
  12.     }
  13.     @Bean
  14.     @Override
  15.     public ShiroEventBusBeanPostProcessor shiroEventBusAwareBeanPostProcessor() {
  16.         return super.shiroEventBusAwareBeanPostProcessor();
  17.     }
  18. }
复制代码
这 3 个方法都是继承自父类,直接调用的父类方法。

  • AbstractShiroBeanConfiguration.java
  1. public class AbstractShiroBeanConfiguration {
  2.     protected LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  3.         return new LifecycleBeanPostProcessor();
  4.     }
  5.     protected EventBus eventBus() {
  6.         return new DefaultEventBus();
  7.     }
  8.     protected ShiroEventBusBeanPostProcessor shiroEventBusAwareBeanPostProcessor() {
  9.         return new ShiroEventBusBeanPostProcessor(eventBus());
  10.     }
  11. }
复制代码
实际上这里初始化了 3 个对象:LifecycleBeanPostProcessor/DefaultEventBus/ShiroEventBusBeanPostProcessor。
LifecycleBeanPostProcessor

这个类实际上比较简单,主要做了 2 件事情。
(1)执行 init() 和 destory()。
(2)指定对应的优先级,默认为最低。
核心部分如下:

  • init 初始化
  1. public Object postProcessBeforeInitialization(Object object, String name) throws BeansException {
  2.     if (object instanceof Initializable) {
  3.         try {
  4.             if (log.isDebugEnabled()) {
  5.                 log.debug("Initializing bean [" + name + "]...");
  6.             }
  7.             ((Initializable) object).init();
  8.         } catch (Exception e) {
  9.             throw new FatalBeanException("Error initializing bean [" + name + "]", e);
  10.         }
  11.     }
  12.     return object;
  13. }
复制代码

  • destory 销毁
  1. public void postProcessBeforeDestruction(Object object, String name) throws BeansException {
  2.     if (object instanceof Destroyable) {
  3.         try {
  4.             if (log.isDebugEnabled()) {
  5.                 log.debug("Destroying bean [" + name + "]...");
  6.             }
  7.             ((Destroyable) object).destroy();
  8.         } catch (Exception e) {
  9.             throw new FatalBeanException("Error destroying bean [" + name + "]", e);
  10.         }
  11.     }
  12. }
复制代码
DefaultEventBus

这个类如其名,就是默认的事件总线类。
接口的如下:
  1. public interface EventBus {
  2.     void publish(Object var1);
  3.     void register(Object var1);
  4.     void unregister(Object var1);
  5. }
复制代码
分别对应的是事件的发布,注册和取消注册。
实现部分实际就是调用对应的 EventListener 类,并且通过读写锁保证并发安全,暂时不做展开。
ShiroEventBusBeanPostProcessor

这个类实际上是配合 EventBus 使用的,核心实现如下:
  1. public class ShiroEventBusBeanPostProcessor implements BeanPostProcessor {
  2.     final private EventBus eventBus;
  3.     public ShiroEventBusBeanPostProcessor(EventBus eventBus) {
  4.         this.eventBus = eventBus;
  5.     }
  6.     @Override
  7.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  8.         //1. 如果实现了 EventBusAware 接口
  9.         if (bean instanceof EventBusAware) {
  10.             ((EventBusAware) bean).setEventBus(eventBus);
  11.         }
  12.         //2. 如果有 Subscribe 注解信息
  13.         else if (isEventSubscriber(bean)) {
  14.             eventBus.register(bean);
  15.         }
  16.         return bean;
  17.     }
  18. }
复制代码
这里会把实现了 EventBusAware 接口,和指定了 @Subscribe 注解的对象,注解对应的 eventbus。
ShiroConfiguration 配置

思考

我们 QuickStart 中自动注入了 SecurityManager 对象,这个对象是在哪里初始化的呢?
核心源码

核心部分如下:
  1. @Configuration
  2. @Import({ShiroBeanConfiguration.class})
  3. public class ShiroConfiguration extends AbstractShiroConfiguration {
  4.     @Bean
  5.     @Override
  6.     protected SessionsSecurityManager securityManager(List<Realm> realms) {
  7.         return super.securityManager(realms);
  8.     }
  9.     @Bean
  10.     @Override
  11.     protected SessionManager sessionManager() {
  12.         return super.sessionManager();
  13.     }
  14.     //... 省略其他组件
  15. }
复制代码
这里可以发现实际上已经导入了 ShiroBeanConfiguration 配置类,所以官方的 demo 可以简化如下:
  1. @Configuration
  2. @Import({ShiroConfiguration.class,
  3.          ShiroAnnotationProcessorConfiguration.class})
  4. @ComponentScan("com.github.houbb.shiro.inaction02.springalone")
  5. public class CliApp{}
复制代码
实际测试了一下,也是通过的。
SecurityManager 初始化

我简单的看了下 SecurityManager 实现子类还是比较多得。断点可以发现默认的类型是 DefaultSecurityManager。
这些都可以在 AbstractShiroConfiguration 类中找到答案。

  • AbstractShiroConfiguration.java
核心实现如下:
  1. public class AbstractShiroConfiguration {
  2.     @Autowired
  3.     protected EventBus eventBus;
  4.     protected SessionsSecurityManager securityManager(List<Realm> realms) {
  5.         SessionsSecurityManager securityManager = createSecurityManager();
  6.         securityManager.setEventBus(eventBus);
  7.         
  8.         // 省略其他属性设置
  9.         return securityManager;
  10.     }
  11.     protected SessionsSecurityManager createSecurityManager() {
  12.         DefaultSecurityManager securityManager = new DefaultSecurityManager();
  13.         securityManager.setSubjectDAO(subjectDAO());
  14.         securityManager.setSubjectFactory(subjectFactory());
  15.         RememberMeManager rememberMeManager = rememberMeManager();
  16.         if (rememberMeManager != null) {
  17.             securityManager.setRememberMeManager(rememberMeManager);
  18.         }
  19.         return securityManager;
  20.     }
  21. }
复制代码
securityManager(List realms) 方法会把我们 CliApp 中定义的 Realm 对象当作参数传入。
createSecurityManager() 方法就会初始化 DefaultSecurityManager 对象。
ShiroAnnotationProcessorConfiguration 配置

思考

我们在 SampleService 中使用了注解 @RequiresPermissions("write"),就可以校验对应的权限了。
但是这一切是如何被自动实现的呢?
源码
  1. @Configuration
  2. public class ShiroAnnotationProcessorConfiguration extends AbstractShiroAnnotationProcessorConfiguration{
  3.     @Bean
  4.     @DependsOn("lifecycleBeanPostProcessor")
  5.     protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  6.         return super.defaultAdvisorAutoProxyCreator();
  7.     }
  8.     @Bean
  9.     protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
  10.         return super.authorizationAttributeSourceAdvisor(securityManager);
  11.     }
  12. }
复制代码
本身没有什么源码,主要看下父类。
AbstractShiroAnnotationProcessorConfiguration
  1. public class AbstractShiroAnnotationProcessorConfiguration {
  2.     protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  3.         return new DefaultAdvisorAutoProxyCreator();
  4.     }
  5.     protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
  6.         AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  7.         advisor.setSecurityManager(securityManager);
  8.         return advisor;
  9.     }
  10. }
复制代码
DefaultAdvisorAutoProxyCreator 是 spring 中的自动代理实现类,此处不做展开。
我们重点看一下 AuthorizationAttributeSourceAdvisor 对象:
AuthorizationAttributeSourceAdvisor

这里主要做了两件事:
(1)设置对应的 securityManager
(2)处理有 RequiresPermissions 等 shiro 的内置注解的方法。
[code]@SuppressWarnings({"unchecked"})public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {    private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);    private static final Class
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表