本文介绍了具有JavaConfig和Spring Boot的Apache Shiro JdbcRealm的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试配置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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-24 14:01