Spring Boot mockMvc does not use securitycontext - spring-boot

My testclass wit mockmvc seems not to load the springsecuritscontext:
#WebMvcTest(ReportingController.class)
class ReportingControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
#WithMockUser(username = "piet", roles = { "ACTUATOR" })
#Test
void test() throws Exception {
Query query = new Query();
ObjectMapper mapper = new ObjectMapper();;
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
mockMvc.perform(post("/statistic/report/test").with(csrf()).sessionAttr("query", query)).andDo(print()).andExpect(view().name("test")).andExpect(status().isOk());
}
}
The test failed with "Failed to load ApplicationContext" .... Error creating bean with name 'webSecurityConfig'. The class webSecurityConfig is my extension for WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LoginSuccessHandler loginSuccessHandler;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/vendors/**", "/build/**", "/images/**", "/favicon.ico", "/qlocation/**", "/service/**", "/help.html", "/invalid-session.html",
"/robots.txt").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/").successHandler(loginSuccessHandler)
.permitAll();
}
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
...
}
My assumption is that springSecurity() should load the context or mock it away.
But I'm not sure what is missing.

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.

Problems Injecting Custom Authentication Manager

I'm attempting to use a custom authentication manager but the standard Provider manager is being called to .authenticate. I suspect it has something to do with either the AuthSever or Web Config. Any help is greatly appreciated.
AuthServer configuration:
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private final DataSource dataSource;
#Autowired
public AuthServerConfig(DataSource dataSource){
this.dataSource = dataSource;
}
#Autowired
MicrosJwtConfig microsJwtConfig;
#Autowired
#Qualifier("microsProviderManager")
AuthenticationManager authenticationManager;
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder(10);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenServices(microsJwtConfig.microsTokenServices())
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients();
security.passwordEncoder(encoder());
security.tokenKeyAccess("permitAll()");
}
}
WebSecurity config:
#EnableWebSecurity
#Configuration
public class WebSecConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
private MECAuthenticationProvider mecAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return new MicrosProviderManager(clientDetailsService, mecAuthenticationProvider );
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("actuator/health").permitAll().and()
.authorizeRequests().antMatchers("oauth/token").permitAll().and()
.authorizeRequests().antMatchers("actuator/info").permitAll();
}
}

Spring Boot Unit Testing with Spring Security

I have a simple application that I have setup with spring security using a custom MySql Database. Now I'm writing test cases for it and they seems to fail on login page and anything that works after the login. My question is how do I write test cases for it to check the successful login and the subsequent requests?
My Security Config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private DataSource dataSource;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Autowired
private CustomAuthenticationSuccessHandler successHandler;
/** Providing the queries and data source for security*/
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception
{
auth.
jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
}
/** Defining fine grained access for ADMIN and CUSTOMER user */
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/registration").permitAll()
.antMatchers("/user/**").hasAuthority(AppRole.CUSTOMER.toString())
.antMatchers("/health/**").hasAuthority(AppRole.ADMIN.toString())
.antMatchers("/admin/**").hasAuthority(AppRole.ADMIN.toString()).anyRequest()
.authenticated().and().csrf().disable().formLogin()
.loginPage("/login").failureUrl("/login?error=true")
.successHandler(successHandler)
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and().exceptionHandling()
.accessDeniedPage("/access-denied");
}
/** Defining ant matchers that should ignore the paths and provide no access to any one */
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
}
}
My Custom Success Handler:
#Component
#Configuration
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
/** Getting reference to UserService */
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Authentication authentication)
throws IOException, ServletException, RuntimeException
{
HttpSession session = httpServletRequest.getSession();
User authUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
com.crossover.techtrial.java.se.model.User user = userService.findUserByUsername(authUser.getUsername());
session.setAttribute("userId", user.getUserId());
session.setAttribute("username", authUser.getUsername());
session.setAttribute("accountId", user.getAccountId());
//set our response to OK status
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
authorities.forEach(authority ->
{
if(authority.getAuthority().equals(AppRole.ADMIN.toString()))
{
session.setAttribute("role", AppRole.ADMIN);
try
{
//since we have created our custom success handler, its up to us to where
//we will redirect the user after successfully login
httpServletResponse.sendRedirect("/admin/home");
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
else if (authority.getAuthority().equals(AppRole.CUSTOMER.toString()))
{
session.setAttribute("role", AppRole.CUSTOMER);
try
{
//since we have created our custom success handler, its up to us to where
//we will redirect the user after successfully login
httpServletResponse.sendRedirect("/user/home");
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
});
}
}
After some seraching I tried to write test cases like this but they don't seem to be working:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TrialApplicationTests
{
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private MockHttpServletRequest request;
private MockMvc mockMvc;
#Test
public void contextLoads()
{
}
#Before
public void setup()
{
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilters(springSecurityFilterChain)
.build();
}
#Test
public void verifiesLoginPageLoads() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.view().name("login"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
#Test
public void testUserLogin() throws Exception
{
HttpSession session = mockMvc.perform(post("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "test")
.param("password", "test123")
)
.andExpect(MockMvcResultMatchers.status().isOk())
//.andExpect(redirectedUrl("/user/home"))
.andReturn()
.getRequest()
.getSession();
request.setSession(session);
SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(securityContext);
}
#Test
public void testRetrieveUserBookings() throws Exception
{
testUserLogin();
mockMvc.perform(MockMvcRequestBuilders.get("user/bookings"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("bookings"))
.andExpect(MockMvcResultMatchers.view().name("user/bookings"))
.andExpect(content().string(containsString("Booking")));
}
}
I searched on the net and there are links WithMockUser and UserDetails, but the problem is as you can see I'm setting a my primary key userId in the session in my custom success handler. So I would also need to get the session in my test. Please tell me the simplest way to write tests that will work, possibly with code since I'm new with security and all such.
UPDATE:
I changed the code as suggested but still getting the 404 error on my testRetrieveUserBookings. Any more ideas?
#RunWith(SpringRunner.class)
#ContextConfiguration
#SpringBootTest
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#TestExecutionListeners(listeners={ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class})
public class TrialApplicationTests
{
#Autowired
private WebApplicationContext webApplicationContext;
MockMvc mockMvc;
#Autowired
ForestApiClient apiClient;
#Autowired
AccountClient accountClient;
#Autowired
AirlineClient airlineClient;
#Autowired
UserService userService;
private final String INTEGRATION_ACCOUNT = "account1";
private MockHttpSession mockSession;
private Authentication authentication;
#Test
public void contextLoads()
{
}
#Before
public void setup()
{
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
//.addFilters(springSecurityFilterChain)
.build();
mockSession = new MockHttpSession(webApplicationContext.getServletContext(), UUID.randomUUID().toString());
mockSession.setAttribute("userId", 3);
mockSession.setAttribute("accountId", "ZWR26539");
}
#Test
public void testVerifiesLoginPageLoads() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.view().name("login"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
#Test
public void testRegistration() throws Exception
{
mockMvc.perform(post("/registration")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "test2")
.param("password", "test123")
.param("email", "crossovertestuser#gmail.com")
.param("address", "Some Address")
.param("accountCurrency", "USD")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("user"))
.andExpect(MockMvcResultMatchers.view().name("registration"))
.andExpect(content().string(containsString("User has been registered successfully")));
}
#Test
#WithMockUser(username="test",roles={"USER","ADMIN"})
public void testRetrieveUserBookings() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("user/bookings"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("bookings"))
.andExpect(MockMvcResultMatchers.view().name("user/bookings"))
.andExpect(content().string(containsString("Booking")));
}
}
If your problem is only getting session inside the test, then you can go for MockHttpSession.
#Before
public void setUp() throws Exception {
mock = MockMvcBuilders.webAppContextSetup(wac).addFilters(springSecurityFilterChain).build();
MockHttpSession httpSession = new MockHttpSession(webAppContext.getServletContext(), UUID.randomUUID().toString());
}
#Test
public void test1(){
mock.perform(get("/").session(mockSession)).perfor();
}

Spring OAuth #EnableResourceServer preventing login page from OAuth server

Browser Response for localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com is 302 Found, but response for localhost:9999/uaa/login is 401 Unauthorized.
I could get the login token prior to adding the #EnableResourceServer. I am using Spring boot and extending WebSecurityConfigurerAdapter to use authentication Manager with data source. When I tried to add a ResourceServerConfigurerAdapter it wouldn't build. What is the easiest way to allow the login page?
#SpringBootApplication
#RestController
#EnableResourceServer
public class OAuthSvcApplication extends WebMvcConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(OAuthSvcApplication.class);
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
public static void main(String[] args) {
SpringApplication.run(OAuthSvcApplication.class, args);
}
}
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource, Environment env)
throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.checkTokenAccess("hasAuthority('USER')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code",
"refresh_token", "password").scopes("openid");
}
}
}
SpringSecurityFilterChain should always be ordered before other filters.
If you want to add your own authentication for all or some endpoints the best thing to do is add your own WebSecurityConfigurerAdapter with lower order. Modifying the WebSecurityConfigurerAdapter subclass as follows allows the ResourceServer to work with a jdbc authentication mgr:
#Configuration
#Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager).jdbcAuthentication().dataSource(dataSource);
}
}

Autowired CrudRepository into UserDetailsService is always null

I'm newbie in Spring-World, I have a Spring Boot application with Spring Security and JPA. Also have CrudRepository and UserDetailsService, see below
Application class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
UserDao
#Repository
public interface UserDao extends CrudRepository<User, Long> {
public Collection<User> findByName(String name);
}
ApiUserDetailsService
#Component
public class ApiUserDetailsService implements UserDetailsService {
#Autowired
private UserDao dao;
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
assert dao != null;
...
}
}
Security Config
#Configuration
#EnableWebSecurity
public class HttpBasicAuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestCache().requestCache(new NullRequestCache());
http.httpBasic();
http.authorizeRequests().anyRequest().authenticated();
}
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new ApiUserDetailsService());
}
}
Why Autowired dao is always null? What I do wrong?
You are creating the ApiUserDetailsService manually, in the the method:
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new ApiUserDetailsService());
}
What you want is:
#Configuration
#EnableWebSecurity
#EnableJpaRepositories(basePackages = {"your.package.dao"})
public class HttpBasicAuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestCache().requestCache(new NullRequestCache());
http.httpBasic();
http.authorizeRequests().anyRequest().authenticated();
}
// register ApiUserDetailsService as a bean
#Bean
public UserDetailsService apiUserDetailsService() {
return new ApiUserDetailsService();
}
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
// get the autowired bean from Spring
auth.userDetailsService(apiUserDetailsService());
}
}

Resources