Make Angular client login to Spring backend with JWT - spring

I added JWT to my Spring backend following this guide: https://auth0.com/blog/securing-spring-boot-with-jwts/
When I send a PUT request using a software like Postman everything works fine, but as soon as I'm trying to login with my Angular client the data in the HttpServletRequest is empty.
I check the data in the attemptAuthentication method of the JWTLoginFilter in the following way:
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
String reqBody = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
// this appears to be empty on angular client calls
System.out.println(reqBody);
ObjectMapper objectMapper = new ObjectMapper().configure(Feature.AUTO_CLOSE_SOURCE, true)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
AccountCredentials creds = objectMapper.readValue(reqBody, AccountCredentials.class);
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(),
creds.getPassword(), Collections.emptyList()));
}
I'm sending the request out of the client like this:
const user = {
username: "asdf",
password: "asdf"
};
// imported from '#angular/http'
const headers = new Headers({
'Content-Type': 'application/json'
});
const body = JSON.stringify(user);
return this.http
.put("http://localhost:8080/api/login", body, {headers})
.toPromise()
.then(response => response.json().data as User)
.catch(this.handleError);
My suggestion would be that I'm sending the request body in a wrong way, but I can't see, what I'm doing wrong.
I tried:
sending the body as a regular JS object
sending it wrapped in another object
sending it as a JSON string as shown in the example
using POST instead of PUT (although it works with PUT in Postman)
changing the Content-Type Header to other values
None of this made any data appear in the backend.
If you need more information about anything, please ask me.

I got it.
I needed to allow CORS also on my HttpSecurity object as following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.so()
.on();
}
}
I'm not really sure, why the requests I sent with Postman could get received without CORS enabled in my HttpSecurity, but nevertheless it's working now.
I hope I can help others with this in future.

This might be the reason, Spring OAuth 2 normally expect a POST request with URL encoded username and password. So try something like this.
return this.http.post(Url, "grant_type=password&username=" + username + "&password=" + password)
I'm not 100% sure if this the case as mine was a Spring-MVC, but I expect it to be very similar.

Related

How do I make Spring Security return a 500 instead of a 403 when a InternalAuthenticationServiceException is thrown

I am using Spring Security to handle auth on my RESTful-ish webservice.
The goal is to create a /login endpoint for which the user provides a username/password and which returns a JWT. I'm loosely following this guide: https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
I've got the happy-path working; when a user provides a valid user/pass, a valid JWT is returned to them.
The problem is my error case. If my UserService.loadUserByUsername method (which is called by Spring Security in order to validate the user/pass) throws an IOException, I want Spring to return a 500 error. Instead, Spring returns a 403. I stepped through the internal Spring classes a bit, and they clearly differentiate between a AuthenticationException (which is thrown when auth fails) and a InternalAuthenticationServiceException (which is thrown when auth is unable to be completed due to an internal error). I want a 500 error returned when an InternalAuthenticationServiceException is encountered. How to I configure Spring to do this? Will I need to implement my own AuthenticationFailureHandler?
As Thomas Andolf said, it might have been best for me to simply implement an OAuth2 flow instead of a custom authentication solution. However, if you want to implement a custom authentication solution, and if you want to return specific HTTP error codes for specific errors, then you can write a custom AuthenticationFailureHandler to accomplish this. I'm not sure if this is a good solution, but it is a solution. Something like this:
class CustomAuthenticationFilter(authManager: AuthenticationManager) : AbstractAuthenticationProcessingFilter(AntPathRequestMatcher("/login", "POST")) {
init{
this.authenticationManager = authManager;
this.setAuthenticationSuccessHandler(CustomAuthenticationSuccessHandler())
this.setAuthenticationFailureHandler(CustomAuthenticationFailureHandler())
}
#Throws(AuthenticationException::class, PreAuthenticatedCredentialsNotFoundException::class)
override fun attemptAuthentication(req: HttpServletRequest, res: HttpServletResponse): Authentication {
// insert code to parse the request into a username and password
return authenticationManager.authenticate(
UsernamePasswordAuthenticationToken(
username,
password,
ArrayList())
)
}
}
class CustomAuthenticationSuccessHandler: AuthenticationSuccessHandler{
override fun onAuthenticationSuccess(request: HttpServletRequest?, response: HttpServletResponse?, authentication: Authentication?) {
// these next three lines simply verify that none of the inputs are null; this is Kotlin syntax.
request!!
response!!
authentication!!
val username = (authentication.principal as User).getUsername();
val expiration = Date(System.currentTimeMillis() + EXPIRATION_DURATION_MILLIS)
// insert code to create a JWT and write it to the response
// no need to return anything
}
}
class CustomAuthenticationFailureHandler : AuthenticationFailureHandler{
override fun onAuthenticationFailure(request: HttpServletRequest?, response: HttpServletResponse?, exception: AuthenticationException?) {
// these next two lines simply verify that none of the inputs are null; this is Kotlin syntax.
request!!
response!!
when (exception) {
is PreAuthenticatedCredentialsNotFoundException -> {
response.status = 400;
}
is AuthenticationServiceException -> {
response.status = 500;
}
else -> {
response.status = 401;
// consider adding a WWW-Authenticate header as well
}
}
}
}

Spring security - Get SESSION cookie value in AuthenticationSuccessHandler

I know that spring security creates a cookies names SESSION on successful authentication. Is it possible to get hold of that cookie value in AuthenticationSuccessHandler.
I have a following implementation inside which I need that SESSION cookie value. I looked as response headers of HttpServletResponse, but they have XSRF-TOKEN set-cookie headers,
#Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
// GET SESSION, COOKIE VALUE HERE
}
}
Can you please help.
The SESSION cookie is created by Spring Session's DefaultCookieSerializer, which is called every time a new Session is created, and not necessarily after successful authentication.
Spring Session's SessionRepositoryFilter wraps the HttpServletRequest in such a way that whenever you obtain an HttpSession from the request at any point in your application, you're actually getting a Spring Session object. However, this cookie is written to the response after your handler has been called, as you can see in SessionRepositoryFilter:
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession(); //the SESSION cookie is created if necessary
}
So if the session has just been created for this request...
The cookie won't be available in the HttpServletRequest because the cookie hasn't been sent yet (and so the browser couldn't have sent it)
The cookie won't be HttpServletResponse as a "Set-Cookie" header because it will be written after your application has handled the request.
However, you could get the cookie value:
String cookieValue = request.getSession().getId();
Note: The above code will force Spring Session to create a session backed Redis/Jdbc/etc that will be used later to generate the SESSION cookie.
I got it using the getSession().getId() method from request. My example is using the Webflux implementation with Kotlin but apparently works similar in HttpServletRequest implementation see https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpServletRequest.html#getSession--
class AuthenticationSuccessHandler : ServerAuthenticationSuccessHandler {
private val location = URI.create("https://redirect.page")
private val redirectStrategy: ServerRedirectStrategy = DefaultServerRedirectStrategy()
override fun onAuthenticationSuccess(webFilterExchange: WebFilterExchange?, authentication: Authentication?): Mono<Void> {
val exchange = webFilterExchange!!.exchange
return exchange.session.flatMap {
it.id // 87b5639c-7404-48a1-b9da-3ca47691a962
this.redirectStrategy.sendRedirect(exchange, location)
}
}
}

Using #PreAuthorize in a method that returns a SseEmitter returns HTTP 406

I'm using jwt for authentication, I'm suspecting that it has something to do with the request header which has the token.
Receiving a server sent event without adding #PreAuthorize works just fine:
#GetMapping("/sse")
public SseEmitter serverSentEvent() throws IOException
{
SseEmitter emitter = new SseEmitter(2592000000L);
emitter.send("OK");
return emitter;
}
How ever when i add #PreAuthorize to check use role, i get a 406 response (can’t establish a connection to the server at http://localhost:8080/sse)
#GetMapping("/sse")
#PreAuthorize("hasRole('ADMIN')")
public SseEmitter serverSentEvent() throws IOException
{
SseEmitter emitter = new SseEmitter(2592000000L);
emitter.send("OK");
return emitter;
}
Apparently, the JWT interceptor wasn't adding the token in authorization header for EventSource requests, so I used ng-event-source which allows for custom HTTP headers in EventSource.

spring 4.1 javaConfg setting to get requestCache working

similar to this:
Spring 3.1: Redirect after login not working
when an authenticated user becomes inauthenticated while deep-linking into a single page web app.
Spring security redirects to logon but:
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
is null
thus i cannot devine the url or params to send re-authenticated user to requested page
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers() //redacted .
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/x/y/logon")
.usernameParameter("userLogon") //redacted
.loginProcessingUrl("/x/y/logon") //redacted
.defaultSuccessUrl("/x/", true)
.failureUrl("/x/y/logon?error=true")
.and()
.logout()
.logoutUrl("/x/y/logout")
.logoutSuccessUrl("/x/")
.permitAll();
}
}
-- controller --
#RequestMapping(method=RequestMethod.GET, value="/y/logon")
public ModelAndView logonHandler(HttpServletRequest request, HttpServletResponse response) {
List<Client> clients = //manager call for list of clients to show at logon
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if (savedRequest != null) {
String reqUrl = savedRequest.getRedirectUrl();
String[] urlParams = reqUrl.split("&");
String prefix = "";
String urlParam = "";
String cid = "";
try {
urlParam = urlParams[1];
} catch(IndexOutOfBoundsException ioob) { }
if (reqUrl.contains("cid=")) { cid = reqUrl.substring(reqUrl.indexOf("cid=")+4, reqUrl.indexOf("&")); }
if (reqUrl.contains("?")) { reqUrl = reqUrl.substring(0, reqUrl.indexOf("?")); }
prefix = reqUrl.substring(reqUrl.indexOf("/x/")+6, reqUrl.indexOf("/x/")+8);
reqUrl = reqUrl.substring(reqUrl.indexOf(prefix)+2);
if (reqUrl.contains("/")) {
reqUrl = reqUrl.substring(0, reqUrl.indexOf("/"));
}
request.setAttribute("requestUrl", prefix+reqUrl);
request.setAttribute("urlParam", urlParam);
request.setAttribute("cid", cid);
}
request.setAttribute("IPAddress", request.getRemoteAddr());
return new ModelAndView("x/logon", "clients", clients);
}
problem is, SavedRequest is null
is this an issue with:
alwaysUseDefaultTargetUrl property?
if yes, how in javaConfig does one set this property?
----- on edit to address comments ------
i'll explain my understanding of ea. .formLogon() settings:
logonPage() will be read by spring and control redirect to logon page when you are not authorized (cookie expire/db record del, etc). There are many ways that a session can not be authorized and spring needs to know what page to send unauth requests to. My manual logon handler only handles requests to the logon url.
usernameParameter() is to change from the default form input name thus obfuscating that one is using spring security.
loginProcessingUrl() this seems to conflict with the custom logonHandler, but i think its req to handle the post and allow for spring to create a secure sesson.
defaultSucessUrl() tells spring where to go after successful logon (the post request).
failureUrl() defines the url for failed logon.
nowhere in my custom logon handler for the get request, are those settings in conflict... i think... but i've read the docs and the Spring Security 3 book and lots of online resources and i still do not feel as though i have a solid understanding of spring security... so i may be way off

How to get request attributes in authentication-success-handler

I am trying to do few things in authentication-success-handler and I need to access few values which was part of initial request data being posted to Spring security.
I am posting following information when user trying to do login
j_username
j_password
storeCode
Spring security is able to authenticate user successfully and is calling "authentication-success-handler".
public class WebshopAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler
{
public WebshopAuthenticationSuccessHandler() {
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
request.getAttribute( "storeCode" );
attr.getRequest().getAttribute( "storeCode" );
}
}
But in all way, I am not able to get value of storeCode and its coming as null.
Not sure what I am doing wrong.
I am assuming that Spring is creating a new instance of Request and response while calling onAuthenticationSuccess, but how can I pass/ retrieve values which passed passed from the login page?
If the data is from an HTTP POST request, you should be using getParameter, not getAttribute. Attributes are server-side state only, not submitted by the client.

Resources