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.
Related
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
I have Spring Security with oAuth2 authorisation.
I use it for REST API.
My configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/health").permitAll()
.antMatchers("/**").authenticated()
.and()
.oauth2Login()
.and()
.httpBasic();
}
}
I need to make all requests return me 401 when I didn't authorise.
But now when I'm not authorised I got redirect to /login page.
I need to use it like usual REST API: if I did authorise then get content, otherwise get 401 Unauthorised.
How I can make it?
Thanks in addition for help.
Basically you need to configure an AuthenticationEntryPoint which is invoked when Spring Security detects a non-authenticated request. Spring also gives you a handy implementation which enables you to return whatever HttpStatus you need:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//rest of your config...
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
I have REST services and static pages, both delivered by my Spring Boot application.
The application action is to be reached under /myapp, while under /myapp/api there are services that are protected by a filter.
The filter expects a cookie.
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.permitAll()
.and()
.addFilter(new CookieFilter(...));
}
The unprotected pages in the 'root-context (/myapp) have no cookie.
How can I configure that the static pages are 'ignored' by Spring security? While the REST endpoints below the static pages are checked by security?
When I try to configure the static pages for 'exclusion' via Web Security, all REST endpoints under /myapp/api are ignored as well
#Override
public void configure(final WebSecurity web) {
web.ignoring()
.antMatchers("/");
}
If I configure a permitAll():
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll();
http.authorizeRequests()
.antMatchers("/api/**")
.permitAll()
.and()
.addFilter(new CookieFilter(...));
}
Spring security complains that the static pages can not be checked by my security filter, so the authorization is performed.
This will make anything under /api require authentication and let everything else through.
http.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilter(new CookieFilter(...));
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"));
I use spring-boot-starter 0.5.0.M6 with spring security to build my application which contains:
"/admin/"**: should be accessible to anyone have role ADMIN, form-based login
"/api/"**: should be accessible to anyone have role API, http basic login
My first attempt was:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.defaultSuccessUrl("/admin/home")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.permitAll();
http
.authorizeRequests()
.antMatchers("/api/**").hasRole("API")
.and()
.httpBasic();
}
With this approach:
all the "/admin/" and "/api/" can authentication use both basic & form-based login. This is not a critical issue.
when any security issue occurred, eg: authentication failed, or authorization failed, the login form is shown. This is a critical issue, I want if /api/** get authentication failed or authorization failed, it show the basic authentication popup with 401/403 status code.
Then I try with the solution from https://github.com/spring-projects/spring-security-javaconfig/blob/master/samples-web.md#sample-multi-http-web-configuration, but I only able to secure either /api/** or /admin/** but not both, depends on which one I annotated with #Order.
Please give me a hand.
Thanks much
For your api part, use the following. Note the first ant matcher that limits the scope of what is filtered by this security configuration. That was the part I did not understand at first from your reference.
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// the ant matcher is what limits the scope of this configuration.
http.antMatcher("/api/**").authorizeRequests()
.antMatchers("/api/**").authenticated()
.and().httpBasic().realmName("Sourcing API");
}
}