最近迷上了springboot,开始把公司我搭的框架从dubbo移植为springboot,xml全部转换为configuration类,遇到了很多问题,解决差不多了主要把与shiro的问题放出来与大家分享,因为网上这方面的信息太少了。
springboot中的动态代理设置
在spring的xml配置中我们知道,要设置cglib动态代理只需一句
<aop:aspectj-autoproxy proxy-target-class="true"/>
至于为什么要用强制使用cglib,因为很多时候我们并不会特意为某个bean去写一个接口,再去实现,这种情况下如果用默认的jdk动态代理,则一旦有多个aop代理,比如既有shiro注解代理,又有事务切片或注解代理,这种时候去注入这个bean便会报错:
springboot中的报错
Description::
The bean 'XXX' could not be injected as a 'com.XXX.XXX' because it is a JDK dynamic proxy that implements:
com.sun.proxy.$XX Action::
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.
原因就是这个bean被多次代理的时候,jdk代理是基于接口的,所以最后这个bean的类型变成了代理接口proxy的类型。
而bean被spring aop重复代理其实也是因为错误的配置,比如shiro中:
<!-- 重复代理错误配置-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="serviceAdvisorAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
<bean id="serviceAuthorizationAttributeSourceAdvisor"
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
(如上,我们已经开启了autoproxy,然后又为shiro的注解开启了一个切面代理)
正确配置应该删除“serviceAdvisorAutoProxyCreator”这个bean。
在springboot中同理,springboot的默认配置中已经默认开启了自动代理
{
"name": "spring.aop.auto",
"type": "java.lang.Boolean",
"description": "Add @EnableAspectJAutoProxy.",
"defaultValue": true
}
所以在configuration中只需定义好切面bean或开启相应的注解支持bean即可,springboot的启动类上也不需要@EnableAspectJAutoProxy注解,而强制cglib动态代理只需在application配置文件中加入
spring.aop.proxy-target-class=true
springboot中的shiroFilter配置
实际集成shiro时,我们大部分情况下都需要自定义一个或者多个filter,然后在ShiroFilterFactoryBean中配置这些filter的匹配规则,最后再把这个ShiroFilterFactoryBean自动创建的shiroFilterBean加入到mvc的web filter链中。
在spring mvc项目中,我们通过web.xml来加入shiroFilter:
<!-- shiro 安全过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
并在spring.xml中配置ShiroFilterFactoryBean来管理自定义filter:
<bean id="MyAuthFilter" class="com.persistence.shiro.MyAuthFilter"/>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="filters">
<util:map>
<entry key="myAuthFilter" value-ref="MyAuthFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/anon/**=myAuthFilter
/auth/**=myAuthFilter
</value>
</property>
</bean>
但是在springboot中,我们按照上面的xml转换为configuration类时会出现问题,比如如下配置:
@Bean
@Order(1)
public MyAuthFilter myAuthFilter() {
return new MyAuthFilter();
}
@Bean
@Order(2)
public ShiroFilterFactoryBean shirFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("myAuthFilter", myAuthFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> mapping = new LinkedHashMap<>();
mapping.put("/anon/**", "myAuthFilter");
mapping.put("/auth/**", "myAuthFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(mapping);
return shiroFilterFactoryBean;
}
运行项目,发现登录验证能够正常进入自定义filter,但是运行到subject.hasRoles或者有@RequiresPermissions注解时,会抛出异常:
org.apache.shiro.authz.UnauthenticatedException: This subject is anonymous - it does not have any identifying principals and authorization operations require an identity to check against. A Subject instance will acquire these identifying principals automatically after a successful login is performed be executing org.apache.shiro.subject.Subject.login(AuthenticationToken) or when 'Remember Me' functionality is enabled by the SecurityManager. This exception can also occur when a previously logged-in Subject has logged out which makes it anonymous again. Because an identity is currently not known due to any of these conditions, authorization is denied.
at org.apache.shiro.subject.support.DelegatingSubject.assertAuthzCheckPossible(DelegatingSubject.java:203) [shiro-core-1.4.0.jar:1.4.0]
at org.apache.shiro.subject.support.DelegatingSubject.checkPermission(DelegatingSubject.java:208) [shiro-core-1.4.0.jar:1.4.0]
at org.apache.shiro.authz.aop.PermissionAnnotationHandler.assertAuthorized(PermissionAnnotationHandler.java:74) ~[shiro-core-1.4.0.jar:1.4.0]
at org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(AuthorizingAnnotationMethodInterceptor.java:84) ~[shiro-core-1.4.0.jar:1.4.0]
at org.apache.shiro.authz.aop.AnnotationsAuthorizingMethodInterceptor.assertAuthorized(AnnotationsAuthorizingMethodInterceptor.java:100) ~[shiro-core-1.4.0.jar:1.4.0]
at org.apache.shiro.authz.aop.AuthorizingMethodInterceptor.invoke(AuthorizingMethodInterceptor.java:38) ~[shiro-core-1.4.0.jar:1.4.0]
...
Caused by: org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.util.Map dna.base.service.UserInfoService.queryUserInfoAndRoles(java.lang.Integer)
at org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(AuthorizingAnnotationMethodInterceptor.java:90) ~[shiro-core-1.4.0.jar:1.4.0]
... 84 common frames omitted
(当前的subject对象是未验证的,无法授权)
打断点在自定义filter中可以看到调用subject.login()可以进入到配置的realm中成功进行登录验证的,并且此时subject.isAuthenticated值为true,但在filter链执行完毕后,subject对象又变成未验证的。通过在org.apache.catalina.core.ApplicationFilterChain(spring的filter执行类)中的internalDoFilter方法打断点:
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
我们可以看到,在执行完自定义filter后又执行了shiroFilter,此时会进入shiro的AbstractShiroFilter中将当前的subject删除又新创建了一个subject对象并不会进行验证操作:
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain);
Subject subject = this.createSubject(request, response);
subject.execute(new Callable() {
public Object call() throws Exception {
AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
AbstractShiroFilter.this.executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException var8) {
t = var8.getCause();
} catch (Throwable var9) {
t = var9;
}
因此之后获取到subject已经不是我们自己验证的subject。
原因:springboot会将所有实现Filter接口的bean自动加入到web filter链中执行,即自定义filter与shiroFilter平级,而不是原xml配置中的包含关系。启动日志中我们也可以看到:
[ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'statelessAuthFilter' to: [/*]
[ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'shirFilter' to: [/*]
解决方法:
1.改变filter链的执行顺序,先执行shiroFilter再执行自定义filter:
注意到configuration类中的@order注解,将两个filter的优先级调换,或者不用@order注解,将自定义filter的bean放在shiroFilter bean下面,即保证最后执行的是自定义filter。
2.方法1只是简单保证了自定义filter的执行,但是我们原先在shiroFilter中配置的mapping会失效,即无法按url匹配不同的自定义filter,因此应该正确的建立filter的关系:
@Bean
public ShiroFilterFactoryBean shirFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("myAuthFilter", new MyAuthFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> mapping = new LinkedHashMap<>();
mapping.put("/anon/**", "myAuthFilter");
mapping.put("/auth/**", "myAuthFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(mapping);
return shiroFilterFactoryBean;
}
(即不将自定义filter注册到spring bean,而是交由shiroFilter管理)