example ejemplo basada autenticacion spring spring-boot jwt

spring - ejemplo - Cómo diseñar un buen filtro de autenticación JWT



spring boot rest security jwt (3)

Aquí hay un filtro que puede hacer lo que necesita:

public class JWTFilter extends GenericFilterBean { private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class); private final TokenProvider tokenProvider; public JWTFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = this.resolveToken(httpServletRequest); if (StringUtils.hasText(jwt)) { if (this.tokenProvider.validateToken(jwt)) { Authentication authentication = this.tokenProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(servletRequest, servletResponse); this.resetAuthenticationAfterRequest(); } catch (ExpiredJwtException eje) { LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage()); ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); LOGGER.debug("Exception " + eje.getMessage(), eje); } } private void resetAuthenticationAfterRequest() { SecurityContextHolder.getContext().setAuthentication(null); } private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { String jwt = bearerToken.substring(7, bearerToken.length()); return jwt; } return null; } }

Y la inclusión del filtro en la cadena del filtro:

public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public final static String AUTHORIZATION_HEADER = "Authorization"; @Autowired private TokenProvider tokenProvider; @Autowired private AuthenticationProvider authenticationProvider; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(this.authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { JWTFilter customFilter = new JWTFilter(this.tokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); // @formatter:off http.authorizeRequests().antMatchers("/css/**").permitAll() .antMatchers("/images/**").permitAll() .antMatchers("/js/**").permitAll() .antMatchers("/authenticate").permitAll() .anyRequest().fullyAuthenticated() .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll() .and().logout().permitAll(); // @formatter:on http.csrf().disable(); } }

La clase TokenProvider:

public class TokenProvider { private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class); private static final String AUTHORITIES_KEY = "auth"; @Value("${spring.security.authentication.jwt.validity}") private long tokenValidityInMilliSeconds; @Value("${spring.security.authentication.jwt.secret}") private String secretKey; public String createToken(Authentication authentication) { String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(",")); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS); Date issueDate = Date.from(now.toInstant()); Date expirationDate = Date.from(expirationDateTime.toInstant()); return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities) .signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact(); } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody(); Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream() .map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken); return true; } catch (SignatureException e) { LOGGER.info("Invalid JWT signature: " + e.getMessage()); LOGGER.debug("Exception " + e.getMessage(), e); return false; } } }

Ahora para responder tus preguntas:

  1. Hecho en este filtro
  2. Proteja su solicitud HTTP, use HTTPS
  3. Solo permita todo en /login URI de /login ( /authenticate en mi código)

Soy nuevo en JWT. No hay mucha información disponible en la web, ya que vine aquí como último recurso. Ya desarrollé una aplicación de arranque de primavera usando seguridad de primavera usando la sesión de primavera. Ahora, en lugar de la sesión de primavera, nos estamos mudando a JWT. Encontré pocos enlaces y ahora puedo autenticar a un usuario y generar token. Ahora la parte difícil es que quiero crear un filtro que autentique cada solicitud al servidor,

  1. ¿Cómo validará el filtro el token? (¿Solo validar la firma es suficiente?)
  2. Si alguien más robó el token y realiza la llamada de descanso, ¿cómo lo verificaré?
  3. ¿Cómo voy a omitir la solicitud de inicio de sesión en el filtro? Dado que no tiene encabezado de autorización.

Echa un vistazo a este proyecto, está muy bien implementado y tiene la documentación necesaria.

1 . En el proyecto anterior, esto es lo único que necesita para validar el token y es suficiente. Donde token es el valor del Bearer en el encabezado de la solicitud.

try { final Claims claims = Jwts.parser().setSigningKey("secretkey") .parseClaimsJws(token).getBody(); request.setAttribute("claims", claims); } catch (final SignatureException e) { throw new ServletException("Invalid token."); }

2 . Robar el token no es tan fácil, pero en mi experiencia puede protegerse creando una sesión de Spring manualmente para cada inicio de sesión exitoso. También mapeando la ID única de sesión y el valor del Portador (el token) en un Mapa (creando un Bean por ejemplo) con alcance API).

@Component public class SessionMapBean { private Map<String, String> jwtSessionMap; private Map<String, Boolean> sessionsForInvalidation; public SessionMapBean() { this.jwtSessionMap = new HashMap<String, String>(); this.sessionsForInvalidation = new HashMap<String, Boolean>(); } public Map<String, String> getJwtSessionMap() { return jwtSessionMap; } public void setJwtSessionMap(Map<String, String> jwtSessionMap) { this.jwtSessionMap = jwtSessionMap; } public Map<String, Boolean> getSessionsForInvalidation() { return sessionsForInvalidation; } public void setSessionsForInvalidation(Map<String, Boolean> sessionsForInvalidation) { this.sessionsForInvalidation = sessionsForInvalidation; } }

Este SessionMapBean estará disponible para todas las sesiones. Ahora, en cada solicitud, no solo verificará el token sino que también comprobará si calcula la sesión (verificando que el ID de la sesión de solicitud coincida con el guardado en SessionMapBean ). Por supuesto, la identificación de la sesión también puede ser robada, por lo que debe asegurar la comunicación. Las formas más comunes de robar la identificación de la sesión son el Sniffing de sesión (o los hombres en el medio) y el ataque de script de sitios cruzados . No entraré en más detalles sobre ellos, puedes leer cómo protegerte de ese tipo de ataques.

3. Puedes verlo en el proyecto que he vinculado. Lo más simple es que el filtro validará todo /api/* y usted iniciará sesión en a /user/login por ejemplo.


Me centraré en los consejos generales sobre JWT, sin tener en cuenta la implementación del código (ver otras respuestas)

¿Cómo validará el filtro el token? (¿Solo validar la firma es suficiente?)

RFC7519 especifica cómo validar un JWT (ver 7.2 Validación de un JWT ), básicamente una validación sintáctica y verificación de firma .

Si se usa JWT en un flujo de autenticación, podemos ver la validación propuesta por la especificación de conexión OpenID 3.1.3.4 Validación de token de ID . Resumiendo:

  • iss contiene el identificador del emisor (y aud contiene client_id si usa oauth)

  • hora actual entre iat y exp

  • Validar la firma del token con la clave secreta

  • sub identifica un usuario válido

Si alguien más robó el token y realiza la llamada de descanso, ¿cómo lo verificaré?

La posesión de un JWT es la prueba de autenticación. Un atacante que robó un token puede hacerse pasar por el usuario. Así que mantén las fichas seguras

  • Encriptar el canal de comunicación usando TLS

  • Use un almacenamiento seguro para sus tokens. Si utiliza un front-end web, considere agregar medidas de seguridad adicionales para proteger localStorage / cookies contra ataques XSS o CSRF.

  • establezca un tiempo de caducidad corto en tokens de autenticación y exija credenciales si el token está vencido

¿Cómo voy a omitir la solicitud de inicio de sesión en el filtro? Dado que no tiene encabezado de autorización.

El formulario de inicio de sesión no requiere un token JWT porque va a validar la credencial del usuario. Mantenga el formulario fuera del alcance del filtro. Emita el JWT después de la autenticación exitosa y aplique el filtro de autenticación al resto de los servicios

Luego, el filtro debe interceptar todas las solicitudes, excepto el formulario de inicio de sesión, y verificar:

  1. si el usuario autenticado? Si no arrojar 401-Unauthorized

  2. si el usuario está autorizado para el recurso solicitado? Si no arrojar 403-Forbidden

  3. Acceso permitido. Poner los datos del usuario en el contexto de la solicitud (por ejemplo, utilizando un ThreadLocal)