JwtRequestFilter.java

/**
 * TFG 75.678 - TFG Desarrollo web 2020 e-Learning for Schools
 * Copyright (C) 2020  Eduardo Rodriguez Carro
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.uoc.tfg.sel.security;

import java.io.IOException;
import java.util.Date;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.uoc.tfg.sel.security.model.JWTToken;
import org.uoc.tfg.sel.security.model.UserDetailsExtended;
import org.uoc.tfg.sel.service.SessionService;

import io.jsonwebtoken.ExpiredJwtException;

/**
 * Filtro de las peticiones para inyectar el usuario si obtenemos un Token
 * Valido.
 *
 * @author Eduardo Rodriguez Carro
 */
@Profile("!test")
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
	
	/** Logger. */
	private static final Logger logger = LoggerFactory.getLogger(JwtRequestFilter.class);
	
	
	/** Servicio de datos de usuario de Spring Security. */
	@Autowired
	private UserDetailsService userDetailsService;

	/** The session service. */
	@Autowired
	private SessionService sessionService;
	
	/** Utilidades de JWT. */
	@Autowired
	private JwtUtil jwtTokenUtil;

	/**
	 * Filtro de la peticion.
	 *
	 * @param request  the request
	 * @param response the response
	 * @param chain    the chain
	 * @throws ServletException the servlet exception
	 * @throws IOException      Signals that an I/O exception has occurred.
	 */
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

		final String requestTokenHeader = request.getHeader("Authorization");

		JWTToken token = null;
		String username = null;

		// JWT Token "Bearer token"
		if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
			try {
				String jwtTokenStr = requestTokenHeader.substring(7);
				token = jwtTokenUtil.getToken(jwtTokenStr);
				username = token.getSubject();
			
			// En caso de error dejamos un log pero no lanzamos excepcion
			// Mas adelante el filtro de securidad de spring lo tratara como un problema
			} catch (IllegalArgumentException e) {
				logger.warn("Unable to get JWT Token");
			} catch (ExpiredJwtException e) {
				logger.warn("JWT Token has expired");
			}
		} else {
			logger.warn("JWT Token does not begin with Bearer String");
		}

		
		// Si tenemos token pero no tenemos aun contexto de autenticacion
		// Esto evita llamar al filtro varias veces en el mismo contexto de peticion
		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

			
			UserDetails userDetails = userDetailsService.loadUserByUsername(username);

			String sessionId = token.getSessionId();
			
			// Si el token es valido para con el usuario y tenemos usuario, entonces procedemos a crear un objeto de autenticacion de spring
			// con todos sus permisos para poder aplicarlos sobre las API
			if (userDetails != null && validateToken(token, userDetails) && sessionService.hasSession(sessionId) ){
				if(userDetails instanceof UserDetailsExtended) {
					((UserDetailsExtended)userDetails).setSessionId(sessionId);
				}
				 
				UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
				usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
				
				// Paso del contexto de autenticacion a Spring security para acceder a las APIs protegidas por ruta (WebSecurityConfig) y
				// a las anotadas con un rol especifico
				SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
				
			}
		}
		
		
	
		chain.doFilter(request, response);
	}

	/**
	 * Validacion del token.
	 *
	 * @param token       the token
	 * @param userDetails the user details
	 * @return the boolean
	 */
	public Boolean validateToken(JWTToken token, UserDetails userDetails) {
		// ha de ser para este usuario
		// ha de tener una fecha de expiracion posterior
		return token.getSubject().equals(userDetails.getUsername()) && !token.getExpiration().before(new Date());
	}
}