Spring Boot + Spring Security permitAll() and addFilter() configuration does not have effect - spring

URL patter with /login should go through the LoginFilter where the user id and password is validated - working fine
URL pattern with /users/register should not go through any of the filer but it is always passing through the JWTAuthentication filter - not working fine
All other URL pattern should go through the JWTAuthentication filter for authorization - working fine
Below is my code for Security Configuration. Kindly help me with what I am missing in this code. How do I configure the filter such that JWT authentication happens for the URL pattern other than /login and /register
Spring-security-core:4.2.3, spring-boot:1.5.4
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/users/register").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new LoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new NoLoginAuthenticationFilter("/users/register"), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter("/**", authenticationManager()),
UsernamePasswordAuthenticationFilter.class);
}

What you want is to ignore certain URLs.
For this override the configure method that takes WebSecurity object and ignore the pattern.
Try adding below method override to your config class.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/users/register/**");
}

Related

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.

Combination of HTTP Filter not working with ignore() method from WebSecurity

I have made Filter which is added like that in Spring Security:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/", "/#/", "resources/static/**").permitAll().anyRequest().permitAll()
.antMatchers(HttpMethod.POST, "/api/auth").permitAll().anyRequest().permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/api/auth", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
Unfortunately request /api/auth has no controller in my Spring Boot app. It only works in filter. So the frontend hit this api to authenticate yourself.
In my app I also had to add
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/static/**").anyRequest();
}
to ignore static files because the first method doesn't exclude it properly. And the ignore() cause the problem because it calls /api/auth which has no equivalent in backend but only exists in .js files. What is more ignore() method ignore all requests from the path in which /api/auth is - this api returns 404 when I want to call when ignore() method is uncommented. So the question is how to make a work around to make it work properly?

Spring Security permitAll() for one URL under some pattern

I have /my-app/login url and I want to permitAll() for this URL. But this page under /my-app/** pattern which permit access only for registered user.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/my-app/**").access("hasRole('USER')")
.and()
.httpBasic()
.authenticationEntryPoint(entryPoint());
}
How to do it?
Add .antMatchers("/my-app/login").permitAll() BEFORE .antMatchers("/my-app/**")... . The request matchers are stored in an list (ordered by the order in which they are defined), and Spring security will use the first rule thats matchers match to the current request. So put the most specific first and the common rules afterwards.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/my-app/login").permitAll()
.antMatchers("/my-app/**").access("hasRole('USER')")
.and()
.httpBasic()
.authenticationEntryPoint(entryPoint());
}
If my-app is the name of your application, and therefore the url that is by your application server (Tomcat) to map the url to the application, then you have to ommit it in the antMatcher because the antMatcher is configured "only" by the application relative url: /my-app/login become /login and /my-app/** become /**
add .anyRequest().permitAll() as last "matcher" of authorizeRequests()
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/my-app/**").access("hasRole('USER')")
.anyRequest().permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(entryPoint());
}
But to be honest: you use some kind of black listing (allow all URLs except some black listed) - and this is not the recommended way (from some security perspective). Because if you forget to add or misspell one URL that should been protected, then it is accessible for every body. The more secure way is to deny every url and allow only some (white list).

Mix HttpBasic and FormLogin in Spring Security with Spring-boot-starter

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");
}
}

Resources