I have tried security configuration for spring webFlux as described in documentation: https://docs.spring.io/spring-security/site/docs/current/reference/html/jc-webflux.html#explicit-webflux-security-configuration
Security configuration ignores userDetailsServiceBean - i cannot login with user:pass, but may login with credentials from autoconfigurated
UserDetailsServiceAutoConfiguration:
2019-04-22 11:29:24.571 INFO 12343 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: ff00a80a-d810-43d6-9c89-e861eb1bed96
My pom.xml (fragment):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
My security config:
#EnableWebFluxSecurity
#EnableWebSecurity
#Configuration
public class WebfluxSecurityConfig {
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin();
return http.build();
}
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("pass")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
Why security config ignores userDetailsService ?
Is there error in spring-security documentation?
Remove the #EnableWebSecurity annotation. It's used for Servlet applications and isn't applied on WebFlux.
You might also consider defining a bean of type UserDetailsRepositoryReactiveAuthenticationManager in your WebFlux Configuration; such as the following:
#Bean
public ReactiveAuthenticationManager authenticationManager(ReactiveUserDetailsService detailsService) {
return new UserDetailsRepositoryReactiveAuthenticationManager(detailsService);
}
In your case, most probably, the #EnableWebSecurity annotation configures a bean of type InMemoryUserDetailsManager which is the non-reactive variant of ReactiveUserDetailsManager.
NOTE: You can remove the following from your POM if you're planning to use WebFlux only: spring-boot-starter-tomcat and spring-boot-starter-web
Related
Recently I have integrated Spring Boot Admin in my application. Everything fine, until I've stared adding security (nothing complicated, just Basic Auth). When I try to login in Spring Boot Admin UI, it redirects me back to the login page with "Login required to access the resource (Error: 401).", or to "variables.css". I am using Spring Boot 3.0.0 with Spring Boot Admin version 3.0.0-M6.
I have to mention that everything works alright if I disable spring security.
Security Config class looks like this:
#Configuration(proxyBeanMethods = false)
#EnableWebSecurity
#AllArgsConstructor
public class AdminSecurityConfig {
private final AdminServerProperties adminServerProperties;
private final SecurityProperties securityProperties;
private final AuthenticationConfiguration authenticationConfiguration;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(this.adminServerProperties.path("/"));
http
.authorizeHttpRequests(
(authorizeRequests) ->
authorizeRequests.requestMatchers(this.adminServerProperties.path("/assets/**")).permitAll()
.requestMatchers(this.adminServerProperties.path("/*.css")).permitAll()
.requestMatchers(this.adminServerProperties.path("/actuator/info")).permitAll()
.requestMatchers(this.adminServerProperties.path("/actuator/health")).permitAll()
.requestMatchers(this.adminServerProperties.path("/login")).permitAll()
.anyRequest().authenticated()
).formLogin(
(formLogin) -> formLogin.loginPage(this.adminServerProperties.path("/login")).successHandler(successHandler).and()
).logout((logout) -> logout.logoutUrl(this.adminServerProperties.path("/logout")))
.httpBasic(Customizer.withDefaults())
.csrf(
(csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
new AntPathRequestMatcher(this.adminServerProperties.path("/instances"),
HttpMethod.POST.name()),
new AntPathRequestMatcher(this.adminServerProperties.path("/instances/*"),
HttpMethod.DELETE.name()),
new AntPathRequestMatcher(this.adminServerProperties.path("/actuator/**"))
));
return http.build();
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User.withUsername(securityProperties.getUser().getName())
.password("{noop}" + securityProperties.getUser().getPassword())
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
also, I have added this in the application.yaml file
spring:
security:
user:
name: ****
password: ****
roles:
- USER
boot:
admin:
monitor:
status-interval: 30000
status-lifetime: 30000
ui:
title: "Invoice Matching Admin"
remember-me-enabled: false
Main class looks like this:
#EnableAdminServer
#SpringBootApplication
public class InvoiceAdminServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InvoiceAdminServiceApplication.class, args);
}
}
pom.xml contains this dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
In case that somebody will encounter the same problem, I managed to find the solution.
In the filterChain method you should add this:
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
Here is a documentation (https://codecentric.github.io/spring-boot-admin/3.0.0-M7/security.html#securing-spring-boot-admin) for Spring Admin security.
Implementing the logic of limiting active sessions for each user (number of possible sessions stored in the database, sessions in Redis), I came across the problem of configuring FindByIndexNameSessionRepository.
Due to the fact that I am injecting FindByIndexNameSessionRepository spring starts to configure the bean in the RedisHttpSessionConfiguration class, and the fields are not injected into the class via setters.
As a result, I get the error:
Caused by: java.lang.IllegalStateException: RedisConnectionFactory is required
at org.springframework.util.Assert.state(Assert.java:73) ~[spring-core-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.data.redis.core.RedisAccessor.afterPropertiesSet(RedisAccessor.java:38) ~[spring-data-redis-2.2.6.RELEASE.jar:2.2.6.RELEASE]
at org.springframework.data.redis.core.RedisTemplate.afterPropertiesSet(RedisTemplate.java:127) ~[spring-data-redis-2.2.6.RELEASE.jar:2.2.6.RELEASE]
at org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.createRedisTemplate(RedisHttpSessionConfiguration.java:291) ~[spring-session-data-redis-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.sessionRepository(RedisHttpSessionConfiguration.java:120) ~[spring-session-data-redis-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
Config code:
#EnableRedisHttpSession
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
public class CustomConfiguration extends WebSecurityConfigurerAdapter {
private final FindByIndexNameSessionRepository sessionRepository;
// ....
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.authenticated()
.and()
.formLogin()
.and()
// ....
.sessionManagement()
.sessionAuthenticationStrategy(new CustomAuthenticationStrategy(sessionRegistry(), sessionsLimitRepository));
}
#Bean
public SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(sessionRepository);
}
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<!-- .... -->
</dependencies>
If you remove\comment out the places where FindByIndexNameSessionRepository is used, then the RedisHttpSessionConfiguration setters are called by the "guts" of the spring when the springSessionRepositoryFilter bean is created.
If I understood correctly, then I accidentally broke the order of configuring the beans. In attempts to fix it, I tried #DependsOn, #Lazy and various frauds with moving bins to different classes of configs, changing the order of loading configs, and so on.
UPD: Add bean httpSessionEventPublisher and pom.xml
The main problem was that spring-boot had already configured most of the bins for me.
To resolve this problem, you must remove #EnableRedisHttpSession (#EnableWebSecurity can also be deleted), beans redisConnectionFactory and httpSessionEventPublisher, and optionally you can remove the unnecessary dependency spring-session-core
Actual config code:
#Configuration
#RequiredArgsConstructor
public class CustomConfiguration extends WebSecurityConfigurerAdapter {
private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;
// ....
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.authenticated()
.and()
.formLogin()
.and()
// ....
.sessionManagement()
.sessionAuthenticationStrategy(new CustomAuthenticationStrategy(sessionRegistry(), sessionsLimitRepository));
}
#Bean
public SessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
}
Actual pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- .... -->
</dependencies>
The addLogoutHandler is not working.
When visiting the API request /logout It still using the default handler CompositeLogoutHandler and SimpleUrlLogoutSuccessHandler.
logoutRequestMatcher,addLogoutHandler,logoutSuccessHandler are all not working.
I am using the spring-boot, part of the dependencies.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
</dependencies>
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.addLogoutHandler((request,response,authentication) -> System.out.println("=====1====="))
.addLogoutHandler((request,response,authentication) -> System.out.println("=====2======"))
.addLogoutHandler((request,response,authentication) -> System.out.println("=====3======"))
.logoutSuccessHandler(((request, response, authentication) -> {
System.out.println("=====4=======");
response.sendRedirect("/html/logoutsuccess1.html");
}))
.clearAuthentication(true)
.invalidateHttpSession(true);
}
}
So how to make the customize LogoutHandler and LogoutSuccessHandler working.
#EnableResourceServer Because I have added this annotation.
#Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
In this class, it also configured the HttpSecurity.
Hello I am not able to open h2-console with spring boot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RC1</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
SpringSecurity Cfg:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("admin").roles("ADMIN").and()
.withUser("user").password("user").roles("USER");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied")
.and()
.csrf();
}
}
#SpringBootApplication
public class SpringBootWebSecureApplication {
#Bean
public Java8TimeDialect java8TimeDialect() {
return new Java8TimeDialect();
}
public static void main(String[] args) {
SpringApplication.run(SpringBootWebSecureApplication.class);
}
}
I am starting boot with:
-Dserver.port=8090
-Dspring.h2.console.enabled=true
-Dspring.datasource.url=jdbc:h2:mem:testdb
-Dspring.datasource.username=sa
-Dspring.datasource.driverClassName=org.h2.Driver
Logs:
2015-11-13 17:37:47 [restartedMain] DEBUG c.m.a.SpringBootWebSecureApplication - Running with Spring Boot v1.3.0.RC1, Spring v4.2.2.RELEASE
2015-11-13 17:37:49 [restartedMain] INFO o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat initialized with port(s): 8090 (http)
2015-11-13 17:37:50 [localhost-startStop-1] INFO o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'webServlet' to [/h2-console/*]
2015-11-13 17:37:51 [localhost-startStop-1] INFO o.s.s.web.DefaultSecurityFilterChain - Creating filter chain: Ant [pattern='/h2-console/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#11ffd6f0, org.springframework.security.web.context.SecurityContextPersistenceFilter#615c57c5, org.springframework.security.web.header.HeaderWriterFilter#6b2cbcbf, org.springframework.security.web.authentication.logout.LogoutFilter#af7bece, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#17d03ab2, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#613197b, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#8d48442, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#7fa13240, org.springframework.security.web.session.SessionManagementFilter#9f16a1c, org.springframework.security.web.access.ExceptionTranslationFilter#1f3f02ef, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#115de1f6]
But when I open browser with my app
console not appearing
http://localhost:8090/h2-console/
Any hints?
Thank you
Same here, I think it's the Java8TimeDialect Bean, in your case. In my case, I have a DandelionDialect Bean and when I remove it the h2-console works again.... Try removing the Java8TimeDialect to see if the console works.
Something related with the spring boot DispatcherServletAutoConfiguration?, the order in which the servlets are created or mapped? Not really sure...
http.headers().frameOptions().disable();
You can try following configuration class:
import org.h2.server.web.WebServlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.DispatcherType;
import java.util.EnumSet;
#Configuration
public class WebConfiguration {
private static final String mapping = "/console/*";
#Bean
public ServletRegistrationBean h2servletRegistration(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean( new WebServlet());
registrationBean.addUrlMappings(mapping);
return registrationBean;
}
#Bean
public FilterRegistrationBean shallowEtagHeaderFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ShallowEtagHeaderFilter());
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
registration.addUrlPatterns(mapping);
return registration;
}
}
I want to use Spring Security 4.0.3 in my project but I don't really know how to do this in combination with the other technologies:
Vaadin 7.5.8
Vaadins official Spring Addon 1.0.0
Spring 4.2.1
Tomcat 8 Server
I read a few articles about Vaadin and Spring Security but i didn't found anything about integreting it with the official Vaadin Spring Addon.
Because I'm new to Vaadin I followed Vaadins official Spring tutorial and the Spring Security documentation. I want to setup my project without Spring Boot! Currently I got the following:
#WebListener
public class MyContextLoaderListener extends ContextLoaderListener
{
}
#WebServlet(value = "/*", asyncSupported = true)
public class Servlet extends SpringVaadinServlet
{
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder()
{
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
// #formatter:off
http
.authorizeRequests()
.antMatchers("/VAADIN/**", "/UIDL/**", "/login**", "/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/#!", true).permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
// #formatter:on
// TODO plumb custom HTTP 403 and 404 pages
/* http.exceptionHandling().accessDeniedPage("/access?error"); */
}
}
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer
{
}
My pom.xml looks like this:
[...]
<spring.version>4.2.1.RELEASE</spring.version>
<spring.security.version>4.0.3.RELEASE</spring.security.version>
[...]
<dependencies>
[...]
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
</dependencies>
[...]
Now I'm asking myself if I implemented it the right way. Preferably I don't want to use a web.xml instead I want to use Java configuration.
The server starts without an error but when I open the URL I get the following error message:
HTTP Status 404 - Request was not handled by any registered handler.
Can anyone help me?
EDIT:
I don't know if I'm on the right way but I added an asterisk to "/*" in the annotation of the SpringVaadinServlet:
#WebServlet(value = "/**", asyncSupported = true)
Now after the browser redirects to http://localhost:8080/myApp/login I get a normal 404 Not Found without anything...