Apache Shiro JdbcRealm с JavaConfig и Spring Boot
Я пытаюсь настроить приложение Spring Boot для использования Apache Shiro в качестве основы безопасности. У меня все работает с PropertiesRealm, теперь я пытаюсь заставить его работать со встроенной базой данных H2 JdbcRealm и Spring Boot. Вот мои зависимости в моем 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>
Моя схема.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)
);
Мои данные.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
Спасибо,
Matt
1 ответ
Есть пара проблем, которые происходят.
LifecycleBeanPostProcessor
Проблема связана с тем, что LifecycleBeanPostProcessor
определяется в вашем классе конфигурации. Так как это BeanPostProcessor
он должен быть инициализирован для обработки всех других компонентов. Кроме того, остальные WebSecurityConfig
нужно инициализировать с нетерпением, так как это может повлиять LifecycleBeanPostProcessor
,
Проблема заключается в том, что функция автоматической проводной связи еще не доступна, поскольку BeanPostProcessor
(т.е. AutowiredAnnotationBeanPostProcessor
) тоже. Это означает, что DataSource
нулевой.
Так как это нуль JdbcRealm
собирается броситьNullPointerException
, Это в свою очередь пойманAbstractAuthenticator
и переброшен какAuthenticationException
, DefaultWebSecurityManager
(на самом деле его родитель DefaultSecurityManager
) потом ловит вызываетonFailedLogin
который удаляет печенье "запомнить меня".
Решение жизненного цикла BeanPostProcessor
Самое простое решение - убедиться, что все связанные с инфраструктурой bean-компоненты определены статическим методом. Это сообщает Spring, что ему не нужно инициализировать весь класс конфигурации (т.е. WebSecurityConfig
). Снова
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
Кроме того, вы также можете изолировать связанные с инфраструктурой bean-компоненты в их собственной конфигурации.
ОБНОВИТЬ
ShiroFilterFactoryBean
Я не поняла что ShiroFilterFactoryBean
инвентарь BeanPostProcessor
также. Это довольно интересный случай для ObjectFactory
также реализовать BeanPostProcessor
,
Проблема в том, что это предотвращает загрузку data.sql, что означает, что у приложения нет пользователей в таблице, поэтому аутентификация не удастся.
Проблема в том, что data.sql загружается через DataSourceInitializedEvent
, Однако из-за нетерпеливой инициализации DataSource
(это была зависимость BeanPostProcessor
) DataSourceInitializedEvent
не может быть уволен. Вот почему вы видите следующее в журналах:
Не удалось отправить событие для завершения инициализации источника данных (ApplicationEventMulticaster не инициализирован)
Обеспечение data.sql нагрузок
Есть несколько вариантов загрузки инструкций вставки.
data.sql-> schema.sql
Самый простой вариант - переместить содержимое data.sql в schema.sql. Schema.sql по-прежнему загружен, так как для его обработки не требуется запуск события. Для data.sql требуется событие, чтобы тот же механизм можно было использовать для загрузки данных, когда JPA инициализирует схему.
Исправление заказа
К сожалению, вы не можете просто дать определение ShiroFilterFactoryBean
static, поскольку он опирается на другие определения bean-компонентов. К счастью, в этом нет необходимости BeanPostProcessor
в этом случае. Это означает, что вы можете изменить свой код, чтобы он возвращал результат фабричного компонента, который удаляет 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, неверен. Это должно включать enabled
колонка. Например:
insert into users values ('admin', '22f256eca1f336a97eef2b260773cb0d81d900c208ff26e94410d292d605fed8',true);