How to validate a custom session in spring - java

I'm new to spring, so just looking for a general direction / advice.
I'm building a small microservice. The service auth is already setup to validate a JWT that is passed to it (issued by a different microservice).
The JWT contains a sessionid claim. I want to be able to get that claim, and validate that the session is still active.
For now that will be by querying the database directly, although in future when we have a dedicated session-service, I will change it to call that service instead.
I can obviously get the claim in each controller that should validate the session (basically all of them), but I am looking for a more global option, like a request interceptor (that only triggers AFTER the JWT is validated, and has access to the validated JWT).
Is there a recommended way to do this kind of thing in spring?

You have several options to do that: using a ControllerAdvice, perhaps AOP, but probably the way to go will be using a custom security filter.
The idea is exemplified in this article and this related SO question.
First, create a filter for processing the appropriate claim. For example:
public class SessionValidationFilter extends GenericFilterBean {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORIZATION_BEARER = "Bearer";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// We will provide our own validation logic from scratch
// If you are using Spring OAuth or something similar
// you can instead use the already authenticated token, something like:
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
// Jwt jwt = (Jwt) authentication.getPrincipal();
// String sessionId = jwt.getClaimAsString("sessionid");
// ...
// Resolve token from request
String jwt = getTokenFromRequest(httpServletRequest);
if (jwt == null) {
// your choice... mine
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// If the token is not valid, raise error
if (!this.validateToken(jwt)) {
throw new BadCredentialsException("Session expired");
}
// Continue filter chain
filterChain.doFilter(servletRequest, servletResponse);
}
// Resolve token from Authorization header
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith(AUTHORIZATION_BEARER)) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
// Validate the JWT token
// We can use the jjwt library, for instance, to process the JWT token claims
private boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
// .setSigningKey(...)
.parseClaimsJws(token)
.getBody()
;
String sessionId = (String)claims.get("sessionid");
// Process as appropriate to determine whether the session is valid or not
//...
return true;
} catch (Exception e) {
// consider logging the error. Handle as appropriate
}
return false;
}
}
Now, assuming for instance that you are using Java configuration, add the filter to the Spring Security filter chain after the one that actually is performing the authentication:
#Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
// the rest of your code
#Override
protected void configure(HttpSecurity http) throws Exception {
// the rest of your configuration
// Add the custom filter
// see https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#filter-stack
// you can name every provided filter or any specified that is included in the filter chain
http.addFilterAfter(new SessionValidationFilter(), BasicAuthenticationFilter.class);
}
}

Related

While using Jetty as a server, how do I return a clear json 401 error when JWT Filter blocks unauthenticated users in Spring security?

I have my Spring Security bean which is doing well in blocking unauthorised requests, while using Tomcat, the error response is a clean exception with the message but with Jetty, a text/html is returned even in postman as shown below.
And my doFilterInternal JWT Filter is as below.
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtility jwtUtility;
#Autowired
private UserServiceImpl userService;
private final ObjectMapper mapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String authorization = httpServletRequest.getHeader("Authorization");
String token = null;
String userName = null;
if(null != authorization && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
userName = jwtUtility.getUsernameFromToken(token);
}
if(null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails
= userService.loadUserByUsername(userName);
try {
if (jwtUtility.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)
);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e){
// System.out.println("Hello world");
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
With Tomcat as my server, everything works as expected, but I have shifted to Jetty Server, how best can I replace this code below to work the same for Jetty?
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
My expected response when the user has not sent in their jwt is as follows:-
HTTP 401 Unauthorized
{
"ACCESS_DENIED": "Some error message here",
}
Otherwise, the request should go through therefore the filter should not return anything.
Note: Please feel free to edit this question to make it better as I have more friends trying to solve the same issue.
The response you are seeing is the default Servlet ERROR dispatch handling (see DispatcherType.ERROR)
Use the standard Servlet error-page mappings to specify your own custom mappings for handling errors. A mapping can be based on status codes, throwables, and more (see Servlet descriptor and <error-page> mappings)

How do i write a WebSecurityConfigurerAdapter correctly when using a JWT filter?

I'm struggling to implement the jwt authentication while i understand (i think so) the theory behind it.
The whole idea is to send a jwt token every time user wants to access personal data through backend secured endpoints, instead of sending the credentials every time.
My backend is based on this repo. I have edited the code for my needs:
JwtTokenFilter.java
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
// Save initial path. Since CustomErrorController always uses /error path
// we can't retrieve the initial path using httpRequest.getRequestURI() method
httpServletRequest.setAttribute("initialPath", httpServletRequest.getRequestURI());
if (httpServletRequest.getHeader("Authorization") == null) {
throw new TokenException("Authorization field is empty.");
}
String token = jwtTokenProvider.resolveToken(httpServletRequest);
if (token == null) {
throw new TokenException("Non Bearer Token");
}
// throws TokenException
jwtTokenProvider.validateToken(token);
// throws NoUserFoundException
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.csrf().disable();
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Entry points
// http.authorizeRequests()
// .antMatchers("/signin").permitAll()
// .antMatchers("/signup").permitAll()
// Disallow everything else..
// .antMatchers("/profile").authenticated();
// If a user tries to access a resource without having enough permissions
// http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
// System.out.println("**************** INSIDE HANDLEEEEEEEEER");
// response.sendError(417);
// });
// IMPORTANT!!!
// DO I NEED THESE???
// http.authorizeRequests().antMatchers("/signin", "signup").permitAll();
// http.authorizeRequests().antMatchers("/signin", "signup").hasAnyRole("ADMIN", "USER");
// http.authorizeRequests().antMatchers("/signin", "signup").hasAnyRole("ADMIN");
// http.authorizeRequests().antMatchers("/profile").authenticated();
http.antMatcher("/profile").apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Here are my questions:
See the commented out code on the WebSecurityConfigurerAdapter. First of, it doesn't seem to work. But even if it did, it wouldn't make sense.
We want the jwt filter to be applied only to specific urls. It wouldn't make sense to ask for a token when everyone has the right to sign up or sign in. The last line inside WebSecurityConfigurerAdapter applies the filter only on the /profile endpoint.
The jwt token contains (if it is valid) the username. When extracted, we search for that user on the database. This user object contains its' role. So, in order to have a user's role we MUST pass through the jwt filter in order to validate it and THEN find the saved user.
So why the code doesn't work at all?
Is the commented code really useless or am i missing something? How would it be possible to authenticate the user before even applying the jwt filter?

Spring security: how to implement "two-step" authentication (oauth then form-based)?

In my app, I have two different gateways for authentication that work separately pretty good:
OAuth 1.0 (service-to-service) which gives ROLE_OAUTH to the
user; Here I know nothing about a user and have only some context information about service it uses inside Principal object;
Standard form-based authentication that gives ROLE_USER to the
user; Here I have full information about my user but no context information about service it uses inside Principal object;
Now I want to implement two-step authentication: 1) OAuth then form-based.
The complexity is that I do not want to lose context-specific information stored in the Principal after step 1 (OAuth); I just want to add some new user-specific information after the completion of form-based authentication to the security context as well as a new role ROLE_USER, all in the same authentication session.
Is it possible to implement smoothly? How can I extract already existing Principal info during second step (form-based authentication) and add it to the new Principal?
Is there any kind of "template solution" without re-inventing the wheel?
My current straightforward solution would be:
I have authenticated user with role ROLE_OAUTH and opened
authentication session;
Create a separate path for 2-d step like /oauth/login;
After user enters his credentials, I process them outside the
security chain in a controller manually checking the credentials;
If successful, manually update the security context not loosing
the authentication session, then redirect user to the requested protected by
ROLE_USER resource;
But I do not like it, it seems lame since I have to manually process second security request..
How can I implement this correctly in Spring-ish way? Thank you.
P.S. I have to use Oauth 1.0 for legacy reasons, cannot upgrade it to v.2 or any other solution.
OK, here is how I managed to fulfill this task.
I have an authenticated user (actually service) with role ROLE_OAUTH
and opened authentication session and some crucial information about the context
kept in the session that was hard-wired into OAuth request;
Now when trying to access a protected resource that demands another role, say ROLE_USER, Spring gives me AccessDeniedException and sends 403 forbidden response (see AccessDeniedHandlerImpl), kindly suggesting to override the default behavior if needed in a custom AccessDeniedHandler. Here is the code sample:
public class OAuthAwareAccessDeniedHandler implements AccessDeniedHandler {
private static final Log LOG = LogFactory.getLog(OAuthAwareAccessDeniedHandler.class);
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(auth)) {
LOG.debug("Prohibited to authorize OAuth user trying to access protected resource.., redirected to /login");
// Remember the request pathway
RequestCache requestCache = new HttpSessionRequestCache();
requestCache.saveRequest(request, response);
response.sendRedirect(request.getContextPath() + "/login");
return;
}
LOG.debug("Ordinary redirection to /accessDenied URL..");
response.sendRedirect(request.getContextPath() + "/accessDenied");
}
}
Now we need to add this new handler to the configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// all the config
.and()
.exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());
}
After this step the default UsernamePasswordAuthenticationFilter would process the input by creating another Authentication object with entered credentials, the default behavior just loses the existing information wired into previous OAuth Authentication object. So we need to override this default behavior by extending this class, like this, fnd adding this filter before the standard UsernamePasswordAuthenticationFilter.
public class OAuthAwareUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Log LOG = LogFactory.getLog(LTIAwareUsernamePasswordAuthenticationFilter.class);
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();
// Check for OAuth authentication in place
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {
LOG.debug("OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");
SecurityContextHolder.clearContext();
Authentication authentication = null;
try {// Attempt to authenticate with standard UsernamePasswordAuthenticationFilter
authentication = super.attemptAuthentication(request, response);
} catch (AuthenticationException e) {
// If fails by throwing an exception, catch it in unsuccessfulAuthentication() method
LOG.debug("Failed to upgrade authentication with UsernamePasswordAuthenticationFilter");
SecurityContextHolder.getContext().setAuthentication(previousAuth);
throw e;
}
LOG.debug("Obtained a valid authentication with UsernamePasswordAuthenticationFilter");
Principal newPrincipal = authentication.getPrincipal();
// Here extract all needed information about roles and domain-specific info
Principal rememberedPrincipal = previousAuth.getPrincipal();
// Then enrich this remembered principal with the new information and return it
LOG.debug("Created an updated authentication for user");
return newAuth;
}
LOG.debug("No OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");
return super.attemptAuthentication(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {
LOG.debug("unsuccessfulAuthentication upgrade for OAuth user, previous authentication :: "+ previousAuth);
super.unsuccessfulAuthentication(request, response, failed);
LOG.debug("fallback to previous authentication");
SecurityContextHolder.getContext().setAuthentication(previousAuth);
} else {
LOG.debug("unsuccessfulAuthentication for a non-OAuth user with UsernamePasswordAuthenticationFilter");
super.unsuccessfulAuthentication(request, response, failed);
}
}
}
The only thing left is to add this filter before UsernamePasswordAuthenticationFilter and apply it only to the given endpoint:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(oauthAwareUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// here come ant rules
.and()
.formLogin()
.and()
.exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());
}
That's it. This example is tested to be workable. Maybe some side-effects will be found afterwards, not sure. Also I'm sure it could be done in a more refined way, but I'll go with this code for now.

Override the existing Spring Security authentication

How can I override the existing Spring Security authentication by invoking a Web Service and when it's failed, need to redirect some third party login page.
For calling this authentication web service, I need to get some ServletRequest parameter and for redirection, I need to access the ServletResponse.
Therefore I need to find out some Authentication method with ServletRequest and ServletResponse parameters.
But still, I failed to find out such a ProcessingFilter or AuthenticationProvider.
According to Spring Security basic it seems I have to override the AuthenticationProvider related authenticate method.
According to use case, I have to implement the Spring Security Pre-authentication,
but the issue is PreAuthenticatedAuthenticationProvider related 'authenticate' method only having the Authentication parameter.
PreAuthenticatedAuthenticationProvider
public class PreAuthenticatedAuthenticationProvider implements
AuthenticationProvider, InitializingBean, Ordered {
public Authentication authenticate(Authentication authentication) {}
}
As solution, is there any possibility to use custom implementation of AuthenticationFailureHandler ?
Thanks.
I have got resolved the issue as following manner,
Implementing a custom AbstractPreAuthenticatedProcessingFilter
Override the doFilter method
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// Get current Authentication object from SecurityContext
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// Call for third party WS when the Authenticator object is null
if (auth == null) {
logger.debug("doFilter : Proceed the authentication");
String appId = "My_APP_ID";
String redirectURL = request.getRequestURL().toString();
// Call for third party WS for get authenticate
if (WS_Authenticator.isAuthenticated(appId, redirectURL)) {
// Successfully authenticated
logger.debug("doFilter : WS authentication success");
// Get authenticated username
String userName = WS_Authenticator.getUserName();
// Put that username to request
request.setAttribute("userName", userName);
} else {
String redirectURL = WS_Authenticator.getAuthorizedURL();
logger.debug("doFilter : WS authentication failed");
logger.debug("doFilter : WS redirect URL : " + redirectURL);
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
((HttpServletResponse) response).sendRedirect(redirectURL);
// Return for bypass the filter chain
return;
}
} else {
logger.debug("doFilter : Already authenticated");
}
} catch (Exception e) {
logger.error("doFilter: " + e.getMessage());
}
super.doFilter(request, response, chain);
return;
}
Override the getPreAuthenticatedCredentials method
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// Get authenticated username
String[] credentials = new String[1];
credentials[0] = (String) request.getAttribute("userName");
return credentials;
}
Implementing a CustomAuthenticationUserDetailsServiceImpl
Override the loadUserDetails method
public class CustomAuthenticationUserDetailsServiceImpl implements AuthenticationUserDetailsService<Authentication> {
protected static final Logger logger = Logger.getLogger(CustomAuthenticationUserDetailsServiceImpl.class);
#Autowired
private UserDataService userDataService;
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
// Get authenticated username
String[] credentials = (String[]) token.getCredentials();
String userName = credentials[0];
try {
// Get user by username
User user = userDataService.getDetailsByUserName(userName);
// Get authorities username
List<String> roles = userDataService.getRolesByUserName(userName);
user.setCustomerAuthorities(roles);
return user;
} catch (Exception e) {
logger.debug("loadUserDetails: User not found! " + e.getMessage());
return null;
}
}
}

Servlet filter "returning" an object for later use

I'm implementing security in my RESTful webservice, and I'm thinking of creating a filter that checks if the Authorization header is valid or not, and this check is done by sending the token to a third party endpoint. If the token is valid, the third party endpoint has to send me a response that contains information regarding the token's expiration, client id, scope, and other stuff. The logic, then, is this:
#Override
public void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
header = req.getHeader("Authorization");
EndpointResponse eResponse = Endpoint.validate(header);
if(eResponse.valid())){
chain.doFilter(...);
return eResponse; //or equivalent
}else{
HttpServletResponse res = HttpServletResponse(response);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
...
}
}
Then, in a DAO class, I will use the eResponse like this
public final class DAO{
public void checks(){
if(eResponse.scope() == "ADMIN"){
...
}else{
...
}
}
}
Is there a way to inject or return an object after the filter does the validation? Hopefully, without using spring or hibernate, since I can't use these at my job.
-EDIT-
The way I'm accessing the DAO would be like this
#Path("")
public class CertificationService {
#GET
#Produces(CertificationApplication.SUPPORTED_REPRESENTATIONS)
#Path(CertificationConstants.URL_PATH)
public Response getCertificationByUpId(String upId) throws CertificationException {
ResponseBuilder response;
try{
response = Response.ok(DAO.findCertificationByUPID(upId));
} catch (CertificationException e) {
response = handleException(e);
}
return response.build();
}
}
The findCertificationByUPID method would have to call the checks() method I declared above.
Try placing the object on the request using setAttribute():
request.setAttribute("auth", eResponse);
Then your controller can grab the object using
EndpointResponse eResponse = (EndpointResponse) request.getAttribute("auth");
and do whatever you like with it (including passing it to the DAO):
dao.checks(eResponse);
where DAO is like what you have above, but with
public void checks(EndpointResponse eResponse) { ... }
instead.
If you prefer to keep the EndpointResponse out of the DAO, you can do
public void checks(String role) { ... }
or similar.

Categories