上一篇文章介绍了shiro在spring-boot中通过filter实现authentication流程(通过设置filterMaps也可以达到authorization的目的);这篇文章主要介绍spring通过AOP的方式实现shiro的authorization的流程。

ShiroAnnotationProcessorAutoConfiguration

shiro-spring-boot-web-starter除了自身在META-INF中定义了ShiroWebAutoConfigurationShiroWebFilterConfiguration外,还在pom文件中引用了shiro-spring-boot-stater。而后者在自己的META-INF文件中又定义了三个配置类:

  • ShiroAutoConfiguration:主要将shiro中重要的组件声明成bean。大部分配置被ShiroWebAutoConfiguration中的bean取代。
  • ShiroBeanAutoConfiguration:主要设置了EventBus(便于监听各种事件)和LifecycleBeanPostProcessor(生命周期管理,对象的初始化和销毁)。
  • ShiroAnnotationProcessorAutoConfiguration:顾名思义,shiro注解处理相关的bean都在这个类中配置。
@SuppressWarnings("SpringFacetCodeInspection")
@Configuration
@ConditionalOnProperty(name = "shiro.annotations.enabled", matchIfMissing = true)
public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration { //负责创建代理类的对象
@Bean
@DependsOn("lifecycleBeanPostProcessor")
@ConditionalOnMissingBean
@Override
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return super.defaultAdvisorAutoProxyCreator();
} //声明了Adviosr,Advisor声明了Pointcut和Advice,即规定了在哪些地方做哪些事
@Bean
@ConditionalOnMissingBean
@Override
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
return super.authorizationAttributeSourceAdvisor(securityManager);
}
}

所以shiro通过声明了Advisor,以AOP的方式在执行某些方法前先进行权限校验。

DefaultAdvisorAutoProxyCreator和创建代理的流程

DefaultAdvisorAutoProxyCreator是spring框架提供的用来创建代理的类。可以通过这个类理清spring创建代理的流程。先了解DefaultAdvisorAutoProxyCreator的类继承关系。图中删除了部分继承关系,只保留了最主要的内容:

从接口的继承关系中可以看到,该类的处理可能处于类的实例化前后(Instantiation)和初始化前后(Initialization)。

下面的分析将以Bean的创建流程为顺序。

  1. Bean实例化前:

    实例化前的操作主要是在postProcessBeforeInstantiation()中。
	//实例化前置处理(该方法会在bean实例化前调用,且如果该方法返回不会空,则不会在创建bean的实例)
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);
//
if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
//是否已经被代理过
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
//AOP相关的系统类 和 需要跳过的类(交由子类根据具体需求拓展) 不需要代理
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
} //如果定义了符合该Bean的TargetSource,那么使用TargetSource为该Bean创建代理
//TargetSource可以让用户自定义代理的过程
if (beanName != null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
this.targetSourcedBeans.add(beanName);
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
} return null;
}
  1. 创建Bean实例:

    如果在postProcessBeanBeforeInstantication中已经创建了Bean的代理对象,则会跳过createBean的过程。
  2. 实例化后置处理postProcessAfterInstantication()

    该方法返回boolean型的值,决定是否继续执行是剩下的InstantationAwareBeanPostProcessor
  3. 初始化前置处理postProcessBeforeInitialization():这里不对bean做任务处理直接返回。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
  1. bean初始化,这个阶段可能会设置bean的属性
  2. 初始化后置处理postProcessAfterInitialization()。这一步是spring创建代理的过程。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
//获取缓存的key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//获取是否在之前已经对其代理过
if (!this.earlyProxyReferences.contains(cacheKey)) {
//如果需要代理,则对其进行包装
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

其中的wrapIfNecessary就是为bean创建代理的过程。先判断该bean是否需要创建代理,如果需要则创建代理封装该bean。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//判断是否已经由TargetSource产生过代理
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
//判断是否已经解析过该bean,且结果是不需要代理
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//判断是否是AOP相关类 或是 不需要代理的类
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
} //获取该Bean相关的Advice
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果不等于空,则说明需要代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

决定这个bean是否要代理的一个重要过程是getAdvicesAndAdvisorsForBean()。这个方法会返回需要应用在该bean上的advice或是advisor。如果返回为空,则说明不需要代理。这个方法的具体实现是在AbstractAdvisorAutoProxyCreator

//获取可以应用在该bean上的advise或advisor
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
//具体查找方法交给findEligibleAdvisors实现
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
//如果没找到,则返回特定对象 表示不需要代理
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
//否则转成数组返回
return advisors.toArray();
} //查询核实的advisor方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//找出所有的advisor做候选
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//再在候选的advisor筛选出适用的
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
//拓展Advisor
extendAdvisors(eligibleAdvisors);
//排序
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
} //查找候选advisor的方法委托给BeanFactoryAdvisorRetrievalHelper
protected List<Advisor> findCandidateAdvisors() {
return this.advisorRetrievalHelper.findAdvisorBeans();
} //获取适用的Advisor,主要委托给AopUtil
protected List<Advisor> findAdvisorsThatCanApply(
List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
} /**
* Return whether the Advisor bean with the given name is eligible
* for proxying in the first place.
* @param beanName the name of the Advisor bean
* @return whether the bean is eligible
*/
protected boolean isEligibleAdvisorBean(String beanName) {
return true;
} //对Advisor排序
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
AnnotationAwareOrderComparator.sort(advisors);
return advisors;
}

BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()大概过程就是先通过在beanFactory中查询类型为Advisor.class或其子类的的bean的name。然后根据beanName,再从beanFactory中根据beanName获取对应的Advisor的bean。

public List<Advisor> findAdvisorBeans() {
// 如果已经缓存过,则直接使用缓存的结果
String[] advisorNames = this.cachedAdvisorBeanNames;
//没缓存 则在BeanFactory中搜索一次
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
//根据Advisor类型查询
//这里只是获取bean的name,并未进行实例化
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
if (advisorNames.length == 0) {
return new ArrayList<Advisor>();
} List<Advisor> advisors = new ArrayList<Advisor>();
//根据beanName获取对应的Advisor的bean
for (String name : advisorNames) {
if (isEligibleBean(name)) {
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping currently created advisor '" + name + "'");
}
}
else {
try {
//实例化advisor的bean advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}

再来看决定Advisors是否适用的过程:AopUtils.findAdvisorsThatCanApply()

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}

主要是将Advisor根据不同的类型分成两类:IntroducationAdvisorPointcutAdvisor。两种Advisor因为类型不同,所以判断方式也不一样。IntroductionAdvisor因为是类级别的拦截,它描述的”切点“是针对类,所以是通过ClassFilter来判断。而PointcutAdvisor可以针对方法,通过Pointcut描述切点。这点可以从canApply()中看出。

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
//IntroductionAdvisor直接通过classFilter匹配
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
//PointcutAdvisor则是通过pointcut,在调用canApply的重载方法实现
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}

找到Advisor之后,剩下的就是创建代理的过程。回到wrapIfNecessary,创建代理的过程在createProxy()中。

	//创建代理对象
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
} //创建代理工厂类,并且拷贝需要的配置
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
} //将拦截器封装成advisor
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//设置拦截器和TargetSource
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
//留给子类根据需要拓展
customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//创建代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

上述方法中主要是创建了ProxyFactory对象,并设置属性,在通过ProxyFactory对象创建代理对象。

最后返回的代理对象便取代了原始的bean对象保存在spring容器中待取用。

如果对上述流程图还有不清楚的地方,可以参考我画的流程图。

shiro生命的Advisor:AuthorizationAttributeSourceAdvisor

通过上述流程我们了解了spring如何根据advisor创建代理。现在我们要了解的是shiro的advisor:AuthorizationAttributeSourceAdvisor

类的关系图:

从图中我们可以了解到AuthorizationAttributeSourceAdvisor是一个PointcutAdvisor。如果看代码的话你会发现Pointcut设置的ClassFilterTureClassFilter,也就是说它对任何类判断都会是通过,只校验方法是否正确。因此AuthorizationAttributeSourceAdvisor中最重要的方法就是matches

@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;
} //设置SecurityManager
public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
this.securityManager = securityManager;
} public boolean matches(Method method, Class targetClass) {
Method m = method; if ( isAuthzAnnotationPresent(m) ) {
return true;
} //The 'method' parameter could be from an interface that doesn't have the annotation.
//Check to see if the implementation has it.
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
//判断方法或是类上是否有shiro关注的注解
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
//default return value is false. If we can't find the method, then obviously
//there is no annotation, so just use the default return value.
}
} return false;
} private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
return true;
}
}
return false;
} private boolean isAuthzAnnotationPresent(Method method) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
} }

除了Advisor的matches方法外,还需要关注到的是Advisor设置的advise对象:AopAllianceAnnotationsAuthorizingMethodInterceptor

个人的理解是AopAllianceAnnotattionsAuthorizingMethodInterceptor是将shiro框架中的MethodInterceptor和aopalliance框架中的MethodInterceptor做了适配,让shiro的处理过程转变成aopalliance的MethodIntercetor的处理过程。而后者是我们所熟悉的spring的拦截器。

上图可以看到同时实现了两个MethodInterceptor接口。

AopAllianceAnnotationsAuthorizingMethodInterceptor代码相对简单。

public class AopAllianceAnnotationsAuthorizingMethodInterceptor
extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor { public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(5); //配置shiro拦截器
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);
}
/**
* Creates a {@link MethodInvocation MethodInvocation} that wraps an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance,
* enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments
* (Spring, etc).
*
* @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation}
* @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance.
*/
protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation; return new org.apache.shiro.aop.MethodInvocation() {
public Method getMethod() {
return mi.getMethod();
} public Object[] getArguments() {
return mi.getArguments();
} public String toString() {
return "Method invocation [" + mi.getMethod() + "]";
} public Object proceed() throws Throwable {
return mi.proceed();
} public Object getThis() {
return mi.getThis();
}
};
} /**
* Simply casts the method argument to an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then
* calls <code>methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}()</code>
*
* @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation}
* @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result.
* @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>.
*/
protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable {
MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
return mi.proceed();
} //通过spring中的拦截器机制发起拦截,并将处理转换成shiro的拦截器处理过程,是一个适配的过程
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//将spring的MethodInvocation转换成shiro的MethodInvocation对象
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
//调用AuthorizingMethodInterceptor的invoke方法
return super.invoke(mi);
}
}

AuthorizingMethodInterceptor的invoke则会调用asserAuthorized方法。

public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport {

    //拦截器方法被调用
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
} //授权判断,交给子类实现
protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException; }

AnnotationAuthorizingMethodInterceptor方法实现了assertAuthorized方法,遍历其配置的AuthorizingAnnotationMethodInterceptor对象,如果匹配则进行验证。

public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor {

    /**
* The method interceptors to execute for the annotated method.
*/
protected Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors; public AnnotationsAuthorizingMethodInterceptor() {
//配置默认的权限认证拦截器
methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
methodInterceptors.add(new RoleAnnotationMethodInterceptor());
methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
methodInterceptors.add(new UserAnnotationMethodInterceptor());
methodInterceptors.add(new GuestAnnotationMethodInterceptor());
} public Collection<AuthorizingAnnotationMethodInterceptor> getMethodInterceptors() {
return methodInterceptors;
} public void setMethodInterceptors(Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors) {
this.methodInterceptors = methodInterceptors;
} //遍历所有权限认证拦截器,如果拦截器支持,则使用拦截器认证
protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
//default implementation just ensures no deny votes are cast:
Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
if (aamis != null && !aamis.isEmpty()) {
for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
if (aami.supports(methodInvocation)) {
aami.assertAuthorized(methodInvocation);
}
}
}
}
}

而权限认证拦截器则是将具体认证过程委托给内部的Handler对象处理。因此拦截器处理的过程大致如下:

  1. AopAllianceAnnotationAuthorizingMethodInterceptorinvoke方法被调用
  2. 调用assertAuthorized()
  3. 获取内部配置的认证拦截器,逐个调用assertAuthorized方法
  4. 内部认证拦截器将认证委托给内部的AuthorizingAnnotationHandler处理
  5. RoleAnnotationHandler为例,它会在自己的assertAuthorized方法中校验Subject对象的Role@RequiredRole中要求的是否一致,不一致则会抛出异常,拦截器不在往下走,因为也无法进入到被拦截的方法里。

总结

Shiro权限认证的过程是通过AOP动态代理实现的。相当于在Spring中配置了一个用于权限认证的拦截器,拦截拥有指定注解(@RequiresAuthentication@RequiresUser@RequiresGuest@RequiresRoles@RequiresPermissions)的方法。

Shiro(三):Spring-boot如何集成Shiro(下)的更多相关文章

  1. shiro 和 spring boot 的集成

    1 添加依赖 使用 shiro-spring-boot-web-starter 在 spring boot 中集成 shiro 只需要再添加一个依赖 <dependency> <gr ...

  2. Spring Boot 中集成 Shiro

    https://blog.csdn.net/taojin12/article/details/88343990

  3. 细说shiro之五:在spring框架中集成shiro

    官网:https://shiro.apache.org/ 1. 下载在Maven项目中的依赖配置如下: <!-- shiro配置 --> <dependency> <gr ...

  4. spring boot(十四)shiro登录认证与权限管理

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...

  5. spring boot不要放在tomcat下启动,因为自身就带了集成tomcat

    spring boot不要放在tomcat下启动,因为自身就带了集成tomcat

  6. Spring Boot:集成Druid数据源

    综合概述 数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据 ...

  7. Spring Boot快速集成kaptcha生成验证码

    Kaptcha是一个非常实用的验证码生成工具,可以通过配置生成多样化的验证码,以图片的形式显示,从而无法进行复制粘贴:下面将详细介绍下Spring Boot快速集成kaptcha生成验证码的过程. 本 ...

  8. [转帖]spring boot项目集成jacoco

    小试牛刀:spring boot项目集成jacoco 2019-03-28 20:14:36 zyq23333 阅读数 509   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议, ...

  9. Java | Spring Boot Swagger2 集成REST ful API 生成接口文档

      Spring Boot Swagger2 集成REST ful API 生成接口文档 原文 简介 由于Spring Boot 的特性,用来开发 REST ful 变得非常容易,并且结合 Swagg ...

  10. Spring Boot Redis 集成配置(转)

    Spring Boot Redis 集成配置 .embody{ padding:10px 10px 10px; margin:0 -20px; border-bottom:solid 1px #ede ...

随机推荐

  1. Docker学习之搭建nginx环境

    前言 很久没写随笔了,今天我们来学习一下如何在docker搭建nginx环境吧! 一:下载镜像,使用docker pull拉取最新的nginx镜像 命令:docker pull nginx 查看镜像: ...

  2. PTA数据结构与算法题目集(中文) 7-38寻找大富翁 (25 分)

    PTA数据结构与算法题目集(中文)  7-38寻找大富翁 (25 分) 7-38 寻找大富翁 (25 分)   胡润研究院的调查显示,截至2017年底,中国个人资产超过1亿元的高净值人群达15万人.假 ...

  3. Git应用详解第三讲:本地分支的重要操作

    前言 前情提要:Git应用详解第二讲:Git删除.修改.撤销操作 分支是git最核心的操作之一,了解分支的基本操作能够大大提高项目开发的效率.这一讲就来介绍一些分支的常见操作及其基本原理. 一.分支概 ...

  4. VXLAN 基础教程:VXLAN 协议原理介绍

    VXLAN(Virtual eXtensible Local Area Network,虚拟可扩展局域网),是一种虚拟化隧道通信技术.它是一种 Overlay(覆盖网络)技术,通过三层的网络来搭建虚拟 ...

  5. Struts2-学习笔记系列(6)-动态调用action

    动态调用之前需要配置: <!--动态方法调用--> <constant name="struts.enable.DynamicMethodInvocation" ...

  6. python3(十三)map reduce

    # map()函数接收两个参数,一个是函数,一个是Iterable, # map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回. def f(x): return x * ...

  7. Idea离线安装plugins插件 如Lombok

    由于公司不允许使用外网,之前用的idea 15 安装了一次.但是idea15的提示不够友好,今天升级idea2017.3.2,同样又需要安装,那就写个教程吧. 网上其他的安装教程不通用,也是针对不同i ...

  8. redis 正确实现分布式锁的正确方式

    前言 最近在自己所管理的项目中,发现redis加锁的方式不对,在高并发的情况有问题.故在网上找搜索了一把相关资料.发现好多都是互相抄袭的,很多都是有缺陷的.好多还在用redis 的 setnx命令来实 ...

  9. list[列表]的使用

    #!/usr/bin/env python3# -*- coding:utf-8 -*-# name:zzyushop_list = [["手机",5000], ["电脑 ...

  10. k8s~helm镜像版本永远不要用latest

    对于容器编排工具k8s来说,你可以使用它规定的yaml格式的脚本,使用客户端kubectl来与k8s进行通讯,将你定义好的yaml部署脚本应用到k8s集群上,而这对yaml脚本一般来说都是很像的,就是 ...