带有 JavaConfig 和 Spring 引导的 Apache Shiro JdbcRealm
Apache Shiro JdbcRealm with JavaConfig and Spring Boot
我正在尝试配置我的 Spring 引导应用程序以使用 Apache Shiro 作为其安全框架。我的一切都与 PropertiesRealm 一起工作,现在我正试图让它与 JdbcRealm 和 Spring Boot 的内置 H2 数据库一起工作。这是我的 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:
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:
insert into users (username, password, enabled) values ('user', '04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb', true);
insert into user_roles (username, role_name) values ('user', 'guest');
还有我的 WebSecurityConfig.java class 配置一切:
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 中来增加日志记录,但它并没有多大帮助。
logging.level.org.apache.shiro=debug
谢谢,
马特
出现了几个问题。
LifecycleBeanPostProcessor
问题是由于 LifecycleBeanPostProcessor
在您的配置 class 中定义。因为它是一个 BeanPostProcessor
,所以必须急切地对其进行初始化以处理所有其他 bean。此外,WebSecurityConfig
的其余部分需要尽快初始化,因为它可能会影响 LifecycleBeanPostProcessor
。
问题是自动装配功能尚不可用,因为它也是 BeanPostProcessor
(即 AutowiredAnnotationBeanPostProcessor
)。这意味着 DataSource
为空。
由于 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
为空,因此删除了 "remember me" cookie。
解决LifecycleBeanPostProcessor
最简单的解决方案是确保使用静态方法定义任何与基础结构相关的 bean。这通知 Spring 它不需要初始化整个配置 class(即 WebSecurityConfig
)。再次
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
或者,您也可以在自己的配置中隔离与基础设施相关的 bean。
更新
ShiroFilterFactoryBean
我没有意识到 ShiroFilterFactoryBean
也实现了 BeanPostProcessor
。 ObjectFactory
也实现 BeanPostProcessor
.
是非常有趣的情况
问题是这阻止了 data.sql 的加载,这意味着该应用程序在 table 中没有任何用户,因此身份验证将失败。
问题是 data.sql 是通过 DataSourceInitializedEvent
加载的。但是,由于 DataSource
的急切初始化(它是 BeanPostProcessor
的依赖项),DataSourceInitializedEvent
无法触发。这就是您在日志中看到以下内容的原因:
Could not send event to complete DataSource initialization (ApplicationEventMulticaster not initialized)
确保 data.sql 负载
我看到有几个选项可以加载插入语句。
data.sql->schema.sql
最简单的选择是将 data.sql 的内容移动到 schema.sql。 schema.sql 仍然加载,因为它不需要触发事件来处理它。 data.sql 需要一个事件,以便在 JPA 初始化架构时可以使用相同的机制加载数据。
修复排序
不幸的是,您不能简单地将 ShiroFilterFactoryBean
的定义设为静态,因为它依赖于其他 bean 定义。幸运的是,在这种情况下确实不需要 BeanPostProcessor
。这意味着您可以将代码更改为 return 从等式中删除 BeanPostProcessor
的工厂 bean 的结果:
@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);
我正在尝试配置我的 Spring 引导应用程序以使用 Apache Shiro 作为其安全框架。我的一切都与 PropertiesRealm 一起工作,现在我正试图让它与 JdbcRealm 和 Spring Boot 的内置 H2 数据库一起工作。这是我的 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:
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:
insert into users (username, password, enabled) values ('user', '04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb', true);
insert into user_roles (username, role_name) values ('user', 'guest');
还有我的 WebSecurityConfig.java class 配置一切:
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 中来增加日志记录,但它并没有多大帮助。
logging.level.org.apache.shiro=debug
谢谢,
马特
出现了几个问题。
LifecycleBeanPostProcessor
问题是由于 LifecycleBeanPostProcessor
在您的配置 class 中定义。因为它是一个 BeanPostProcessor
,所以必须急切地对其进行初始化以处理所有其他 bean。此外,WebSecurityConfig
的其余部分需要尽快初始化,因为它可能会影响 LifecycleBeanPostProcessor
。
问题是自动装配功能尚不可用,因为它也是 BeanPostProcessor
(即 AutowiredAnnotationBeanPostProcessor
)。这意味着 DataSource
为空。
由于 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
为空,因此删除了 "remember me" cookie。
解决LifecycleBeanPostProcessor
最简单的解决方案是确保使用静态方法定义任何与基础结构相关的 bean。这通知 Spring 它不需要初始化整个配置 class(即 WebSecurityConfig
)。再次
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
或者,您也可以在自己的配置中隔离与基础设施相关的 bean。
更新
ShiroFilterFactoryBean
我没有意识到 ShiroFilterFactoryBean
也实现了 BeanPostProcessor
。 ObjectFactory
也实现 BeanPostProcessor
.
问题是这阻止了 data.sql 的加载,这意味着该应用程序在 table 中没有任何用户,因此身份验证将失败。
问题是 data.sql 是通过 DataSourceInitializedEvent
加载的。但是,由于 DataSource
的急切初始化(它是 BeanPostProcessor
的依赖项),DataSourceInitializedEvent
无法触发。这就是您在日志中看到以下内容的原因:
Could not send event to complete DataSource initialization (ApplicationEventMulticaster not initialized)
确保 data.sql 负载
我看到有几个选项可以加载插入语句。
data.sql->schema.sql
最简单的选择是将 data.sql 的内容移动到 schema.sql。 schema.sql 仍然加载,因为它不需要触发事件来处理它。 data.sql 需要一个事件,以便在 JPA 初始化架构时可以使用相同的机制加载数据。
修复排序
不幸的是,您不能简单地将 ShiroFilterFactoryBean
的定义设为静态,因为它依赖于其他 bean 定义。幸运的是,在这种情况下确实不需要 BeanPostProcessor
。这意味着您可以将代码更改为 return 从等式中删除 BeanPostProcessor
的工厂 bean 的结果:
@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);