问题描述
我正在尝试配置Spring Boot应用程序以使用Apache Shiro作为其安全框架.我可以使用PropertiesRealm进行所有操作,现在我正在尝试使其与JdbcRealm和Spring Boot的内置H2数据库一起使用.这是我pom.xml中的依赖项:
I'm trying to configure my Spring Boot application to use Apache Shiro as its security framework. I have everything working with a PropertiesRealm, now I'm trying to get it working with a JdbcRealm and Spring Boot's built-in H2 database. Here's my dependencies in my pom.xml:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
我的schema.sql:
My schema.sql:
create table if not exists users (
username varchar(256),
password varchar(256),
enabled boolean
);
create table if not exists user_roles (
username varchar(256),
role_name varchar(256)
);
我的data.sql:
My data.sql:
insert into users (username, password, enabled) values ('user', '04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb', true);
insert into user_roles (username, role_name) values ('user', 'guest');
还有我的WebSecurityConfig.java类,用于配置所有内容:
And my WebSecurityConfig.java class that configures everything:
package security;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.h2.server.web.WebServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WebSecurityConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMapping = new HashMap<>();
filterChainDefinitionMapping.put("/api/health", "authc,roles[guest],ssl[8443]");
filterChainDefinitionMapping.put("/login", "authc");
filterChainDefinitionMapping.put("/logout", "logout");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/login");
Map<String, Filter> filters = new HashMap<>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl("/login?logout");
filters.put("logout", logoutFilter);
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
return shiroFilter;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jdbcRealm());
return securityManager;
}
@Autowired
private DataSource dataSource;
@Bean(name = "realm")
@DependsOn("lifecycleBeanPostProcessor")
public JdbcRealm jdbcRealm() {
JdbcRealm realm = new JdbcRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
realm.setCredentialsMatcher(credentialsMatcher);
realm.setDataSource(dataSource);
realm.init();
return realm;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ServletRegistrationBean h2servletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet());
registration.addUrlMappings("/console/*");
return registration;
}
}
我在日志中没有看到任何错误.我尝试增加日志记录,然后将以下内容添加到application.properties中,但这并没有太大帮助.
I'm not seeing any errors in my logs. I tried cranking up the logging my adding the following to my application.properties, but it doesn't help much.
logging.level.org.apache.shiro=debug
谢谢
马特
推荐答案
正在发生一些问题.
问题是由于在配置类中定义了LifecycleBeanPostProcessor
所致.由于它是BeanPostProcessor
,因此必须热切初始化它以处理所有其他bean.此外,WebSecurityConfig
的其余部分需要急切初始化,因为它可能会影响LifecycleBeanPostProcessor
.
The problem is due to the fact that LifecycleBeanPostProcessor
is defined in your config class. Since it is a BeanPostProcessor
it must be initialized eagerly to process all other beans. Furthermore, the rest of WebSecurityConfig
needs to be initialized eagerly since it may impact LifecycleBeanPostProcessor
.
问题在于自动连线功能尚不可用,因为它也是BeanPostProcessor
(即AutowiredAnnotationBeanPostProcessor
).这意味着DataSource
为空.
The problem is that the autowired feature is not yet available because it is a BeanPostProcessor
(i.e. AutowiredAnnotationBeanPostProcessor
) too. This means the DataSource
is null.
由于为空,所以 JdbcRealm
将抛出NullPointerException
.反过来被AbstractAuthenticator
和重新发布为AuthenticationException
.然后DefaultWebSecurityManager
(实际上是其父级DefaultSecurityManager
)捕获调用onFailedLogin
,该删除会删除记住我" cookie.
Since it is null the JdbcRealm
is going to throw a NullPointerException
. This is in turn caught by AbstractAuthenticator
and rethrown as an AuthenticationException
. The DefaultWebSecurityManager
(actually its parent DefaultSecurityManager
) then catches it invokes onFailedLogin
which removes the "remember me" cookie.
最简单的解决方案是确保使用静态方法定义所有与基础架构相关的bean.这通知Spring不需要初始化整个配置类(即WebSecurityConfig
).再次
The easiest solution is to ensure any infrastructure related beans are defined with a static method. This informs Spring that it does not need to initialize the entire configuration class (i.e. WebSecurityConfig
). Again
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
或者,您也可以按自己的配置隔离与基础架构相关的bean.
Alternatively, you can also isolate infrastructure related beans in their own configuration.
更新
我没有意识到ShiroFilterFactoryBean
也实现了BeanPostProcessor
. ObjectFactory
也实现BeanPostProcessor
的情况非常有趣.
I didn't realize that ShiroFilterFactoryBean
implements BeanPostProcessor
also. It is pretty interesting case for an ObjectFactory
to also implement BeanPostProcessor
.
问题在于,这阻止了data.sql的加载,这意味着应用程序在表中没有任何用户,因此身份验证将失败.
The problem is that this is preventing the loading of data.sql which means the application does not have any users in the table so authentication will fail.
问题在于data.sql是通过DataSourceInitializedEvent
加载的.但是,由于DataSource
的急切初始化(它是BeanPostProcessor
的依赖项),无法触发DataSourceInitializedEvent
.这就是为什么您在日志中看到以下内容的原因:
The issue is that data.sql is loaded via a DataSourceInitializedEvent
. However, due to the eager initialization of the DataSource
(it was a dependency of a BeanPostProcessor
) the DataSourceInitializedEvent
cannot be fired. This is why you see the following in the logs:
确保data.sql加载
我看到一些选项可以加载insert语句.
Ensuring data.sql Loads
There are a few options that I see to get the insert statements to load.
最简单的选择是将data.sql的内容移动到schema.sql. schema.sql仍处于加载状态,因为它不需要触发事件来处理它. data.sql需要一个事件,以便在JPA初始化架构时可以使用相同的机制来加载数据.
The easiest option is to move the contents of data.sql to schema.sql. The schema.sql is still loaded since it does not require an event to be fired to process it. The data.sql requires an event so that the same mechanism can be used to load data when JPA initializes the schema.
不幸的是,您不能简单地将ShiroFilterFactoryBean
的定义设为静态,因为它依赖于其他bean定义.幸运的是,在这种情况下,确实不需要BeanPostProcessor
.这意味着您可以更改代码以返回工厂bean的结果,该结果将从等式中删除BeanPostProcessor
:
Unfortunately, you cannot simply make the definition for ShiroFilterFactoryBean
static since it relies on other bean definitions. Fortunately, there really is no need for the BeanPostProcessor
in this instance. This means you can change your code to return the result of the factory bean which removes the BeanPostProcessor
from the equation:
@Bean(name = "shiroFilter")
public AbstractShiroFilter shiroFilter() throws Exception {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMapping = new HashMap<>();
filterChainDefinitionMapping.put("/api/health", "authc,roles[guest],ssl[8443]");
filterChainDefinitionMapping.put("/login", "authc");
filterChainDefinitionMapping.put("/logout", "logout");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/login");
Map<String, Filter> filters = new HashMap<>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl("/login?logout");
filters.put("logout", logoutFilter);
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
return (AbstractShiroFilter) shiroFilter.getObject();
}
插入用户
在data.sql中找到的插入语句不正确.它需要包括enabled
列.例如:
insert into users values ('admin', '22f256eca1f336a97eef2b260773cb0d81d900c208ff26e94410d292d605fed8',true);
这篇关于具有JavaConfig和Spring Boot的Apache Shiro JdbcRealm的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!