小编典典

具有JavaConfig和Spring Boot的Apache Shiro JdbcRealm

java

我正在尝试配置Spring Boot应用程序以使用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类,它配置所有内容:

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

谢谢,

马特


阅读 215

收藏
2020-11-16

共1个答案

小编典典

有几个正在发生的问题。

LifecycleBeanPostProcessor

问题是由于LifecycleBeanPostProcessor在config类中定义的事实。由于它是a,BeanPostProcessor因此必须将其初始化以处理所有其他bean。此外,其余WebSecurityConfig需求可能会急切初始化,因为它可能会影响LifecycleBeanPostProcessor

问题在于自动连线功能尚不可用,因为它也是一个BeanPostProcessor(即AutowiredAnnotationBeanPostProcessor)。这表示的DataSource值为null。

由于它为null,JdbcRealm因此将抛出NullPointerException。反过来,它被捕获AbstractAuthenticator并重新抛出AuthenticationException。在DefaultWebSecurityManager(实际上它的父DefaultSecurityManager),然后捕获它调用onFailedLogin它消除了“记住我”的cookie。

解决LifecycleBeanPostProcessor

最简单的解决方案是确保使用静态方法定义所有与基础架构相关的bean。这通知Spring不需要初始化整个配置类(即WebSecurityConfig)。再次

@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
}

另外,您也可以在其自己的配置中隔离与基础结构相关的Bean。

更新

ShiroFilterFactoryBean

我也没意识到那个ShiroFilterFactoryBean工具BeanPostProcessorObjectFactory也可以实施一个非常有趣的情况BeanPostProcessor

问题在于,这阻止了data.sql的加载,这意味着应用程序在表中没有任何用户,因此身份验证将失败。

问题是data.sql是通过加载的DataSourceInitializedEvent。但是,由于急切的初始化DataSource(这是a的依赖项BeanPostProcessor),DataSourceInitializedEvent因此无法触发。这就是为什么您在日志中看到以下内容的原因:

无法发送事件以完成数据源初始化(未初始化ApplicationEventMulticaster)

确保data.sql加载

我看到一些选项可以使insert语句加载。

data.sql-> schema.sql

最简单的选项是将data.sql的内容移动到schema.sql。schema.sql仍在加载,因为它不需要触发事件来处理它。data.sql需要一个事件,以便在JPA初始化架构时可以使用相同的机制来加载数据。

整理订单

不幸的是,您不能简单地为ShiroFilterFactoryBeanstatic
定义,因为它依赖于其他bean定义。幸运的是,BeanPostProcessor在这种情况下,确实不需要。这意味着您可以更改代码以返回工厂bean的结果,该结果BeanPostProcessor将从等式中删除:

@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中找到的insert语句不正确。它需要包括enabled列。例如:

insert into users values ('admin', '22f256eca1f336a97eef2b260773cb0d81d900c208ff26e94410d292d605fed8',true);
2020-11-16