The scenario we are looking for is as follows:
- client connects with REST to a REST login url
- Spring microservice (using Spring Security) should return
200 OK
and a login token - the client keeps the token
- the client calls other REST endpoints using the same token.
However, I see that the client is getting 302
and a Location
header, together with the token. So it does authenticate, but with un-desired HTTP response status code and header.
The Spring Security configuration looks like this:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // Refactor login form
// See https://jira.springsource.org/browse/SPR-11496
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and()
.formLogin()
.loginPage("/signin")
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
...
}
I tried adding interceptors and filters but can't see where 302 and Location being set and added in Spring side.
However, the Location
header does show in the response headers received at the client side (together with the rest of the Spring Security headers LINK):
Server=Apache-Coyote/1.1
X-Content-Type-Options=nosniff
X-XSS-Protection=1; mode=block
Cache-Control=no-cache, no-store, max-age=0, must-revalidate
Pragma=no-cache
Expires=0
X-Frame-Options=DENY, SAMEORIGIN
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch
Content-Length=0
Date=Thu, 22 Dec 2016 20:15:20 GMT
Any suggestion how to make it work as expected ("200 OK", no Location header and the token)?
NOTE: using Spring Boot, Spring Security, no UI, just client code calling REST endpoints.
It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.
You can use
headers().defaultsDisabled()
and then chain that method to add the specific headers you want.You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.
Example:
http.formLogin()
is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.
Based on your requirement/scenario,
have you considered using HTTP Basic for authentication?
http.httpBasic()
Using HTTP Basic, you can populate the Authorization header with the username/password and the
BasicAuthenticationFilter
will take care of authenticating the credentials and populating the SecurityContext accordingly.I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.
If you look at
security-service.js
, you will see a factory namedsecurityService
which provides alogin()
function. This function calls the/principal
endpoint with theAuthorization
header populated with the username/password as per HTTP Basic format, for example:Authorization : Basic base64Encoded(username:passsword)
The
BasicAuthenticationFilter
will process this request by extracting the credentials and ultimately authenticating the user and populating theSecurityContext
with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint/principal
which is mapped toSecurityController.currentPrincipal
which simply returns a json representation of the authenticated principal.For your remaining requirements:
You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.
If you need a rest api, you must not use
http.formLogin()
. It generates form based login as described here.Instead you can have this configuration
Create a class,
AuthTokenFilter
which extends SpringUsernamePasswordAuthenticationFilter
and overridedoFilter
method, which checks for an authentication token in every request and setsSecurityContextHolder
accordingly.Then create an
AuthenticationController
, mapped with/login
url, which checks credentials, and returns token.To understand
loadUserByUsername
,UserDetailsService
andUserDetails
, please refer Spring security docs }For better understanding, please thoroughly read above link and subsequent chapters.