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
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 fromUserControllerAdvice
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
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
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.
- Understanding Request, RITM, Task in ServiceNow
- Steps to create a case in ServiceNow (CSM)
- Performance Analytics in 10 mins
- Event Management in 10 minutes - part1
- Event Management in 10 minutes - part2
- Custom Lookup List
- Script includes in 5 minutes
- Interactive Filter in 5 minutes
- UI Policy in 6 Minutes
- Client Side Script Versus Server Side Script in 3 minutes
- Java
- ACL
- Performance analytics(PA) Interactive Filter
- Various Configurations in Performance analytics(PA)
- Service Portal
- Performance Analytics(PA) Widgets
- Performance Analytics(PA) Indicator
- Performance Analytics(PA) Buckets
- Performance Analytics(PA) Automated Breakdown
- Client Script
- Rest Integration
- Understanding the Request, RITM, Task
- Service Catalogs
- Events in ServiceNow
- Advance glide script in ServiceNow
- CAB Workbench
Comments