Authentication should not be null in unit tests with spring-session - spring-boot

I have a spring boot (version 1.5.9.RELEASE) application which uses spring-session to store sessions on Redis. It also uses spring-security to authenticate users. When running the application, after a successful login, the security context contains the Authentication object. But when running unit tests I get this error message Authentication should not be null. Code to reproduce is the following:
#SpringBootApplication
public class DemoRedisDataSessionApplication {
#Configuration
#EnableWebSecurity
#EnableRedisHttpSession(redisNamespace = "demo-redis-spring-session")
public static class AppConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("0000").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and()
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
#RestController
public static class AppController {
#GetMapping("/secured")
public String secured() {
return "secured";
}
}
public static void main(String[] args) {
SpringApplication.run(DemoRedisDataSessionApplication.class, args);
}
}
Here is application.properties
spring.session.store-type=redis
Here is the failing test
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class DemoRedisDataSessionApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testUserShouldBeAuthenticated() throws Exception {
mockMvc.perform(formLogin().user("user").password("0000"))
.andExpect(status().is3xxRedirection())
.andExpect(authenticated());
}
}
Error message for the failed test:
java.lang.AssertionError: Authentication should not be null
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:35)
at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:65)
at org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers$AuthenticatedMatcher.match(SecurityMockMvcResultMatchers.java:98)
In particular it seems that the session is null in the class HttpSessionSecurityContextRepository line 110, but I don't understand why.
I expect the user to be authenticated and the SecurityContext populated after a successful login. Do you have any idea on how to solve this?

Updated:
Firstly, you need to instruct your authentication provider (in your case, it is the default DaoAuthenticationProvider) to return what kind of Authentication object. For instance, you can add httpBasic() into your configure(HttpSecurity http) method in your customized WebSecurityConfigurerAdapter. Essentially, httpBasic() will convert your username and password to a UsernamePasswordAuthenticationToken object such that your DaoAuthenticationProvider can use it to do authentication.
In addition, you need to permitAll for you login url.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and()
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.anyRequest().fullyAuthenticated()
.and().httpBasic();
}
With regards to the unit test, the issue was due to the fact that you didn't wire-in spring security into your mockMvc object. As you are actually spring-boot, I would give you a sample solution with spring-boot-test:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class DemoRedisDataSessionApplicationTests {
#Autowired
WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(springSecurity())
.build();
}
#Test
public void testUserShouldBeAuthenticated() throws Exception {
mockMvc.perform(formLogin().user("user").password("0000"))
.andExpect(status().is3xxRedirection())
.andExpect(authenticated());
}
}
Key Note: springSecurity() in the code above is from import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity.

Related

Test spring boot controllers with JUnit5+Spring Security

I have a spring boot application and want to write integration tests for controllers. It is my SecurityConfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService userDetailsService;
private final SessionAuthenticationProvider authenticationProvider;
private final SessionAuthenticationFilter sessionAuthenticationFilter;
#Override
public void configure(WebSecurity web) {
//...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
}
It is my controller:
#RestController
public class MyController {
//...
#GetMapping("/test")
public List<TestDto> getAll(){
List<TestDto> tests= testService.findAll(authService.getLoggedUser().getId());
return mapper.toTestDtos(tests);
}
}
I Created a test(JUnit 5):
#WebMvcTest(TestController.class)
class TestControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean(name = "mockTestService")
private TestService testService;
#Autowired
private TestMapper mapper;
#MockBean(name = "mockAuthService")
private AuthService authService;
private Test test;
#BeforeEach
void setUp() {
User user = new Test();
user.setId("userId");
when(authService.getLoggedUser()).thenReturn(user);
test = new Facility();
test.setId("id");
test.setName("name");
when(testService.findAll("userId")).thenReturn(singletonList(test));
}
#Test
void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
mockMvc.perform(get("/test").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$..id").value(test.getId()))
.andExpect(jsonPath("$..timeOut").value(test.getName()));
}
}
When I start the test I get an exception: Consider defining a bean of type 'com.auth.MyUserDetailsService' in your configuration.
It happens because I have not UserDetailsService bean in the test. What should I do:
Add 3 beans are required for SecurityConfig, like:
#MockBean
MyUserDetailsService userDetailsService;
#MockBean
SessionAuthenticationProvider authenticationProvider;
#MockBean
SessionAuthenticationFilter sessionAuthenticationFilter;
Add test implementation of SecurityConfig
something else
Which approach is better?
For writing tests for your controller endpoints using #WebMvcTest I would use the great integration from MockMvc and Spring Security.
Make sure you have the following dependency:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Next, you can mock the authenticated user for your MockMvc requests and also set the username, roles, etc.
Either use an annotation to populate the authenticated user inside the SecurityContext for the test:
#Test
#WithMockUser("youruser")
public void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
// ...
}
Or use one of the SecurityMockMvcRequestPostProcessors:
this.mockMvc
.perform(
post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"taskTitle\": \"Learn MockMvc\"}")
.with(csrf())
.with(SecurityMockMvcRequestPostProcessors.user("duke"))
)
.andExpect(status().isCreated());
You can find more information on this here.

Sping Boot DefaultSecurityFilterChain configuration is different in test than in production

We'd like to secure the HTTP endpoint metrics that is a built-in endpoint of Spring Boot. Therefore, we write own WebSecurityConfigurerAdapter, see below
#Configuration
#Order(95)
public class MetricsWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Value("${metrics.username}")
private String metricsUsername;
#Value("${metrics.password}")
private String metricsPassword;
#Autowired
private MyAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(metricsUsername).password("{noop}" + metricsPassword)
.roles("METRICS");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // disable csrf for our requests.
http
.antMatcher("/metrics") //
.httpBasic() //
.authenticationEntryPoint(authenticationEntryPoint) //
.and() //
.authorizeRequests() //
.antMatchers("/metrics").authenticated(); //
}
}
My Spring Boot test for testing the authentication is
#ExtendWith(SpringExtension.class)
#SpringBootTest
#TestPropertySource(locations = "classpath:test.properties")
#AutoConfigureMockMvc
public class MetricsEndpointTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testGetMetricsForbidden() throws Exception {
mockMvc.perform(get("/metrics"))
.andExpect(status().isForbidden());
}
#Test
public void testGetMetrics() throws Exception {
mockMvc.perform(get("/metrics")
.header("Authorization", "Basic " + getBasicAuthentication("testmetricsuser", "testmetricspass")))
.andExpect(status().isOk());
}
private static String getBasicAuthentication(String user, String password) {
String token = user + ":" + password;
try {
return DatatypeConverter.printBase64Binary(token.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException("Cannot encode with UTF-8", ex);
}
}
}
The test testGetMetricsForbidden fails.
When I debugged the class WebSecurity during the test run and in production, I can find following difference in the variable securityFilterChains :
In production:
In test:
The difference is that in production we have five security filter chains and in test six security filter chains. The sixth filter chain is responsible that the test fails because it matches at first. I think the #Order is the reason for the order in the test. My question is how can I disabled that sixth filter in my test.

Authentication provider per url pattern - Spring Boot

I faced problem when configuring different auth providers per url pattern using Spring Boot security. I am trying to configure security in Spring Boot app and want to have swagger behind basic auth and all API is secured only by token. I have it almost working, but noticed that API except the fact that it is secured by token which is verified by IDAuthProvider class it also is secured by basic auth. I do not want that and also noticed that if I removed line:
sessionCreationPolicy(SessionCreationPolicy.STATELESS).
it seems to be working correctly, but still header Basic {token} is being added in request which is something I do not want...
Do you know how can I configure it to make all swagger stuff secured by basic auth and API stuff secured by token?
My configuration looks like below:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public class SwaggerSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProvider userPassAuthProvider;
#Autowired
SwaggerSecurityConfig(UserPassAuthProvider userPassAuthProvider) {
this.userPassAuthProvider = userPassAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/swagger**").
authorizeRequests().
antMatchers("/swagger**").authenticated().
and().httpBasic().and().csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(userPassAuthProvider);
}
}
#Configuration
#Order(2)
public class APISecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProvider idAuthProvider;
#Autowired
APISecurityConfig(IDAuthProvider idAuthProvider) {
this.idAuthProvider = idAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**").
authorizeRequests().anyRequest().authenticated().
and().
addFilterBefore(idpAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class).sessionManagement().
and().
csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(idAuthProvider);
}
IDPAuthenticationFilter idpAuthenticationFilter(AuthenticationManager auth) {
return new IDPAuthenticationFilter(auth, new OrRequestMatcher(new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.GET.toString()), new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.POST.toString()), new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.DELETE.toString()), new AntPathRequestMatcher("/swagger**", HttpMethod.GET.toString())));
}
}
}

How to configure oAuth2 when Authorization Server is also the Resource server

I'm trying to setup a very basic oAuth2 authentication in spring boot 2.x.x using either authorization code grant or implicit grant but I can't seem to access the Resource server (which resides in the same spring boot app as the Authorization server) after the token is obtained.
Following is the configuration of WebSecurityConfigurerAdapter
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] IGNORE_URIS = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/webjars/**",
"/resources/**",
"/h2-console/**",
"/common/**",
"/configuration/ui",
"/configuration/security",
"/error"
};
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(IGNORE_URIS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/product/**")
.hasAnyRole("ADMIN").and()
.httpBasic().and().formLogin().and().authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN");
}
#Bean
public PasswordEncoder bCrypt() {
return new BCryptPasswordEncoder();
}
And the AuthorizationServerConfigurerAdapter
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfiguration(AuthenticationConfiguration authenticationConfiguration) throws Exception {
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("my-client-id")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ADMIN")
.scopes("all")
.resourceIds("product_api")
.secret("{noop}secret").redirectUris("https://google.com").accessTokenValiditySeconds(0);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
So far so good. I am able to reach the default Spring login page by typing the following Url in the browser.
http://localhost:8080/oauth/authorize?response_type=token&client_id=my-client-id&redirect_uri=https://google.com
Then The login page shows up and I enter my credentials.
After I log in I can then grant access to "my-client-id" app.
Eventually after I approve the app I can see the newly generated access token in the URL bar of the browser which is something like this.
https://www.google.com/#access_token=f2153498-6a26-42c6-93f0-80825ef03b16&token_type=bearer&scope=all
My question is that All of this flow won't work when I also configure a Resource Server.
#EnableResourceServer
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("product_api");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/**").permitAll();
}
}
What am I doing wrong? When I try to access the oauth/authorize url as before I get the following:
Why? How can one access the login page and retrieve the token? What Am I missing?
You need to use
#Order
Annotation to specify order for WebMvc and ResourceServer classes
#EnableWebSecurity
#Configuration
#Order(1)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
and for Resource Server
#EnableResourceServer
#Configuration
#Order(2)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
}
If you want to see workable example, you can check it here https://github.com/alex-petrov81/stackoverflow-answers/tree/master/auth-server-also-resource
I've created it from your code example.

EnableResourceServer breaks oAuth2 authorization server

I implemented oAuth2 authorization server using Spring Boot version 1.5.2.RELEASE. The authorization server supports implicit flow. With the WebSecurityConfig below the login form (http://localhost:8200/login) works well.
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JpaUserDetailsService userDetailsService;
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return userDetailsService;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceBean());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return new ProviderManager(singletonList(authenticationProvider()));
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/")
.antMatchers("/docs/**")
.antMatchers("/swagger/**")
.antMatchers("/token/**")
.antMatchers("/v2/*")
.antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login**").permitAll().anyRequest().authenticated().and()
.formLogin().loginPage("/login").permitAll().and()
.logout().permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
I want resource server be a part of the same application. The purpose is I need a /me endpoint that will provide me details of logged in user and endpoints for managing users. But as soon as I add ResourceServerConfig annotated with EnableResourceServer below I start getting an error "Full authentication is required to access this resource" when I request http://localhost:8200/login.
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "proclaim-auth";
#Autowired
private ResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(RESOURCE_ID)
.tokenServices(tokenServices);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/ **").authenticated();
}
}
I suspect that resource server security chain precedes authorization server security chain. I tried to annotate WebSecurityConfig with annotation Order but it did not fix my problem:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
What am I doing wrong? Please advise.
Thanks in advance!
EDIT 1
I added method configure(HttpSecurity http) into ResourceServerConfig and changed value of Order annotation to -1 on WebSecurityConfig. Now the security filted defined in WebSecurityConfig is applied and the one defined in ResourceServerConfig is ignored. So when I call /me endpoint with valid token I'm redirected to login page.
The cause of the problem was wrong configuration of http security in the ResourceServerConfig class. The correct configuration is as follows:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/api/**").and()
.authorizeRequests().anyRequest().authenticated();
}
The requestMatchers will ensure that only requests on paths starting with "/api/" will be processed by this security chain. All other requests will be passed to the security chain defined in the WebSecurityConfig class. I was missing this in my config so all requests were processed by the ResourceServerConfig security chain and none request reached the WebSecurityConfig security chain.

Resources