Spring Security: don't redirect to login page in case unauthorised - spring

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

Related

Oauth2 security configuration antmatchers request filtering not working as expected

I am working on a simple spring boot project along with spring security oauth2 to use google authentication for a specified endpoint which is /google/login.
With following security configurations everything is working perfectly.
#Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests().antMatchers("/ldap/login").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}
}
But I need to specify only /google/login endpoint to authenticate with oauth2. Therefore I specified it like this.
#Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/google/**")
.and()
.authorizeRequests().antMatchers("/ldap/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}
}
with this security configuration http://localhost:8080/google/login endpoint call redirects to another endpoint called http://localhost:8081/oauth2/authorization/google which is I haven't defined.
Please help me to overcome this problem. Thank you.
This configuration works for me. I had to allow all endpoints that were redirecting while Google's authentication process was running. 
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers("/google/**","/oauth2/authorization/google","/login/oauth2/code/google")
.and()
.authorizeRequests().antMatchers("/ldap/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}

Spring security - create 2 filter chains with specific matchers

I'm in the process of implementing ADFS support to an existing spring project.
Since we already have our own JWT authentication, which we want to work in parallel to ADFS authentication, I want to implement a new filter chain that will handle only certain API request paths.
By this I mean I want to create:
ADFS filter chain that will handle all the /adfs/saml/** API calls
Leave the default filter chain that will handle all the rest API calls
I'm using the ADFS spring security lib that defines the filter chain like this:
public abstract class SAMLWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//some code
protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(samlEntryPoint())
.and()
.csrf().ignoringAntMatchers("/saml/**")
.and()
.authorizeRequests().antMatchers("/saml/**").permitAll()
.and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
// store CSRF token in cookie
if (samlConfigBean().getStoreCsrfTokenInCookie()) {
http.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
return http;
}
}
And I extend this class:
#EnableWebSecurity
#Configuration
#Order(15)
#RequiredArgsConstructor
public class ADFSSecurityConfiguration extends SAMLWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.authorizeRequests()
.antMatchers("/adfs")
.authenticated();
}
}
But when debugging I see that this new filter chain is set to match "any" request.
So I'm probably setting the matchers wrong.
Actually, after reading the official docs the answer was a simple one:
(see "Creating and Customizing Filter Chains" section)
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.antMatcher("/adfs/**");
}
It should not be put after .authorizeRequests() but strait on the first matcher.

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

Providing custom OAuth2AccessTokenResponseClient to spring

I have a demo spring boot app and I would like to configure oauth login as well my own custom token response client.
This is my configuration:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.authorizationCodeGrant()
.accessTokenResponseClient(customAccessTokenResponseClient());
}
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRequestEntityConverter(new CustomOAuth2AuthorizationCodeGrantRequestEntityConverter());
return client;
}
}
The login flow is working find, but it looks like my custom token response client is not getting registered. I put a break point in there, but the flow doesn't go through it. It looks like it is still using the defaults.
Am I missing something here or is the order wrong?
Figured out the issue.
The right way to configure is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(customAccessTokenResponseClient());
}

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.

Resources