Form Login and HTTP Authorization header - spring-boot

I was checking the HTML code for the default form login page of Spring Security using Spring Boot. Apparently, there are 3 fields in the form (username, password and a hidden CSRF token) and it is a POST request.
I assumed that the HTTP request will not use Authorization header in this case - I have not seen any JavaScript code or something like that which encodes the username:password combination into a Base64 encoded string and puts it into HTTP Authorization header.
But when I checked the Google Chrome Developer tools Inspect, I find that the browser is actually sending the Authorization header with Basic scheme (with username and password Base64 encoded). Also the post body of the HTTP request again contained all the 3 fields (username, password and CSRF token).
How does this happen? Will the browser automatically recognize that this is as a login form and automatically create the Authorization header?
Please find my configuration below:
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/user").hasAnyRole("ADMIN", "USER")
.antMatchers("/").permitAll()
.and()
//.httpBasic();
.formLogin()
;
httpSecurity.csrf().disable();
}
Please note that I am testing using Form login.

I couldn't reproduce your issue. It would have been much confusing for me if it did
When I put back httpBasic() in config, as I expected I see WWW-Authenticate: Basic realm="Realm" in the server response, which makes the browser to create the login popup and Send the Authorization header on submission.
Do you see any such response header before the login form is displayed?

Related

Spring Security + Keycloak - setting no bearer token to REST request leads to an response with HTML content

I'm using a SpringBoot 2 (2.7.0) application (including Spring security 5.7.1) to secure REST endpoints with Keycloak for authentication and authorization. Everything works fine but the only thing which bothers me is when I don't set the bearer token I get a HTTP 400 response. The response itself is correct but the body of the response contains HTML (Keycloak login page).
Is there a way to avoid that the body of the response contains the login page? I would like to set a custom response body.
That is an expected default behavior. If you want to instead get relevant 4xx error instead, you can try setting the the "bearer-only" in your "keycloak.json" file so that it would not redirect API calls (i.e. AJAX calls from browser) to the login page:
{
...
"bearer-only": true
}

How to get csfr token if login page is disable

for example a simple spring configuration
http.authorizeRequests()
.antMatchers(HttpMethod.POST,"/create").permitAll()
.anyRequest().authenticated()
.and().formLogin();
with csfr enabled, the request for "/create" endpoint will be redirected to login page, where I can find _csfr value, then I can make another post request with a new header "X-CSRF-Token", and finally request will be served.
But what if login page is disable? :
http.authorizeRequests()
.antMatchers(HttpMethod.POST,"/create").permitAll()
.anyRequest().authenticated()
//.and().formLogin();
server will return 403 forbiden, there will be no html page to parse, how can I get csfr value in this case?

Spring Security OAuth2 login redirect to error page despite being successful

As to not reveal confidential information, the provider will be replaced by $PROVIDER.
The authorization works, but instead of redirecting me to the index, it redirects me to /error.
Steps to reproduce
Start the application
Go on any page, it will redirect me to http://localhost/oauth_login which displays a link login.
Click on the link login (which links to http://localhost/oauth2/authorization/$PROVIDER)
Get redirected to http://localhost/error
The error page displays the following (I formatted it for readability):
{
"timestamp":"2018-04-05T14:18:47.720+0000",
"status":999,
"error":"None",
"message":"No message available"
}
This is obviously the same parameters shown on the default Whitelabel Error Page, so really, the only problem is that it's in JSON except of being an HTML page. If I refresh http://localhost/error after, it shows me the normal Whitelabel Error Page.
Now this is where it gets weird, if I try to navigate to http://localhost/ after having been redirected to the error page, I am authenticated (the user data is there, so the authorization was successful). Basically, the issue is that I am redirected to http://localhost/error instead of http://localhost/.
Because it is fully functional (aside from the redirection), I will not post the whole security configuration, but I will instead limit it to relevant code:
SecurityConfiguration
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests().anyRequest()
.authenticated()
.and()
.oauth2Login()
.loginPage("/oauth_login")
.defaultSuccessUrl("/")
.failureUrl("/oauth_login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/oauth_login").permitAll()
;
}
Relevant properties
spring.security.oauth2.client.registration.$PROVIDER.redirectUriTemplate=http://localhost/login/oauth2/code/$PROVIDER
Relevant information
Once I am authenticated on the website, I can navigate, refresh the page, do whatever I wish (until I log out).
TL;DR Authorization works but redirected to /error instead of /.
I actually found the answer to my own question, but I decided to post it anyways in case somebody runs into the same issue.
.defaultSuccessUrl("/")
should have been
.defaultSuccessUrl("/", true)
If you don't set it to true, it will automatically redirect the user to the last previous request, and in my case, the last request is made to the url /error, which explains my problem.
By setting it to true, you force the user to be redirected to whatever your defaultSuccessUrl is.
By looking at the logs, you can actually see it:
2018-04-05 11:04:09 DEBUG [-nio-80-exec-10] RequestAwareAuthenticationSuccessHandler : Redirecting to DefaultSavedRequest Url: http://localhost/error
2018-04-05 11:04:09 DEBUG [-nio-80-exec-10] o.s.security.web.DefaultRedirectStrategy : Redirecting to 'http://localhost/error'

Spring SPNEGO without form

I have some questions about setting up SPNEGO without use of form fall back.
I am writing a web service that uses SPNEGO authentication and returns a signed JWT for the authenticated principal. I don't have any use for forms (can't use forms with authentication-only principals, for example).
Can I skip the formLogin() and logout() parts as shown in the spring SPNEGO reference, and use the zero-argument SpnegoEntryPoint constructor?
It seems from looking at the source for SPNEGO filter that if the Authorization header is missing then the filter doesn't actually return a 401 Unauthorized response with a WWW-Authenticate: Negotiate challenge to the caller. That is, there is no else to the check on line 135. Am I reading this correctly? The RFC states
If the server receives a request for an access-protected object, and
if an acceptable Authorization header has not been sent, the server
responds with a "401 Unauthorized" status code, and a "WWW-
Authenticate:" header
If the above is correct, are clients expected to preemptively send the Authorization header?
Thanks!
EDIT (and probably RESOLVED): My original security config had:
http.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore()...`
When I remove .antMatchers("/v1/**").permitAll() the SpnegoEntryPoint kicks in and I now see a 401 with WWW-Authenticate: Negotiate response.
Of course now I get a Server not found in Kerberos database gss_init_sec_context() failure but i suspect that has to do with SPN setup or host name resolution (since I am testing this locally on my Macbook) than the Spring app.
Will update once finally when I get this working.
Yes, the zero argument cosntructor is correct.
The Filter is not responsible for setting the Status and Header. The SpnegoEntryPoint is. org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint.commence(HttpServletRequest, HttpServletResponse, AuthenticationException)
No, see 2.

JMeter Basic Authentication with HTTP Authorization Manager

I am trying to do Basic Authentication for protected endpoints. I tried the following :
In the HTTP Header Manager, add an entry with the name "Authorization" and the value "Basic [encoded credentials from above]" as suggested in JMeter Basic Authentication
Added Http Authorization manager and added the
base url as https://shopping-qa.myproject.mydomain.comalong with the username and password. The url of the endpoint is https://shopping-qa.myproject.mydomain.com/api/v3/profile/summary.
While it works when I use the option 1, it does not work when I use option 2. I also uncommented httpclient.parameters.file=httpclient.parameters in jmeter.Properties and http.authentication.preemptive$Boolean=true in httpclient.parameters. But I still do not get the authentication to work.
Any suggestions on where I am going wrong?
Thank you!
If you use httpclient.parameters - make sure that you have HTTPClient3 implementation of the HTTP Request Sampler(s).
Double check that HTTP Authorization Manager really adds "Authorization" header and credentials are correct.
See How to use HTTP Basic Authentication in JMeter for example of bypassing basic HTTP authentication in phpmyadmin.

Resources