This article and the source codes attached should be used as reference only.Please thoroughly test your implementation before making changes to production environment
Checkout our NEW Video Channel you can like and subscribe too!

Introduction

In earlier times having a MVC 2 tier application coupled frontend JSP and HTML was enough.Slowly we moved to dynamic rendering of pages using Jquery,Ajax.But with the inception of powerful frameworks like Angular,React,Vue the entire concept of application design have changed.

Modern apps have segregated UI from the backend.This gives us flexibility in the stack we choose for frontend and backend.But what about app security? How we deal with it ? Is the cross platform access allowed? How can we protect a backend resource ? In this post are going to cover the basic one today. The Basic Authentication Headers

SourceCode

Authenticating the User

There are multiple ways to authenticate a user, basic auth which uses http headers to authenticate is most basic one. once a user authenticates , server typically creates a session for the user to compensate the stateless behavior of HTTP.But with modern frontend frameworks it is not mandatory to have session at backend, we can have JWT token at the client browser that is equivalent to a session.But the backend server needs to validate the incoming request whether or not it is maintaining a server side session.

A session helps the server by avoiding revalidation every request till the session expires

Resource can be cross-domain yet be part of the same parent domain.For example www.learnowlab.com might be our frontend but we may need to connect the backend api.learnnowab.com.Technically they are CORS(Cross origin resources sharing) which by default blocked in any modern browser unless a server explicitly sends response header with Access-Control-Allow-Origin: *(This is evil).

Whether or not we use Session for api.learnnowab.com we need to identify the requestor and validate the request.Basic authentication is the simplest of them.

Basic authentication helps us to protect a resource(an endpoint) from unauthorized access

Our use-case

We will write a very simple spring boot application with following story points

  • All resources are protected by default
  • Only authenticated user can access the api
  • We also observe the difference of having a session and being stateless

In addition we also have a look

  • The default authentication failure response using BasicAuthenticationEntryPoint (prompts www-authenticate in browser)
  • Use of Custom authentication entrypoint to respond with 401 (when we call via api - postman)
  • we will use HandlerExceptionResolver to throw our custom exception from UserControllerAdvice

Password Encoder in Spring 5

From Spring5 for any kind of credential store we have to have a passwordEncoder bean.

have a look at here Password Storage Format

Because we are going to use plain text we are going to use this format ({noop}password). Please use BCryptPasswordEncoder or Pbkdf2PasswordEncoder for a real world scenario.

Note:if you dont mention a encoder type you will get an error like below

There is no PasswordEncoder mapped for the id "null"

This is understandable now, as id here is nothing but the encoding type.

Few words on Spring Security filter chain

Spring is wisely select the filter types based on http request.For example if we choose to use basic authentication then it will flow through BasicAuthenticationFilter.If the request is form based post request then it will enable UsernamePasswordAuthenticationFilter.

After successful extraction of username password it generates UsernamePasswordAuthenticationToken This is then forward to AuthenticationManagager to authenticate. Authentication manager can have various provides as source of truth.In our case it will be inMemoryAuthentication using a username password provided.

There might be additional step to create UserDetails object from a User model.Spring will give us a default version of this anyway,so we are not going to provide our own.But in case a JWT claims this might be useful to provided additional details to the token using this UserDetails service.

If it authenticates fine, then it will return the granted authorities,user details.In this regard note that spring can inject a AuthenticationPrincipal object to any controller we expose to get this authenticated object.

org.springframework.security.core.userdetails.User@459c5729: 
Username: app_user; 
Password: [PROTECTED]; 
Enabled: true; 
AccountNonExpired: true; 
credentialsNonExpired: true; 
AccountNonLocked: true; 
Granted Authorities: ROLE_USER

If an AuthenticationException is thrown then it is handled by AuthenticationEntryPoint

In this regard, the default feature of a BasicAuthenticationEntryPoint is to return back a WWW-Authenticate which is meant for a browser to prompt for authentication challenge like this

WWW-Authenticate.PNG

But this will not work in case of post-man or any rest client which needs a json type response in the body.To do this we are going to implement our version AuthenticationEntryPoint and return a custom message which will be

We can override this endpoint for our custom response message or return headers.

Code walk-through

Project Structure

project-structure.PNG

You can download the whole source code here and play around.

we first define our SpringSecurityConfig which extends WebSecurityConfigurerAdapter.Here we define our inMemoryAuthentication type with a dmmy user name and password.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("app_user").password("{noop}password").roles("USER");
}

We are going to authenticate all request and authentication type wil be basic so

@Autowired
private EntryPointConfigurer entryPointConfigurer;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and().httpBasic()
            //.realmName("lnl-app")
            .authenticationEntryPoint(entryPointConfigurer)
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

As we are going have a custom error message we have added entryPointConfigurer

Below is the class

@Component
public class EntryPointConfigurer implements AuthenticationEntryPoint {

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    public void commence(HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException exception) throws IOException, ServletException {
        resolver.resolveException(request, response, null,
            new InvalidUserNamePasswordException(MessageDTO.INVALID_USER_NAME_PWD));
    }
}

We are going throw InvalidUserNamePasswordException by overriding commence method exception here and in controller advice we will convert it back to respone entity and send it back to client with appropriate message

Note we have used a qualifier here, to avoid NoUniqueBeanDefinitionException for HandlerExceptionResolver

org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'org.springframework.web.servlet.HandlerExceptionResolver' available: 
expected single matching bean but found 2: errorAttributes,handlerExceptionResolver

Now let look at the controller advice method

@ExceptionHandler({
    InvalidUserNamePasswordException.class
})
public ResponseEntity handleInvalidAccessToken(InvalidUserNamePasswordException e) {
    return new ResponseEntity < > (MessageDTO.INVALID_USER_NAME_PWD.toString(),
        HttpStatus.UNAUTHORIZED);
}

Ok finally have look at the controller where we are going to inject our principal object and send it back as response.

@RestController
public class UserController {

    @PreAuthorize("hasRole('USER')")
    @GetMapping(value = "/")
    public String getUserDetails(@AuthenticationPrincipal User user) {
        return "Welcome User " + user.toString();
    }
}

Lets see it in action

1.With valid user name and password

#Request
curl --location --request GET 'localhost:8080' \
--header 'Authorization: Basic YXBwX3VzZXI6YXNkYWQ='

In console - debug mode

..at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
.. Basic Authentication Authorization header found for user 'app_user'
..Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
.. Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@181e67d: 
Principal: org.springframework.security.core.userdetails.User@459c5729: 
Username: app_user; Password: [PROTECTED]; Enabled: true; 
AccountNonExpired: true; 
credentialsNonExpired: true; 
AccountNonLocked: true; 
Granted Authorities: ROLE_USER; 
Credentials: [PROTECTED]; 
Authenticated: true; 
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: 
RemoteIpAddress: 0:0:0:0:0:0:0:1; 
SessionId: null; 
Granted Authorities: ROLE_USER

#Response
Welcome User org.springframework.security.core.userdetails.User@459c5729: 
Username: app_user; 
Password: [PROTECTED]; 
Enabled: true; 
AccountNonExpired: true; 
credentialsNonExpired: true; 
AccountNonLocked: true; 
Granted Authorities: ROLE_USER

We can see that @AuthenticationPrincipal is giving us entire authentication object

2.With invalid user and password

#Response

User name or password provided is incorrect

Nice! we get our custom error message.Remember if don’t override the entry point you will see a blank response this because of the BasicAUthenticationEntryPoint which return www-authenticate header we discussed earlier.

have look at the spring BasicAuthenticationEntryPoint class below


package org.springframework.security.web.authentication.www;

public void commence(HttpServletRequest request, HttpServletResponse response,
    AuthenticationException authException) throws IOException {
    response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
    response.sendError(HttpStatus.UNAUTHORIZED.value(),
        HttpStatus.UNAUTHORIZED.getReasonPhrase());
}

Session vs Stateless

Well this debateable whether we should have complete stateless app or have spring sessions.But apart from all other factors do note that if you dont use session, the server takes the additional overhead to authenticate your request every time.If you are ok with it then you should definitely go for statelessness as it free up your mind form maintain a centralized state like spring session backed up by well know caches like redis,memcache.

Anyway, we need set sessionCreationPolicy as SessionCreationPolicy.STATELESS to not let store spring the sessions and JSESSIONID in the browser.

       http.authorizeRequests()...and().
       sessionManagement().
       sessionCreationPolicy(SessionCreationPolicy.STATELESS);

These are 4 types of behavior available for SessionCreationPolicy

public enum SessionCreationPolicy {
	/** Always create an {@link HttpSession} */
	ALWAYS,
	/**
	 * Spring Security will never create an {@link HttpSession}, but will use the
	 * {@link HttpSession} if it already exists
	 */
	NEVER,
	/** Spring Security will only create an {@link HttpSession} if required */
	IF_REQUIRED,
	/**
	 * Spring Security will never create an {@link HttpSession} and it will never use it
	 * to obtain the {@link SecurityContext}
	 */
	STATELESS
}

taken from SessionCreationPolicy Enum in Spring

But as mentioned see the below overhead spring security has when we make calls as stateless

..FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /; Attributes: [authenticated]
..BasicAuthenticationFilter  : Basic Authentication Authorization header found for user 'app_user'
..authentication.ProviderManager : Authentication attempt using
..org.springframework.security.authentication.dao.DaoAuthenticationProvider
..BasicAuthenticationFilter  : Authentication success: ..org.springframework.security.authentication.UsernamePasswordAuthenticationToken@181e67d....

had we used session then in 2nd call onwards (till session expires)

..Rfc6265CookieProcessor   : Cookies: Parsing b[]: JSESSIONID=DB4EDCC2E967E7ABC39DC2E4116964FF
..CoyoteAdapter     : Requested cookie session id is DB4EDCC2E967E7ABC39DC2E4116964FF
..FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /; Attributes: [authenticated]
..FilterSecurityInterceptor    : Previously Authenticated: 
..org.springframework.security.authentication.UsernamePasswordAuthenticationToken@181e67d....
..FilterSecurityInterceptor    : Authorization successful

Carefully observe here Spring is happy to see a valid session so it not attempting validate the user again.So here actually we are reducing the overhead of Spring to re-authenticate the user every time.

Conclusion

Thats all for today. We learnt how to create a basic authentication using Spring,also how to response with a custom error message and finally how session versus statelessness impacts the server processing.Thanks for reading.Please have a look at Caching too.

    Content