Authentication with spring and web flux - spring

I have a question concerning spring and web flux.
I have a spring project with spring security and MVC as dependencies.
This application accepts requests and check authentication using the session cookie.
For all the requests starting with "/api/" a failed authentication results in a 401 response, so that can be intercepted by the frontend as such.
For all the requests different from "/api/**" a failed authentication results in the server returning a login page so that the user can login.
This is the SecuritConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
.and()
.cors();
}
}
Now, I am trying to achieve the same thing using web flux. With web flux the SecurityConfig is different, I can setup almost all the configs that I have in the old class but there is no equivalent for:
defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
My new security config look like:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/login/**")
.permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.build();
}
}
But in this case I only get 401 for all the requests that fail authentication.
Does anybody know how to achieve the same behavior with web flux?
Thank you

Related

Two login authentication ways in Spring Boot

I need develop an app with two authentication endpoints: one a login web form and other sending credentials via custom token.
I create two WebSecurityConfigurerAdapter and the login forms work perfectly but the token not: When I tried to identify via token, it run ok but always redirect to de login form page.
This is my configuration:
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(), CustomAuthenticationFilter.class)
.authorizeRequests()
.mvcMatchers(PublicUrls.URLS).permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.cors()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
.. and the token configuration:
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.mvcMatcher(LOGINJWT)
.addFilterBefore(authenticationFilter(), WebAsyncManagerIntegrationFilter.class)
.authorizeRequests()
.antMatchers(LOGINJWT).permitAll()
.anyRequest().fullyAuthenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
// #formatter:on
}
When I trie to authenticate via token, it run the customFilter, and the custom authentication provider correctly but always redirect to login page.
The classes order annotation are this:
// Token annotation class
#Configuration
#Order(1)
#EnableWebSecurity
public class JwtWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {....}
//login annotation clas
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Slf4j
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {...}
I don't see the problem.
I found the problem: the JWT filter is executing before WebAsyncManagerIntegrationFilter.

Users are able to access all endpoints after setting antMachers in Spring Security

I'm developing a spring-boot application and its spring security configuration is as follows:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/actuator/**", "/login*", "/logout*")
.permitAll();
httpSecurity
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/taas/v1/**").hasRole("admin")
.antMatchers("/taas/v1/teams", "/taas/v1/profiles", "/taas/v1/tests/summary").hasRole("tester")
.antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary").hasRole("user")
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
.and()
.httpBasic()
.and()
.formLogin()
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.and()
.logout()
.logoutSuccessHandler(customLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
}
Even though i have set the url pattern for each roles. All users are able to access all endpoints as mentioned in antMatchers(). A user with role user is not supposed to access /taas/v1/profiles. But when I try to access that endpoint by logging in as user, I'm getting the response but expected response is 403 forbidden.
I request someone to provide a workaround for me.
I got this issue resolved by doing some minor changes in my antMatchers(). Below is the modified code.
The main issue is that antMatcher() pattern must not contain the context path, see Spring security antMatcher does not work
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/profiles").hasAnyRole("TESTER")
.antMatchers( "/teams", "/tests/summary").hasAnyRole("USER", "TESTER", "ADMIN")
.anyRequest().authenticated()
.and().csrf().disable()
.exceptionHandling()
.accessDeniedHandler(customAccessDeniedHandler)
.and()
.httpBasic()
.and()
.formLogin()
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.and()
.sessionManagement()
.invalidSessionUrl("/invalidSession.html")
.maximumSessions(1).sessionRegistry(sessionRegistry()).and()
.sessionFixation().none()
.and()
.logout()
.logoutSuccessHandler(customLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
}
Please verify the code that you're sharing because as you've mentioned. A user with role user is not supposed to access /ptaas/v1/profiles. But when I try to access that endpoint by logging in as user.
Where your mapping says you've not configured access to user role as given.
.antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary").hasRole("user")
As per your comments it should have been .antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary", "/ptaas/v1/profiles").hasRole("user")

How to make a custom UsernamePasswordAuthenticationFilter register at an endpoint other than /login?

I've been following a tutorial to implementing JWT authentication in Spring Boot but am trying to adapt it to a case where I have two WebSecurityConfigurerAdapter classes, one for my API (/api/** endpoints) and one for my web front-end (all other endpoints). In the tutorial, a JWTAuthenticationFilter is created as a subclass of UsernamePasswordAuthenticationFilter and added to the chain. According to the author, this filter will automatically register itself with the "/login" endpoint, but I want it to point somewhere different, such as "/api/login" because I'm using this authentication method for my API only.
Here's the security configuration code for both the API and front-end (with some abbrevation):
#EnableWebSecurity
public class MultipleSecurityConfigurations {
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
;
}
}
}
The question is: how can I define an endpoint such as "/api/login" as the endpoint for my custom JWTAuthenticationFilter?
Or, do I need to change the filter to not be a subclass of UsernamePasswordAuthenticationFilter and if so, how would I configure that?
EDIT: Something I've tried:
I guessed that the /api/login endpoint needed to be .permitAll() and I tried using formLogin().loginProcessingUrl(), even though it's not really a form login - it's a JSON login. This doesn't work. When i POST to /api/login I end up getting redirected to the HTML login form as if I were not logged in. Moreover, my Spring boot app throws a weird exception:
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
The configuration I'm trying now:
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().loginProcessingUrl("/api/login").and()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
Since JWTAuthenticationFilter is a UsernamePasswordAuthenticationFilter, you could change the login endpoint directly on the filter instance:
JWTAuthenticationFilter customFilter = new JWTAuthenticationFilter(authenticationManager());
customFilter.setFilterProcessesUrl("/api/login");
http.addFilter(customFilter);
This configures JWTAuthenticationFilter to attempt to authenticate POST requests to /api/login.
If you wish also to change the default POST to another method (e.g. GET), you can set the RequiresAuthenticationRequestMatcher instead. For instance:
customFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/login", "GET"));

Spring Boot Security - Multiple configurations

I'm working (and struggling a little bit) on an example using spring-boot with spring security.
My system is using a web app and also provide an REST-API, so i would like to have form based security (web) and basic auth (resp api).
As the spring documentation recommend (https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity), I need to create a multi http web security configuration.
The main code works, but if I use Postman for the test of my RestApi following use-case does not work.
All GET-requests to /restapi/ working without authentication (statuscode 200)
All POST-requests to /restapi/ without the BASIC Auth Header are working (statuscode 401)
All POST-requests to /restapi/ with a correct BASIC Auth Header are work (statuscode 200)
BUT all requests with a wrong BASIC Auth header (f.e. user1/1234567) are returning the HTML-Loginpage defined in the first WebSecurityConfigurerAdapter (FormWebSecurityConfigurerAdapter)
Does anyone has an idea - what is wrong with my configuration?
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
private static RestAuthenticationAccessDeniedHandler restAccessDeniedHandler;
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}12345678").roles("ADMIN").and()
.withUser("user").password("{noop}12345678").roles("USER");
}
#Configuration
#Order(1)
public static class RestWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/restapi/**")
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/restapi/**").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(UNAUTHORIZED))
.and()
.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) ;
}
}
/*
Ensures that any request to our application requires the user to be authenticated (execpt home page)
Requests matched against "/css/**", "/img/**", "/js/**", "/index.html", "/" are fully accessible
Allows users to authenticate with HTTP Form Based authentication
Configure logout with redirect to homepage
*/
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**", "/index.html", "/").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index.html")
.permitAll();
}
}
}
I know it is a question from some time ago but I still want to share the answer for people who are struggling with this issue.
After a lot of searching I found out that the /error endpoint in spring boot 2.x is now secured by default. What I mean to say is in the past the /error was a endpoint what had no security at all (or didn't exist). The solution to this issue is quite straight forward.
antMatchers('/error').permitAll()
within your web security adapter configuration(s).
What happens if you don't do this, the security will check the endpoint against your configuration and if it cannot find this endpoint (/error) it will redirect to the standard login form, hence the 302.

Restrict access to Swagger UI

I have swagger UI working with spring-boot. I have a stateless authentication setup for my spring rest api which is restricted based on roles for every api path.
However, I am not sure how can i put <server_url>/swagger-ui.html behind Basic authentication.
UPDATE
I have following websecurity configured via WebSecurityConfig
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/sysadmin/**").hasRole("SYSADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/siteadmin/**").hasRole("SITEADMIN")
.antMatchers("/api/**").hasRole("USER")
.anyRequest().permitAll();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
One suggestion without knowing more about your configuration is from this SO question.
https://stackoverflow.com/a/24920752/1499549
With your updated question details here is an example of what you can add:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/sysadmin/**").hasRole("SYSADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/siteadmin/**").hasRole("SITEADMIN")
.antMatchers("/api/**").hasRole("USER")
// add the specific swagger page to the security
.antMatchers("/swagger-ui.html").hasRole("USER")
.anyRequest().permitAll();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
The problem with this is it only protects the Swagger UI page and not the API specification which is loaded as a .json file from that UI page.
A better approach is to put the swagger files under a path so that you can just add antMatchers("/swagger/**").hasRole("USER")
A bit late to answer. I carried out a small POC to execute the same. I am using Keycloak and Spring Security. Below is my configuration
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/swagger-resources/**","/swagger-ui.html**","/swagger-ui/").hasRole("admin")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.defaultAuthenticationEntryPointFor(authenticationEntryPoint(), new CustomRequestMatcher(AUTH_LIST))
.and()
.httpBasic()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.clearAuthentication(true)
.addLogoutHandler(keycloakLogoutHandler());
I have a working example here

Resources