Spring Boot 2.0.2 Spring Security how to disable custom Form Login for two endpoints - spring-boot

EDIT:
After several days of trying various Security configuration changes, I punted and put .permitAll() on every endpoint which should authorize/authenticate any request. But even then, although I could freely "browse" any page without authenticating, my device clients were still unable to submit PUT requests to their normal application endpoint.
So now the question is, why can the remote clients successfully submit PUT requests to my app running on the 1.5.4 Spring Boot version but not when "the same app" is running at Spring Boot 2.0.2?
I get a successful "health check" response ("up and running as usual...") when I hit the same "device" endpoint with a GET request from my browser. But the client devices just get ERR_CONNECTION_REFUSED (or similar) when they try to PUT.
/EDIT
This question is related to one I asked about Web Socket migration a couple of days ago, but the web socket part turned out to be a red herring.
The real issue I'm facing is related to Spring Security in SB 2.0.2.
springBootVersion = '2.0.2.RELEASE'
springVersion = '5.0.13.RELEASE'
springSecurityVersion = '5.2.1.RELEASE'
Everything was working the way we needed at SB 1.5.4, but at 2.0.2 I can't seem to restore the necessary behavior. What I need is my custom Form Login applied to all endpoints except /input and /input/auth
This is the only configurer adapter we were using at 1.5.4 (with ACCESS OVERRIDE)
#Configuration
#EnableWebSecurity
//#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#Order(1)// highest priority
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
SimpleAuthenticationManager sam;
#Override
protected void configure(HttpSecurity http) throws Exception {
// false means go to original destination page after login success
boolean alwaysRedirectToSuccessUrl = false;
http.headers().cacheControl().disable();
http.headers().frameOptions().sameOrigin();
http.csrf().ignoringAntMatchers("/input/auth/**");// ignoring WebSocket endpoints (secured by other means)
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http.authorizeRequests()
.antMatchers('/widgetInfo/**', '/stats', '/errorCodes').hasAuthority('USER').anyRequest().fullyAuthenticated()
http.formLogin()
.loginPage('/widgetInfo/login')
.loginProcessingUrl("/widgetInfo/fooInfo")
.defaultSuccessUrl("/widgetInfo/fooInfo", alwaysRedirectToSuccessUrl)
.failureUrl("/widgetInfo/login?status=LOGIN_FAILURE").permitAll()
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers('/webjars/**', '/static/**', '/css/**', '/js/**', '/input/**');
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(sam)
}
}
The above configuration works in 2.0.2, except that it is not allowing free access to the /input endpoints. After chasing the red herring for a couple of days, and realizing my misunderstanding, I tried adding another much more lenient configurer adapter as more-or-less described at the bottom of this page
#Configuration
#EnableWebSecurity
#Order(11)// lowest priority
class LenientWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/input/auth/**");// ignoring WebSocket endpoints (secured by other means)
http.authorizeRequests().antMatchers('/input', 'input/auth', '/input/**').permitAll()
}
}
But it's not working, the /input endpoint is not yet freely accessible. What is the issue?
If I swap the #Order, then nothing goes through my custom Form Login.

Answering here just to close loop for future users who might land here.
The problem turned out to be that Spring Boot 1.5.4 would accept "HTTP1.1" requests, but Spring Boot 2.0.2 will not.
Our app sits behind an F5 device that rejects inbound requests if/when the application "healthcheck" requests fail. This is the "healthcheck" request that was working at 1.5.4
GET /myGateway/input HTTP/1.1\r\n
But at 2.0.2, that request was failing. At 2.0.2 the healthcheck request needs to be
GET /myGateway/input \r\n
Therefore, "Spring Security" configuration issues were also a red herring.
EDIT: Apparently, this is/was a known issue with 2.0.x that was fixed in Spring Boot 2.2.x (summer 2019)

Related

Unable to Remove ;jsessionid in a Spring Boot / Web Flow Application's URL when Deployed to Tomcat 8.5

I'm working on a Java application where a user registers a password for his/her account. The following are being used:
Spring Boot
Spring MVC
Spring Web Flow
Spring Security
Thymeleaf
Interceptor (for checking the session in the preHandle method)
For the Spring Security part, there's really no authentication required. I just use it to handle CSRF and the configuration is as follows:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF feature only
http.authorizeRequests().anyRequest().permitAll();
}
}
Now, this is where things get messy. When I deploy it to Tomcat in a Unix environment, ;jsessionid gets appended to the URL and Spring Security is not happy. I have scrounged the Internet and found the following solutions to remove it (alongside my results).
server.servlet.session.tracking-modes=cookie in application.properties does nothing.
web.xml
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
or
#Configuration
public class WebConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
HashSet<SessionTrackingMode> set = new HashSet<>();
set.add(SessionTrackingMode.COOKIE);
servletContext.setSessionTrackingModes(set);
}
}
yields an IllegalArgumentException: The session tracking mode [COOKIE] requested for context [/<context-name>] is not supported by that context
I'm about to pull what remains of my hair off so I reverted any cookie-related changes and thought of just allowing semicolons in the URL (I know, I know, not secure) using the snippet below in the same SecurityConfig class.
#Bean
public HttpFirewall allowUrlSemicolonHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.httpFirewall(allowUrlSemicolonHttpFirewall());
}
And voila! The web flow runs on an infinite redirect.
Questions:
Has anyone ever encountered IllegalArgumentException: The session tracking mode [COOKIE] requested for context [/<context-name>] is not supported by that context before? I've searched far and wide and the closest that I could find is this.
Could the reason behind server.servlet.session.tracking-modes=cookie not working be the same as above?
Could the infinite redirect be caused by http.authorizeRequests().anyRequest().permitAll()? I tried using anonymous() but the result was the same.
Is it possible to know which part exactly is causing the infinite redirect?
Please note that allowing semicolons in the URL is working fine and dandy in my localhost, so I have a hunch that what's causing the redirects is SSL-related. In the same way that locally ;jsessionid is not being appended to the URL.
My next step is to try configuring SSL locally in an attempt to replicate the issue. In the meantime, any help would be highly appreciated. My apologies if there's too much information here; I'm willing to repost it as multiple questions if that's necessary.

Spring Security OAuth2 client with authorization_code grant - How to handle token request?

I am building a Spring Boot application using version 2.3.4 with spring-boot-starter-oauth2-client and spring-boot-starter-security dependencies.
I am trying to implement the JIRA Tempo plugin OAuth support.
I have it partially working using the following properties:
spring.security.oauth2.client.registration.tempo.redirect-uri=http://localhost:8080
spring.security.oauth2.client.registration.tempo.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.tempo.client-id=<the-client-id>
spring.security.oauth2.client.registration.tempo.client-secret=<the-client-secret>
spring.security.oauth2.client.registration.tempo.provider=tempo
spring.security.oauth2.client.provider.tempo.authorization-uri=https://mycompany.atlassian.net/plugins/servlet/ac/io.tempo.jira/oauth-authorize/?access_type=tenant_user
spring.security.oauth2.client.provider.tempo.token-uri=https://api.tempo.io/oauth/token/
and this config:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(expressionInterceptUrlRegistry -> expressionInterceptUrlRegistry.anyRequest().authenticated())
.oauth2Login();
}
When I access http://localhost:8080, it redirects to JIRA/Tempo and shows the approval dialog there to grant access to the Tempo data for my application. I can grant access, but after that, it just redirects again to that page instead of showing my own application.
With debugging, I noticed that there is a redirect to http://localhost:8080/?code=.... but Spring Security is not handling that. What else do I need to configure?
I also tried to set some breakpoints in DefaultAuthorizationCodeTokenResponseClient, but they are never hit.
UPDATE:
I changed the redirect-uri to be:
spring.security.oauth2.client.registration.tempo.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
(and I changed the Redirect URIs setting in Tempo to http://localhost:8080/login/oauth2/code/tempo).
This now redirects back to my localhost, but it fails with authorization_request_not_found.
UPDATE 2:
The reason for the authorization_request_not_found seems to be mismatch in HttpSessionOAuth2AuthorizationRequestRepository.removeAuthorizationRequest(HttpServletRequest request) between what is in the authorizationRequests and the stateParameters.
Note how one ends with = and the other with %3D, which makes them not match. It is probably no coincidence that the URL encoding of = is %3D. It is unclear to me if this is something that is a Spring Security problem, or a problem of the Tempo resource server, or still a misconfiguration on my part.
The redirect-uri property should not point to the root of your application, but to the processing filter, where the code after redirect is processed.
Best practice for you would be to leave the redirect-uri alone for the time being. Then it will default to /login/oauth2/code/* and this Url is handled by org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.

My heroku app is requesting a password that I did not put there

I'm new to the process of sending an application to production and I'm using Heroku free plan to test. Today I went to check my app and the API I made using Spring boot is not working and is requesting a login that I didn't do. My app address is https://erik-financial-api.herokuapp.com and when you go there it redirects you to the address https://erik-financial-api.herokuapp.com/login with the following:
I did not make this page and none of the passwords (from my app or from my Heroku account) work on it. This was supposed to be just a REST API for another front-end app. Does anyone know why is this happening?
The code for this project can be found on my GitHub on https://github.com/esscheffer/financial-api
Edit: this seems to be a default spring security login page. I have searched for solutions, but none worked so far. What I have tried:
Add
override fun configure(security: HttpSecurity) {
security.httpBasic().disable()
.formLogin().disable()
}
to my WebSecurityConfigurerAdapter class.
Add http.httpBasic().disable().formLogin().disable() to the configure of my ResourceServerConfigurerAdapter class.
Add (exclude = [SecurityAutoConfiguration::class]) to the #SpringBootApplication sanitation on my application class.
The first 2 tries didn't remove the login page and the last one broke the app, returning 404 for all pages. Note that this only happens when I deploy my application to Heroku. When running locally I don't have this login page or any other problem.
Add a new configuration class com.scheffer.erik.financial.api.config.SecurityConfig, where in the configure method you can disable the HTTP Basic authentication as well as login form based authentication, like below:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity security) throws Exception {
security
.httpBasic().disable()
.formLogin().disable();
}
}
Do it like this...permit all requests for the home page...I hope it will work for you.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/").permitAll() //OR .antMatchers("/**").permitAll()
.anyRequest().authenticated();
}

Spring Boot 2.0.0.M4 breaks http basic auth in application.yml

Spring Boot 1.5.6.RELEASE respected the basic-auth username and password as specified in my application.yml below.
I have upgraded to 2.0.0.M4 and now the application always starts with the default 'user' and randomly generated password. Basically the settings below are always completely ignored.
I saw some changes in the release note/doc specific to simplifying actuator security enabled/disabled. I didn't see anything specific to this.
Any ideas?
From my application.yml
security:
basic:
enabled: true
realm: some-service
user:
name: example_user
password: example_password
Update:
I've confirmed this functionality was just plainly taken out starting with Spring Boot 2.0.0.M4
In the appendices:
All the security.basic.* family of stuff is missing here from the M4 reference:
https://docs.spring.io/spring-boot/docs/2.0.0.M4/reference/html/common-application-properties.html
But appears here in the M3 reference:
https://docs.spring.io/spring-boot/docs/2.0.0.M3/reference/html/common-application-properties.html
I was able to temporarily downgrade to M3 to restore the previous functionality but would still appreciate some guidance on what replaced it. I just need a single hardcoded basic-auth user for this scenario. I'm aware I could use object configurations to do a much more complicated setup.
Edit 2018-01-31:
The ability to auto-configure a single user has been restored (via the spring.security.user configuration keys) starting with Spring Boot 2.0.0-RC1 (source).
Original answer:
The Spring Boot security.* properties have been deprecated starting with Spring Boot 2.0.0-M4. You can read about this in the Release Notes:
Security auto-configuration has been completely revisited: developers should read the companion blog post and refer to the Spring Boot 2.0 Security wiki page for more details about the change.
In order to restore the basic auth functionality you can register a custom WebSecurityConfigurerAdapter, like this:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder().username("user").password("password")
.authorities("ROLE_USER").build(),
User.withDefaultPasswordEncoder().username("admin").password("admin")
.authorities("ROLE_ACTUATOR", "ROLE_USER").build());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
.antMatchers("/**").hasRole("USER")
.and()
.cors()
.and()
.httpBasic();
}
}
(This will also configure basic auth for the Actuator endpoints)
If you additionally need to read the username and password from a .properties file, you can simply inject the values using the #Value annotation.

Spring Boot CSRF

Tried to implement CSRF protection on the latest Spring Boot.
All the examples on internet are based on user login and authentication, which I do not need.
My site does not have any sections requiring authentication.
I would like
1) Rest requests come from within site. No direct request from outside with wget to be allowed.
2) All pages (routes) must be requested from the index page (/)
Included the security dependency in pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-- Defined users in application.properties (even though, I do not need)
-- App creates _csrf.token .
-- Created class extending WebSecurityConfigurerAdapter with "configure" method overriding.
Tried all suggested filters in "configure". It did not work and finally left it blank.
The problem is that Wget can get api pages directly.
How to prevent it?
I've quickly put together a POC of this configuration:
#Configuration
#EnableWebSecurity
#SpringBootApplication
public class StackoverflowQ40929943Application extends WebSecurityConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(StackoverflowQ40929943Application.class, args);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
}
}
The gist of it is Spring Boot + Security will secure all endpoints automatically. Here we explicitly allow requests to all endpoints. But, Spring Boot + Security automatically configures CSRF out of the box which we've left enabled. Thus you get the best of both worlds.
NOTE: You'll probably need to refine this configuration further to meet your needs.
Full Example on GitHub

Resources