Spring MVC testing (security Integration test), JSESSIONID is not present - spring

I have created custom login form for my spring boot app.
In my form integration test, I want to check that received cookies contain JSESSIONID and XSRF-TOKEN.
But, I received only XSRF-TOKEN.
Here is my test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
public class UserIT {
#Autowired
private WebApplicationContext context;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Value("${local.server.port}")
private Integer port;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc =
MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain)
.build();
}
#Test
public void getUserInfoTest() throws Exception {
disableSslVerification();
MvcResult result =
mockMvc.perform(formLogin("/login").user("roy").password("spring")).andExpect(authenticated())
.andReturn();
Cookie sessionId = result.getResponse().getCookie("JSESSIONID");
Cookie token = result.getResponse().getCookie("XSRF-TOKEN");
}
Security conf:
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
//.httpBasic()
//.and()
.headers().frameOptions().disable()
.and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.antMatchers("/actuator/**").hasAuthority(Authority.Type.ROLE_ADMIN.getName())
.antMatchers("/login**", "/index.html", "/home.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login.jsp")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.permitAll()
.and()
.logout().logoutSuccessUrl("/login.jsp").permitAll()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// #formatter:on
}
Please, help me to obtain the required result.

You also don't see Set-Cookie header. For me it's a big limitation of MockMVC. For a workaround see Why does Spring MockMvc result not contain a cookie?.

if you are using spring security with default login endpoint, there is a good news.
you dont need JSESSION from cookies after invoking /login ep. just use #WithMockUser
above your test method, perform login request and from now, all your requests will be authorized as the last logged user.

Related

Configure public access, JWT authentication and HTTP basic authentication depending on paths in Spring Security

A Spring Boot application provides some REST endpoints with different authentication mechanisms. I'm trying to setup the security configuration according to the following requirements:
By default, all endpoints shall be "restricted", that is, if any endpoint is hit for which no specific rule exists, then it must be forbidden.
All endpoints starting with /services/** shall be secured with a JWT token.
All endpoints starting with /api/** shall be secured with HTTP basic authentication.
Any endpoint defined in a RESOURCE_WHITELIST shall be public, that is, they are accessible without any authentication. Even if rules #2 or #3 would apply.
This is what I came up with so far but it does not match the above requirements. Could you help me with this?
#Configuration
#RequiredArgsConstructor
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] RESOURCE_WHITELIST = {
"/services/login",
"/services/reset-password",
"/metrics",
"/api/notification"
};
private final JwtRequestFilter jwtRequestFilter;
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("some-username")
.password(passwordEncoder().encode("some-api-password"))
.roles("api-role");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf().disable()
.formLogin().disable()
// apply JWT authentication
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.mvcMatchers(RESOURCE_WHITELIST).permitAll()
.mvcMatchers("/services/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// apply HTTP Basic Authentication
.and()
.authorizeRequests()
.mvcMatchers("/api/**")
.hasRole(API_USER_ROLE)
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

Spring Security: Multiple http elements with Multiple AuthenticationManagers

I am struggling with Java Config for Spring Security. I have multiple entry points but I cannot get the AuthenticationManagers provisioned correctly.
My first configuration file is like this:
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/service/**")
.addFilterAfter(requestHeaderAuthenticationFilter(), SecurityContextPersistenceFilter.class)
.authorizeRequests()
.antMatchers("/service/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(preAuthenticatedAuthenticationProvider(null));
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception
{
// Takes the value of the specified header as the user principal
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SECRET_HEADER");
filter.setAuthenticationManager(authenticationManager());
filter.setExceptionIfHeaderMissing(false);
return filter;
}
This all works correctly. When I set a breakpoint in the RequestHeaderAuthenticationFilter I see an AuthenticationManager with one AuthenticationProvider, and that is the preAuthenticatedAuthenticationProvider (not shown because is just a regular old bean).
I also have a special security chain for admin users and the like:
#Configuration
#Order(101)
public class AdminSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authenticationProvider(mainSiteLoginAuthenticationProvider())
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").access("SECRET ADMIN ACCESS EXPRESSION")
.antMatchers("/internal/**").access("SECRET INTERNAL ACCESS EXPRESSION")
.anyRequest().permitAll()
.and()
.formLogin()
.defaultSuccessUrl("/admin/thing")
.loginPage("/login")
.loginProcessingUrl("/do_login")
.defaultSuccessUrl("/admin/thing")
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.and()
.exceptionHandling()
//.authenticationEntryPoint(null) // entry-point-ref="loginEntryPoint"
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // create-session="ifRequired"
.and()
.csrf().disable();
}
This is now working (after a lot of struggle), but if I put a breakpoint in the UsernamePasswordAuthenticationFilter, I see that this filter has a different AuthenticationManager instance, which is provisioned with the mainSiteLoginAuthenticationProvider as expected. However, it has a parent AuthenticationManager which is provisioned with the default DaoAuthenticationProvider that generates a temporary password in the logs:
Using default security password: 47032daf-813e-4da1-a224-b6014a705805
So my questions are:
How can I get both security configs to use the same AuthenticationManager? I thought that the SecurityConfig, being order 100, would create one, and then AdminConfig, being 101, would just use it. But I have been unable to get them to use the same AuthenticationManager.
Failing that, how can I prevent the AuthenticationManger of AdminConfig from generating a parent that has the default DaoAuthenticationProvider?
I am using Spring Boot 1.5.9.RELEASE, which means Spring Security 4.2.3.RELEASE.

getting http 404 when testing Spring Boot with Spring Security

TLDR: spring boot test does not find url endpoint defined using spring security
Long story:
My SpringBoot application uses Spring Security.
In its Security context it defines:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/api/login")
.antMatchers(POST, "/api/**")
.hasAuthority(ADMIN)
}
}
My test code is initialized as a SpringBoot Test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MySpringBootApplication.class)
public class ContractVerifierBase {
#Autowired
private WebApplicationContext context;
#Before
public void setUp() throws Exception {
RestAssuredMockMvc.webAppContextSetup(context);
}
}
My test sends s POST request to /api/login and although I expect a 401 to be rturned, a 404 is returned.
ResponseOptions response = given().spec(request.post("/api/login");
Why is it not finding the /api/login?
I think you need to pass to rest assured in the base class basic authentication data. E.g.
#Before
public void setup() {
RestAssuredMockMvc.authentication =
RestAssuredMockMvc.basic("sk_test_BQokikJOvBiI2HlWgH4olfQ2", "");
}

Spring Boot Authorization Basic Header Never Changes

I am attempting to setup a very basic spring boot authenticated application. I am setting the Authorization header in the client and sending it to the backend. I can verify that the client is sending the correct header.
The backend receives the header correctly on the first attempt to login. However if the login credentials are incorrect subsequent requests retain whatever the header for the intial request was (caching it or something).
I am using Redis to Cache the session. My config is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired AuthenticationEntryPoint authenticationEntryPoint;
#Autowired CsrfTokenRepository csrfTokenRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.authorizeRequests().antMatchers("**")
.permitAll()
.anyRequest()
.authenticated()
;
}
}
AuthenticationEntryPoint
public class AuthenticationEntryPointBean {
#Bean
AuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
}
Any direction would be appreciated.
** Edit **
Adding cache settings
#Configuration
#EnableRedisHttpSession
public class HttpSessionConfig {
#Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(); // <2>
}
}
Also I am trying to invalidate cache but that doesn't seem to work
#CrossOrigin
#RequestMapping(value="/auth/login", method = RequestMethod.GET, produces="application/json")
public #ResponseBody String login(#RequestHeader(name = "authorization") String authorization, HttpSession session, HttpServletRequest request)
{
try
{
authorization = authorization.substring("Basic ".length());
String decoded = new String(Base64.getDecoder().decode(authorization),"UTF-8");
Gson gson = new Gson();
LoginRequest login = gson.fromJson(decoded,LoginRequest.class);
UserAuthenticationEntity entity = service.getSecurityContext(login).orElseThrow(() ->
new BadCredentialsException("Authentication Failed.")
);
session.setMaxInactiveInterval((int)TimeUnit.MINUTES.toSeconds(expiresInMinutes));
SecurityContextHolder.getContext().setAuthentication(new EntityContext(entity,expiresInMinutes));
String response = gson.toJson(BasicResponse.SUCCESS);
return response;
}
catch (Exception e)
{
session.invalidate();
e.printStackTrace();
throw new AuthenticationCredentialsNotFoundException("Authentication Error");
}
}
Adding the following to my web security config seemed to do the trick.
.requestCache()
.requestCache(new NullRequestCache())
I am not sure what side effects are of doing this. I picked it up off of a blog https://drissamri.be/blog/2015/05/21/spring-security-and-spring-session/
If there is any more insight into if this is good practice or bad practice I would appreciate any comments.
My final web security config looks like the following:
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.authorizeRequests().antMatchers("**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.sessionManagement()
.sessionFixation()
.newSession()
;

access static content in secured Spring Boot application

I have a standalone Spring Boot application with templates in /src/main/resources/templates and static content in /src/main/resources/static. I would like the static content to be accessible before authentication, so the CSS loads on the login page as well. Now it only loads after authentication. My security configuration looks like this:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = Logger.getLogger(SecurityConfig.class);
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth.inMemoryAuthentication()
...
} catch (Exception e) {
logger.error(e);
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.defaultSuccessUrl("/projects", true)
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/static/**").permitAll()
.anyRequest().authenticated();
}
}
The static content in classpath:/static is served at the root of the application (i.e. /*), whether or not the application is secure, so you need to match on specific paths underneath the root. Spring Boot permits all access by default to /js/**, /css/**, /images/** (see SpringBootWebSecurityConfiguration for details), but you may have switched that off (can't see the rest of your code).

Resources