序言

后面咱们学习了如下内容:

5 分钟入门 shiro 平安框架实战笔记

shiro 整合 spring 实战及源码详解

置信大家对于 shiro 曾经有了最根本的意识,这一节咱们一起来学习写如何将 shiro 与 spring 进行整合。

spring 整合

maven 依赖

<dependencies>    <dependency>        <groupId>org.apache.shiro</groupId>        <artifactId>shiro-spring</artifactId>        <version>1.7.0</version>    </dependency>    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>4.3.13.RELEASE</version>    </dependency></dependencies>

服务类定义

定义一个简略的服务类,用于演示 @RequiresPermissions 注解的权限校验。

package com.github.houbb.shiro.inaction02.springalone;import org.apache.shiro.authz.annotation.RequiresPermissions;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * Simple Service with methods protected with annotations. */@Componentpublic class SimpleService {    private static Logger log = LoggerFactory.getLogger(SimpleService.class);    @RequiresPermissions("write")    public void writeRestrictedCall() {        log.info("executing method that requires the 'write' permission");    }    @RequiresPermissions("read")    public void readRestrictedCall() {        log.info("executing method that requires the 'read' permission");    }}

疾速开始

咱们对原来的 Quick Start 进行革新如下:

package com.github.houbb.shiro.inaction02.springalone;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authz.AuthorizationException;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;/** * Simple Bean used to demonstrate subject usage. */@Componentpublic class QuickStart {    private static Logger log = LoggerFactory.getLogger(QuickStart.class);    @Autowired    private SecurityManager securityManager;    @Autowired    private SimpleService simpleService;    /**     * Sets the static instance of SecurityManager. This is NOT needed for web applications.     */    @PostConstruct    private void initStaticSecurityManager() {        SecurityUtils.setSecurityManager(securityManager);    }    public void run() {        // get the current subject        Subject subject = SecurityUtils.getSubject();        // Subject is not authenticated yet        System.out.println(!subject.isAuthenticated());        // login the subject with a username / password        UsernamePasswordToken token = new UsernamePasswordToken("joe.coder", "password");        subject.login(token);        // joe.coder has the "user" role        subject.checkRole("user");        // joe.coder does NOT have the admin role        System.out.println(!subject.hasRole("admin"));        // joe.coder has the "read" permission        subject.checkPermission("read");        // current user is allowed to execute this method.        simpleService.readRestrictedCall();        try {            // but not this one!            simpleService.writeRestrictedCall();        }        catch (AuthorizationException e) {            log.info("Subject was NOT allowed to execute method 'writeRestrictedCall'");        }        // logout        subject.logout();        System.out.println(!subject.isAuthenticated());    }}

这里最外围的区别是 SecurityManager 是间接通过 @Autowired 注入失去的。

也没有看到咱们以前初始化 SecurityManager 的 ini 文件,这些在上面的配置文件中。

配置类

package com.github.houbb.shiro.inaction02.springalone;import org.apache.shiro.realm.Realm;import org.apache.shiro.realm.text.TextConfigurationRealm;import org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration;import org.apache.shiro.spring.config.ShiroBeanConfiguration;import org.apache.shiro.spring.config.ShiroConfiguration;import org.springframework.context.annotation.*;/** * Application bean definitions. */@Configuration@Import({ShiroBeanConfiguration.class,         ShiroConfiguration.class,         ShiroAnnotationProcessorConfiguration.class})@ComponentScan("com.github.houbb.shiro.inaction02.springalone")public class CliApp {    /**     * Example hard coded Realm bean.     * @return hard coded Realm bean     */    @Bean    public Realm realm() {        TextConfigurationRealm realm = new TextConfigurationRealm();        realm.setUserDefinitions("joe.coder=password,user\n" +                                 "jill.coder=password,admin");        realm.setRoleDefinitions("admin=read,write\n" +                                 "user=read");        realm.setCachingEnabled(true);        return realm;    }}

这里通过 @Bean 的形式申明了用户角色等信息,能够简略了解为和 Ini 文件初始化是等价的。

@Import 导入了 3 个配置类,咱们前面进行介绍。

启动

spring 利用的启动:

public static void main(String[] args) {    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CliApp.class);    context.getBean(QuickStart.class).run();}

测试日志如下:

十二月 31, 2020 10:33:02 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6267c3bb: startup date [Thu Dec 31 10:33:02 CST 2020]; root of context hierarchy十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization信息: 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)...信息: 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)十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization信息: 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)十二月 31, 2020 10:33:03 上午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization信息: 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)...truetruetrue

ShiroBeanConfiguration 配置类

@Import 共计导入了 3 个配置类,咱们接下来逐个剖析下这 3 个配置类。

源码

@Configurationpublic class ShiroBeanConfiguration extends AbstractShiroBeanConfiguration {    @Bean    @Override    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {        return super.lifecycleBeanPostProcessor();    }    @Bean    @Override    protected EventBus eventBus() {        return super.eventBus();    }    @Bean    @Override    public ShiroEventBusBeanPostProcessor shiroEventBusAwareBeanPostProcessor() {        return super.shiroEventBusAwareBeanPostProcessor();    }}

这 3 个办法都是继承自父类,间接调用的父类办法。

  • AbstractShiroBeanConfiguration.java
public class AbstractShiroBeanConfiguration {    protected LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {        return new LifecycleBeanPostProcessor();    }    protected EventBus eventBus() {        return new DefaultEventBus();    }    protected ShiroEventBusBeanPostProcessor shiroEventBusAwareBeanPostProcessor() {        return new ShiroEventBusBeanPostProcessor(eventBus());    }}

实际上这里初始化了 3 个对象:LifecycleBeanPostProcessor/DefaultEventBus/ShiroEventBusBeanPostProcessor。

LifecycleBeanPostProcessor

这个类实际上比较简单,次要做了 2 件事件。

(1)执行 init() 和 destory()。

(2)指定对应的优先级,默认为最低。

外围局部如下:

  • init 初始化
public Object postProcessBeforeInitialization(Object object, String name) throws BeansException {    if (object instanceof Initializable) {        try {            if (log.isDebugEnabled()) {                log.debug("Initializing bean [" + name + "]...");            }            ((Initializable) object).init();        } catch (Exception e) {            throw new FatalBeanException("Error initializing bean [" + name + "]", e);        }    }    return object;}
  • destory 销毁
public void postProcessBeforeDestruction(Object object, String name) throws BeansException {    if (object instanceof Destroyable) {        try {            if (log.isDebugEnabled()) {                log.debug("Destroying bean [" + name + "]...");            }            ((Destroyable) object).destroy();        } catch (Exception e) {            throw new FatalBeanException("Error destroying bean [" + name + "]", e);        }    }}

DefaultEventBus

这个类如其名,就是默认的事件总线类。

接口的如下:

public interface EventBus {    void publish(Object var1);    void register(Object var1);    void unregister(Object var1);}

别离对应的是事件的公布,注册和勾销注册。

实现局部理论就是调用对应的 EventListener 类,并且通过读写锁保障并发平安,临时不做开展。

ShiroEventBusBeanPostProcessor

这个类实际上是配合 EventBus 应用的,外围实现如下:

public class ShiroEventBusBeanPostProcessor implements BeanPostProcessor {    final private EventBus eventBus;    public ShiroEventBusBeanPostProcessor(EventBus eventBus) {        this.eventBus = eventBus;    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        //1. 如果实现了 EventBusAware 接口        if (bean instanceof EventBusAware) {            ((EventBusAware) bean).setEventBus(eventBus);        }        //2. 如果有 Subscribe 注解信息        else if (isEventSubscriber(bean)) {            eventBus.register(bean);        }        return bean;    }}

这里会把实现了 EventBusAware 接口,和指定了 @Subscribe 注解的对象,注解对应的 eventbus。

ShiroConfiguration 配置

思考

咱们 QuickStart 中主动注入了 SecurityManager 对象,这个对象是在哪里初始化的呢?

外围源码

外围局部如下:

@Configuration@Import({ShiroBeanConfiguration.class})public class ShiroConfiguration extends AbstractShiroConfiguration {    @Bean    @Override    protected SessionsSecurityManager securityManager(List<Realm> realms) {        return super.securityManager(realms);    }    @Bean    @Override    protected SessionManager sessionManager() {        return super.sessionManager();    }    //... 省略其余组件}

这里能够发现实际上曾经导入了 ShiroBeanConfiguration 配置类,所以官网的 demo 能够简化如下:

@Configuration@Import({ShiroConfiguration.class,         ShiroAnnotationProcessorConfiguration.class})@ComponentScan("com.github.houbb.shiro.inaction02.springalone")public class CliApp{}

理论测试了一下,也是通过的。

SecurityManager 初始化

我简略的看了下 SecurityManager 实现子类还是比拟多得。断点能够发现默认的类型是 DefaultSecurityManager

这些都能够在 AbstractShiroConfiguration 类中找到答案。

  • AbstractShiroConfiguration.java

外围实现如下:

public class AbstractShiroConfiguration {    @Autowired    protected EventBus eventBus;    protected SessionsSecurityManager securityManager(List<Realm> realms) {        SessionsSecurityManager securityManager = createSecurityManager();        securityManager.setEventBus(eventBus);                // 省略其余属性设置        return securityManager;    }    protected SessionsSecurityManager createSecurityManager() {        DefaultSecurityManager securityManager = new DefaultSecurityManager();        securityManager.setSubjectDAO(subjectDAO());        securityManager.setSubjectFactory(subjectFactory());        RememberMeManager rememberMeManager = rememberMeManager();        if (rememberMeManager != null) {            securityManager.setRememberMeManager(rememberMeManager);        }        return securityManager;    }}

securityManager(List<Realm> realms) 办法会把咱们 CliApp 中定义的 Realm 对象当作参数传入。

createSecurityManager() 办法就会初始化 DefaultSecurityManager 对象。

ShiroAnnotationProcessorConfiguration 配置

思考

咱们在 SampleService 中应用了注解 @RequiresPermissions("write"),就能够校验对应的权限了。

然而这所有是如何被主动实现的呢?

源码

@Configurationpublic class ShiroAnnotationProcessorConfiguration extends AbstractShiroAnnotationProcessorConfiguration{    @Bean    @DependsOn("lifecycleBeanPostProcessor")    protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {        return super.defaultAdvisorAutoProxyCreator();    }    @Bean    protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {        return super.authorizationAttributeSourceAdvisor(securityManager);    }}

自身没有什么源码,次要看下父类。

AbstractShiroAnnotationProcessorConfiguration

public class AbstractShiroAnnotationProcessorConfiguration {    protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {        return new DefaultAdvisorAutoProxyCreator();    }    protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();        advisor.setSecurityManager(securityManager);        return advisor;    }}

DefaultAdvisorAutoProxyCreator 是 spring 中的主动代理实现类,此处不做开展。

咱们重点看一下 AuthorizationAttributeSourceAdvisor 对象:

AuthorizationAttributeSourceAdvisor

这里次要做了两件事:

(1)设置对应的 securityManager

(2)解决有 RequiresPermissions 等 shiro 的内置注解的办法。

@SuppressWarnings({"unchecked"})public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {    private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =            new Class[] {                    RequiresPermissions.class, RequiresRoles.class,                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class            };    protected SecurityManager securityManager = null;    /**     * Create a new AuthorizationAttributeSourceAdvisor.     */    public AuthorizationAttributeSourceAdvisor() {        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());    }    public SecurityManager getSecurityManager() {        return securityManager;    }    public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {        this.securityManager = securityManager;    }}

AopAllianceAnnotationsAuthorizingMethodInterceptor

这个名字起的,好家伙,真长。

这里就是对于注解的响应办法 aop 拦截器实现。

package org.apache.shiro.spring.security.interceptor;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.apache.shiro.aop.AnnotationResolver;import org.apache.shiro.authz.aop.*;import org.apache.shiro.spring.aop.SpringAnnotationResolver;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;/** * Allows Shiro Annotations to work in any <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> * specific implementation environment (for example, Spring). * * @since 0.2 */public class AopAllianceAnnotationsAuthorizingMethodInterceptor        extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {    public AopAllianceAnnotationsAuthorizingMethodInterceptor() {        List<AuthorizingAnnotationMethodInterceptor> interceptors =                new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);        //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the        //raw JDK resolution process.        AnnotationResolver resolver = new SpringAnnotationResolver();        //we can re-use the same resolver instance - it does not retain state:        interceptors.add(new RoleAnnotationMethodInterceptor(resolver));        interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));        interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));        interceptors.add(new UserAnnotationMethodInterceptor(resolver));        interceptors.add(new GuestAnnotationMethodInterceptor(resolver));        setMethodInterceptors(interceptors);    }        //省略 invoke 局部}

到这里实际上就比较简单了,置信聪慧如你肯定曾经晓得整个 spring-shiro 面纱背地的机密了。

咱们间接看一下 PermissionAnnotationMethodInterceptor 的实现。

PermissionAnnotationMethodInterceptor

import org.apache.shiro.aop.AnnotationResolver;public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {    public PermissionAnnotationMethodInterceptor() {        super( new PermissionAnnotationHandler() );    }    /**     * @param resolver     * @since 1.1     */    public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {        super( new PermissionAnnotationHandler(), resolver);    }}

解决类实现如下:

package org.apache.shiro.authz.aop;import org.apache.shiro.authz.AuthorizationException;import org.apache.shiro.authz.annotation.Logical;import org.apache.shiro.authz.annotation.RequiresPermissions;import org.apache.shiro.authz.annotation.RequiresRoles;import org.apache.shiro.subject.Subject;import java.lang.annotation.Annotation;public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {    public PermissionAnnotationHandler() {        super(RequiresPermissions.class);    }        // 获取对应的注解值    protected String[] getAnnotationValue(Annotation a) {        RequiresPermissions rpAnnotation = (RequiresPermissions) a;        return rpAnnotation.value();    }    // 校验以后主题,是否领有对应的权限。    public void assertAuthorized(Annotation a) throws AuthorizationException {        if (!(a instanceof RequiresPermissions)) return;        RequiresPermissions rpAnnotation = (RequiresPermissions) a;        String[] perms = getAnnotationValue(a);        Subject subject = getSubject();        if (perms.length == 1) {            subject.checkPermission(perms[0]);            return;        }        if (Logical.AND.equals(rpAnnotation.logical())) {            getSubject().checkPermissions(perms);            return;        }        if (Logical.OR.equals(rpAnnotation.logical())) {            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first            boolean hasAtLeastOnePermission = false;            for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;            // Cause the exception if none of the role match, note that the exception message will be a bit misleading            if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);                    }    }}

小结

这一节咱们解说了如何整合 spring 与 shiro,下一节咱们将实战整合 springboot 与 shiro,感兴趣的小伙伴能够关注一波不迷路。

为了便于大家学习,所有源码都已开源:

https://gitee.com/houbinbin/shiro-inaction/tree/master/shiro-inaction-02-springalone

心愿本文对你有所帮忙,如果喜爱,欢送点赞珍藏转发一波。

我是老马,期待与你的下次相遇。

参考资料

10 Minute Tutorial on Apache Shiro

https://shiro.apache.org/reference.html

https://shiro.apache.org/session-management.html

本文由博客一文多发平台 OpenWrite 公布!