Spring security with basic auth: password is verified only the first time - spring

I'm using Spring security with Basic auth
I have the following configuration:
#Configuration
public class SecurityConfiguration {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
return http.build();
}
}
And also a service implementing UserDetailsService:
#Service
public class UserService implements UserDetailsService { //... }
This service reads the credentials from the database. I created some credentials username/password.
I'm using Postman to test it and I have the following results (in that order):
1) GET /endpoint using username/wrong_password -> 401
2) GET /endpoint using username/password -> 200
3) GET /endpoint using username/wrong_password -> 200
I expect the last call to return 401, but once it returns 200, it continues returning 200.
Any advice?
Thanks!

It was not an Spring Security issue. I disabled the "cookie jar" from Postman and it worked.
Answer update based on comments: If you don't want to support sessions at all in Spring Security you should set the session creation policy to NEVER (more info here)

Related

How to test http status code 401 (unauthenticated) with MockMVC and Spring Boot OAuth2 Resource Server?

I am currently developing a Spring Boot 3 application which provides a REST API. To consume this API, users have to be authenticated via an OAuth2 workflow of our identity provider keycloak. Therefore, I have used org.springframework.boot:spring-boot-starter-oauth2-resource-server. When I run the application, authentification and authorization works as expected.
Unfortunately, I am unable to write a WebMvcTest for the use case when the user does not provide a JWT for authentification. In this case I expect a HTTP response with status code 401 (unauthenticated) but I get status code 403 (forbidden). Is this event possible because MockMvc mocks parts of the response processing?
I have successfully written test cases for the following to use cases.
The user provides a JWT with the expected claim => I expect status code 200 ✔
The user provides a JWT without the expected claim => I expect status code 403 ✔
I have tried to follow everything from the Spring Security documentation: https://docs.spring.io/spring-security/reference/servlet/test/index.html
Here is my code.
#WebMvcTest(CustomerController.class)
#ImportAutoConfiguration(classes = {RequestInformationExtractor.class})
#ContextConfiguration(classes = SecurityConfiguration.class)
#Import({TestConfiguration.class, CustomerController.class})
public class PartnerControllerTest {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
// runs successfully
#Test
void shouldReturnListOfCustomers() throws Exception {
mockMvc.perform(
post("/search")
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"searchKeyword\": \"Mustermann\"" +
"}")
.with(jwt()
.authorities(
new SimpleGrantedAuthority("basic")
)))
.andExpect(status().isOk());
}
// fails: expect 401 but got 403
#Test
void shouldReturn401WithoutJwt() throws Exception {
mockMvc.perform(
post("/search")
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"searchKeyword\": \"Mustermann\"" +
"}"))
.andExpect(status().isUnauthorized());
}
// runs successfully
#Test
void shouldReturn403() throws Exception {
mockMvc.perform(
post("/search")
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"searchKeyword\": \"Mustermann\"" +
"}")
.with(jwt()))
.andExpect(status().isForbidden());
}
}
#org.springframework.boot.test.context.TestConfiguration
public class TestConfiguration {
#Bean
public JwtDecoder jwtDecoder() {
SecretKey secretKey = new SecretKeySpec("dasdasdasdfgsg9423942342394239492349fsd9fsd9fsdfjkldasd".getBytes(), JWSAlgorithm.HS256.getName());
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build();
return jwtDecoder;
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/actuator/**").permitAll()
.anyRequest().hasAuthority("Basic")
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthoritiesClaimName("groups");
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
You probably have a 403 because an exception is thrown before access control is evaluated (CORS or CSRF or something).
For instance, in your security configuration, you disable sessions (session-creation policy to stateless) but not CSRF protection.
Either disable CSRF in your conf (you can because CSRF attacks use sessions) or use MockMvc csrf() post-processor in your tests.
I have many demos of resource-servers with security configuration and tests (unit and integration) in my samples and tutorials. Most have references to my test annotations and boot starters (which enable to define almost all security conf from properties without Java conf), but this one is using nothing from my extensions. You should find useful tips for your security conf and tests there.

Securing Spring Cloud Gateway with Spring Security

I am struggling with configuring security for my Spring Cloud Gateway service.
For now i have configured in my api-gateway just one route to user service /api/v1/users. Requests are correctly routed to user service untill I add Spring Security to the dependescies.
Even with that simple config, that should allow all traffic, I am still getting 401 Unathorized response:
#Configuration
#EnableWebFluxSecurity
class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) {
return serverHttpSecurity
.authorizeExchange()
.anyExchange().permitAll().and()
.csrf().disable()
.build();
}
}
What am I doing wrong?
You need to create user to do that. See the sample attached in below. I am using in-memory user to authenticate. Note in-memory user is just for testing purpose only.
#Configuration
public class InMemoryUserSecurityAdapter {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/school-library-service/**").authenticated()
.and().authenticationManager(reactiveAuthenticationManager())
.authorizeExchange().anyExchange().permitAll().and()
.httpBasic().and()
.build();
}
#Bean
ReactiveAuthenticationManager reactiveAuthenticationManager(){
return new UserDetailsRepositoryReactiveAuthenticationManager(getInMemoryUserDetails());
}
#Bean
public MapReactiveUserDetailsService getInMemoryUserDetails() {
UserDetails admin = User.withDefaultPasswordEncoder().username("admin1").password("password")
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(admin);
}
}
https://github.com/DeepuGeorgeJacob/school-management/blob/main/security/in-memory-user-security/src/main/java/com/school/management/config/InMemoryUserSecurityAdapter.java
Happy coding :)

Spring Cloud Gateway - Intercept under hood request/response to Keycloak IDP

We are implementing a Spring Cloud Gateway application (with Webflux) that is mediating the OAuth2 authentication with Keycloak.
SCG checks if the Spring Session is active: if not, redirects to Keycloak login page and handles the response from the IDP. This process is executed out-of-the-box by the framework itself.
Our needs is to intercept the IDP Keycloak response in order to retrieve a field from the response payload.
Do you have any advices that will help us to accomplish this behavior?
Thanks!
You can implement ServerAuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
private ServerRedirectStrategy redirectStrategy;
public AuthenticationSuccessHandler(AuthenticationService authenticationService) {
redirectStrategy = new DefaultServerRedirectStrategy();
}
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
if(authentication instanceof OAuth2AuthenticationToken) {
//Your logic here to retrieve oauth2 user info
}
ServerWebExchange exchange = webFilterExchange.getExchange();
URI location = URI.create(httpRequest.getURI().getHost());
return redirectStrategy.sendRedirect(exchange, location);
}
}
And update your security configuration to include success handler:
#Configuration
public class SecurityConfiguration {
private AuthenticationSuccessHandler authSuccessHandler;
public SecurityConfiguration(AuthenticationSuccessHandler authSuccessHandler) {
this.authSuccessHandler = authSuccessHandler;
}
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
//other security configs
.anyExchange().authenticated()
.and()
.oauth2Login(oauth2 -> oauth2
.authenticationSuccessHandler(authSuccessHandler)
);
return http.build();
}
}

Different authentication on GET and POST

I've been following a spring security example but I cannot make sense of it. A simple RestController replying hello on a GetMapping("/hello") with a 200 status code. Once I change it to a PostMapping I receive a 401 for the same credentials sent.
Seems I am missing something fundamental here as I would expect both requests to return a 200 status code.
The security config:
#Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
List.of(
User.withUsername("john")
.password("12345")
.authorities("ROLE_ADMIN")
.build(),
User.withUsername("jane")
.password("12345")
.authorities("ROLE_MANAGER")
.build()
)
);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.hasRole("ADMIN");
}
}
The RestController with the following get mapping returns 200 for this call:
curl -v -u john:12345 localhost:8080/hello
and this mapping:
#RestController
public class HelloController {
#GetMapping("/hello")
public String hello() {
return "Hello!";
}
}
The RestController with the following post mapping returns 401 for this call:
curl -X POST -v -u john:12345 localhost:8080/hello
and this mapping:
#RestController
public class HelloController {
#PostMapping("/hello")
public String hello() {
return "Hello!";
}
}
Spring's CSRF protection comes enabled by default in Spring Security. POST requests are affected by this behavior.
Disable it by doing:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
As pointed out by #Matheus Cirillo, CSRF protection is enabled by default.
It's tempting to want to disable CSRF protection since we're using a restful api, but consider what happens if you're using a browser-based single page application to interact with the server. The same authenticated session is still available in the browser, and the application is still vulnerable to a CSRF attack.
You can find some examples of how to work with csrf protection in your own application in the docs. In a restful api, you can also provide an endpoint that returns the csrf token in a header or a response parameter.

Run a Spring Boot oAuth2 application as resource server AND serving web content

I'm using Spring Boot 1.5.13 and with that Spring Security 4.2.6 and Spring Security oAuth2 2.0.15.
I want to find a best practice setup for our Spring Boot applications that serve a mixed set of content: A REST API, and some web pages that provide a convenience "landing page" for developers with some links on it, plus Swagger based API documentation, which is also web content.
I have a configuration that allows me to run the app with proper authorization code flow, hence I can access all web content via Browser and get authenticated by the configured IdP (in my case PingFederate), plus I can make API calls from within the Browser, i.e. directly or with a REST Client, e.g. with RESTClient.
This is my security configuration:
#Slf4j
#Configuration
#EnableWebSecurity
#EnableOAuth2Sso // this annotation must stay here!
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login**", "/webjars/**", "/css/**").permitAll()
.antMatchers("/cfhealth").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/protected", "/api/**").authenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
and the oAuth2 configuration:
#Configuration
#Slf4j
public class OAuth2Config extends ResourceServerConfigurerAdapter {
#Value("${pingfederate.pk-uri}")
String pingFederatePublicKeyUri;
#Autowired
PingFederateKeyUtils pingFederateKeyUtils;
#Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String certificate = pingFederateKeyUtils.getKeyFromServer(pingFederatePublicKeyUri);
String publicKey = pingFederateKeyUtils.extractPublicKey(certificate);
converter.setVerifier(pingFederateKeyUtils.createSignatureVerifier(publicKey));
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
But when I want to call a REST API programmatically/outside the Browser with a bearer token in the header, e.g. with curl, the authorization code flow kicks in and redirects to the local login endpoint. What I want is that API calls accept the bearer token for authentication, without creating a session, and that all web content/mvc calls in the Browser establish a session.
curl -i -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8080/authdemo/api/hello
Adding the #EnableResourceServer annotation to the above SecurityConfig class (and adding security.oauth2.resource.filter-order=3 in the application properties file, I can make the curl command work, but then the authorization code flow is broken, I get the following output in the Browser for all URLs in my application:
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
Now is there a way to get this szenario working nicely? If yes, how would that look like? Or is it only supported in later versions of Spring Boot+Security+oAuth2?
The question at Spring Boot with Security OAuth2 - how to use resource server with web login form? is quite similar
I found the solution: It takes multiple HttpSecurity configurations. I found out by reading the great article written by Matt Raible at https://developer.okta.com/blog/2018/02/13/secure-spring-microservices-with-oauth where he introduced me to the notion of requestMatchers(.). This is how I finally implemented it:
#Configuration
#EnableResourceServer
#EnableWebSecurity(debug = true)
#EnableOAuth2Sso
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization"))
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
With that I can access the service with a Browser, leading to a authorization code flow. But accessing the API (or actually any part of the service) leads to a validation of the provided Bearer token.
And to illustrate the way how some endpoints can be exluded/made public in such a case, here's how I configure the actuator endpoints and one very simple 'ping' endpoint I've added myself:
#Configuration
#Order(1)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OrRequestMatcher(EndpointRequest.to("health", "info"),
new AntPathRequestMatcher("/cfhealth"))).authorizeRequests().anyRequest().permitAll();
}
}
And my implementation of the /cfhealth endpoint:
#Controller
#Slf4j
public class MainController {
#GetMapping(value = "/cfhealth")
#ResponseBody
public String cfhealth() {
return "ok";
}
}
I'm happy to learn from others if that's the best practice way of Spring Security configuration or if there are better ways to do it. I've spent quite some time on the topic in the last few weeks on it, and it takes quite some effort to grasp the basic Spring Security concepts.

Resources