Disable BasicAuth on specific sub paths - java

I know there are some question on stackoverflow, but nothing helped...
http
.addFilterBefore(RestConfiguration.getCorsFilter(), ChannelProcessingFilter.class)
.authorizeRequests() //Authorize Request Configuration
.antMatchers("/api/**").hasRole("API")
.antMatchers("/api/confirm/**").permitAll()
.antMatchers("/api/version").permitAll()
.anyRequest().authenticated()
.and() //HTTP basic Authentication only for API
.antMatcher("/api/**").httpBasic()
.and() // angularjs requires csrf
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().disable();
I've a api path with some sub paths. But I want to access two of them without basic auth (confirm/** and version).
How can I do that? I always get the login dialog.

You should use antMatchers("/api/**").hasRole("API") rule after /api/confirm/** and /api/version. Current ordering is not quite right:
.antMatchers("/api/confirm/**").permitAll()
.antMatchers("/api/version").permitAll()
.antMatchers("/api/**").hasRole("API")
To sum up, if you want to only secure /api/* and let public access on /api/confirm/**, /api/version and all other paths without api prefix, you should have a HttpSecurity config like this:
http
// Same as before
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/api/confirm/**").permitAll()
.antMatchers("/api/version").permitAll()
.antMatchers("/api/**").hasRole("API")
.anyRequest().permitAll();
// Same as before

Related

Spring security configuration; both Basic Auth and SiteMinder

I have a Spring boot web app that serves up both web content and exposes REST Services. The web content is protected by SiteMinder, the REST Services are protected by "Basic Auth".
I using Springs security 4.2.3. My Java code is extending the class WebSecurityConfigurerAdapter, my configure(HttpSecurity) method looks like:
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestHeaderAuthenticationFilter siteMinderFilter = new RequestHeaderAuthenticationFilter();
siteMinderFilter.setAuthenticationManager(authenticationManager());
http
// Error page is for everyone
.authorizeRequests()
.antMatchers("/error.html")
.permitAll()
.anyRequest()
.anonymous()
// Basic Auth for our REST services
.and()
.authorizeRequests()
.antMatchers("/services/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
// Site-Minder protection for the web content
.and()
.addFilter(siteMinderFilter)
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().hasRole(ApplicationConstants.SITE_MINDER_AUTHORITY);
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class);
}
Is there something wrong with my configuration? Does my configuration create three separate filters? Maybe my question should be, how do I create the three filters?
When I attempt to call the REST Service using PostMan / "Basic Auth", I get the error message:
org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException: SM_USER header not found in request.
I expect the service to get called, instead I get the SiteMinder filter firing.

Spring Security applies filter to all paths, not only "/api**"

I am struggling to get my Spring Security config done properly.
I have JWT security set up, but I want it to work only on /api** and I can't get it right... Even when I try to hit localhost:8080 I am getting error from JWTFilter.
This is my config:
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
.exceptionHandling()
.authenticationEntryPoint(this.restAuthEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "**").permitAll()
.antMatchers(FORM_BASED_REGISTRATION_ENTRY_POINT).permitAll()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.and()
.addFilterBefore(buildJWTLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJWTAuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
Theoretically it should apply filters only on API, but it somehow applies them on all paths.
Can someone help to make it working as it should work, so only /api** will be secured and I can freely access all paths outside /api ?
Add and().antMatchers("/**").permitAll()

Spring Security 404 page for unauthenticated users

I'm using Spring Boot and Thymeleaf. I have a custom 404 template page defined in src/main/resources/templates/error/404.html
This works properly when users are logged in.
However, when they are logged out, they do not get any type of 404 page, they just get redirected back to /login.
I'm thinking my security configuration needs to change but not sure what.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/register*","/resetPassword","/forgotPassword","/login","/404").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/login").failureUrl("/login?error")
.defaultSuccessUrl("/dashboard").successHandler(successHandler)
.usernameParameter("email").passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login?logout").and()
.exceptionHandling().accessDeniedPage("/access-denied");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/error**","/resources/**", "/static/**", "/css/**", "/js/**", "/img/**");
}
First of all I encourage you to use indentation when using java config to configure your security for your spring application. It helps with readability.
Note all top level methods on the first indentation (authRequest,formLogin,logout) all configure/update the HTTP object it self. All these elements are from the org.springframework.security.config.annotation.web.builders.HttpSecurity class.
The children of these classes further refine the HTTP security configuration.
http
.authorizeRequests()
.antMatchers("/","/register*","/resetPassword","/forgotPassword","/login","/404")
.permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated() // <--------
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/dashboard")
.usernameParameter("email").passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied");
Note .anyRequest().authenticated() is specifically stating that any request must be authenticated. So when you attempt to goto any missing url on your domain it will ask you to login rather than goto the 404 page.
So if you remove that statement it and then try an goto a missing url page it will redirect you to a 404 page.
If you remove .anyRequest().Authenticated() then you can log in without authenticated.
Therefore, do not try to delete. For example, if you go to the address "http://localhost:8080/user", then you will be taken to the authorization page. And if you try to enter the page "http://localhost:8080/user/" then you will be taken to the user page. Please note that links differ only by the forward slash at the end. Of course if you remove ".anyRequest().Authenticated()" in this case you need to add more parameters to antMatchers like "/user" and "/user/"
Therefore, be careful and attentive.

X-CSRF-TOKEN is not generated by Spring Boot

I followed the guide here: http://spring.io/guides/gs/rest-service/ to build my rest service example and now I am trying to enable the CSRF protection. I read that it should be enabled by default, so if I DON'T include:
http.csrf().disable()
in my WebSecurityConfigurerAdapter configuration, the CSRF protectection should be enabled by default, but it does not seem to to be the case. The problem is that the X-CSRF-TOKEN is not generated and not included in my HTTP response in any way.
What am I expected to do, to have the x-csrf-token generated and included in the response and, of course, the csrf protection fully working?
I noticed that, with a similar spring mvc configuration, I get the x-csrf-token generated simply including:
< security:csrf disabled="false"/>
in my security configuration file. But, with spring boot maybe I am getting something wrong and there is no way to have the csrf token generated. Can anybody help me, perhaps pointing me to a working example? My security configuration is:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
// .csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(new RestAuthenticationEntryPoint())
.and()
.formLogin()
.successHandler(new RestAuthenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.and()
.logout()
.logoutSuccessHandler(new RestLogoutSuccessHandler());
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(restUserDetailService);
}
To include the CSRF Token in your csrf protection, you can include CSRFTokenRepository to generate tokens. To illustrate in your case adding a simple line is enough:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //HERE ! Defaults XSRF-TOKEN as cookie name and X-XSRF-TOKEN as header name
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(new RestAuthenticationEntryPoint())
.and()
.formLogin()
.successHandler(new RestAuthenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.and()
.logout()
.logoutSuccessHandler(new RestLogoutSuccessHandler());}
Using Spring security 5.3.0.Final, one of the ways you can generate the CSRF token is by setting it in the cookie using the following code below.
http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
You also need to include the generated CSRF token in your request for the server to authorize.
<form>
<input type="hidden" name="_csrf" value="${cookie['XSRF-TOKEN'].getValue()}" />
//Code goes here
</form>
In the event you're using a JS framework, you need to include the token by setting it in the request header.
Here is an example for a JQuery ajax call.
// Get the CSRF token from the cookie
const csrfCookie= document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
// Add the CSRF token to each ajax request header
settings.beforeSend = function(xhr) {
xhr.setRequestHeader('X-XSRF-TOKEN', springCsrfCookie);
};
$.ajax(settings);
There are other implementations that will suit your needs documented in the following link by Spring | https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#servlet-csrf
We had pretty similar issue during our security tests where we suspected that we accidentally disable csfr in configure method of websecurityconfig class,by default it is enabled. by changing the congfigure method as shown below , we had spring automatically generate csfr tokens.
websecurityconfig class configure method==>
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login","/loginError","/home","/interruption").permitAll()
.antMatchers("/admin").hasAuthority(Roles.ROLE_PREFIX.role()+Roles.HALLEYYNT01.role())
.antMatchers("/requests").hasAuthority(Roles.ROLE_PREFIX.role()+Roles.CCHALLEYLOGIN.role())
.antMatchers("/solrequests").hasAuthority(Roles.ROLE_PREFIX.role()+Roles.SOLHALLEYLOGIN.role())
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
//.failureUrl("/loginError")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.and()
.logout().clearAuthentication(true).invalidateHttpSession(true).deleteCookies("JSESSIONID")
.logoutSuccessUrl("/login");
//.and()
//.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}

In addition to token based authentication, allow Rest api endpoint with http basic auth

I have a recently created jhipster application with the following .yo-rc.json
{
"generator-jhipster": {
"baseName": "cmpayments",
"packageName": "au.com.cmx.myapp",
"packageFolder": "au/com/cmx/myapp",
"authenticationType": "token",
"hibernateCache": "no",
"clusteredHttpSession": "no",
"websocket": "no",
"databaseType": "sql",
"devDatabaseType": "postgresql",
"prodDatabaseType": "postgresql",
"useCompass": false,
"buildTool": "maven",
"frontendBuilder": "gulp",
"javaVersion": "8"
}
}
I like having the token based authentication on the webapp but I'd like the server to expose a REST api call with just http basic authentication. I've been battling with for a while but I'm completely new to Spring security and I'm hoping someone has already done this and can help me out.
I tried following the solution here:
Basic and form based authentication with Spring security Javaconfig
I created a second configuration with #Order(1) in SecurityConfiguration.java like so
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("api").password("pass").roles("API");
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/basicAuthApi/**").hasRole("API")
.and()
.httpBasic();
}
}
This works. If I hit an endpoint under /basicAuthApi with anything other than api/pass credentials, I get a 401. Yay.
However, after this, when I log in to the webapp as admin/admin (or user/user), I get logged in as anonymousUser. If I comment out the extra #Configuration in SecurityConfiguration.java and restart the app, that problem goes away and I get logged in correctly as admin (or user).
Interestingly, I tried changing the order of the second #Configuration to #Order(101) because I saw somewhere in one of the base classes an #Order(100). In this case the admin and user logins on the webapp work. But the rest api call is no longer secure i.e it succeeds even with incorrect password.
Does anyone know what I am doing wrong?
Thanks
dalyc
Replace the original SecurityConfiguration.configure :
http
.csrf()
.ignoringAntMatchers("/websocket/**")
.and()
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.rememberMeParameter("remember-me")
.key(env.getProperty("jhipster.security.rememberme.key"))
.and()
.formLogin()
.loginProcessingUrl("/api/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset_password/init").permitAll()
.antMatchers("/api/account/reset_password/finish").permitAll()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
by this one :
http
.csrf()
.ignoringAntMatchers("/websocket/**")
.and()
.csrf()
.ignoringAntMatchers("/basicAuthApi/**")
.and()
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.rememberMeParameter("remember-me")
.key(env.getProperty("jhipster.security.rememberme.key"))
.and()
.formLogin()
.loginProcessingUrl("/api/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset_password/init").permitAll()
.antMatchers("/api/account/reset_password/finish").permitAll()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated()
.and()
.authorizeRequests()
.antMatchers("/basicAuthApi/**")
.hasAuthority(AuthoritiesConstants.USER).and().httpBasic();
I've only added :
.and()
.csrf()
.ignoringAntMatchers("/basicAuthApi/**")
and :
.and()
.authorizeRequests()
.antMatchers("/basicAuthApi/**")
.hasAuthority(AuthoritiesConstants.USER).and().httpBasic()
You can also create a new authority which can only access these web services.
An intro regarding the order first: The default order if you do not specify one, is the biggest possible number which maps to the lowest possible priority (since lower order translates to higher priority). Therefore when you added the configuration with order=1, you had two configurations, with the new one with order=1 having higher priority and being checked first.
In the scenario you describe where both configurations exist the following happens:
You try to log in to the webapp as admin/admin (or user/user), but spring security checks first the configuration with order=1 which has the ant matcher " .antMatchers("/basicAuthApi/**").hasRole("API")". It obviously does not match since the url you point is the one for the website, however the security does not fail because you are missing .anyRequest().authenticated() which would be needed in order to make the security check actually fail for users that failed to authenticate. Without this you actually pass the security check, although you are not authenticated i.e. you are considered an anonymous user with anonymous user access. Since spring security for that configuration succeeded, it does not even check the other one which is related to the website.
I found a solution that works for me. I realised that I don't need another #Configuration because the default jhipster configuration does not redirect to login page for unauthenticated access - it returns a 401 which is I want for the REST api too.
So I just registered a user with the username and password that I want to use for the REST api and added the following line to the bottom of the configure method in
OAuth2ServerConfiguration.ResourceServerConfiguration
`.antMatchers("/basicAuthApi/**").hasAuthority(AuthoritiesConstants.USER).and().httpBasic();`
I will now try to improve this by creating an API role and giving that role to users who need to call the REST API
I would still like to know why my original attempt had those problems if anyone knows.
Cheers
dalyc

Categories

Resources