05 jan 2016

Autenticação com Spring Security + JWT

O que é o JWT?

Json Web Token é um padrão aberto para transmissão de informações de forma segura entre partes.

O JWT pode ser utilizados para várias tarefas, mas nesse post vamos utilizar para autenticação de usuários.

A vantagem de se utilizar o JWT, é que toda a informação referente ao usuário autenticado, já está toda guardada nesse token, então todas as vezes que fizermos a chamada de uma API passando o token, a aplicação não precisa ir ao banco de dados buscar informações sobre o usuário autenticado, pois podemos armazenar todas essas informações dentro do token.
Um JWT é dividido em 3 partes que são separadas por pontos, um JWT se parece com isso: xxxxx.yyyyy.zzzzz

1 – Header: contém duas informações, o tipo do token, que no caso é JWT e o algoritmo que foi utilizado na criptografia.

2 – Payload: aqui que vamos colocar as informações sobre o usuário, tempo de expiração do token, etc.

3 – Signature: Para criar a assinatura é preciso fazer o encode do header, payload, secret e do algoritmo especificado no header.

<!–more–>


signature(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

Overview

O usuário irá se autenticar na aplicação utilizando o serviço de login, após logado, será criado um token JWT com todas as informações do usuário e esse token será adicionado na resposta do login. A aplicação cliente que efetuou o login, deve guardar esse token para futuras requisições.

CTA-ebook-transformação-digital

Vamos a implementação agora.

Podemos criar nosso projeto utilizando o  http://start.spring.io/ para facilitar um pouco, devemos selecionar somente as dependências do Web e Security.

Segundo passo é adicionar a dependência do JWT ao projeto, existem várias bibliotecas, utilizaremos a seguinte:

https://github.com/jwtk/jjwt


<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>

Configurando o Spring Security:


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

@Autowired
private UserDetailsService userDetailsService;

public WebSecurityConfig() {
super(true);
}

@Override
protected void configure(HttpSecurity http) throws Exception {

TokenAuthenticationService tokenAuthenticationService = new TokenAuthenticationService("REDSPARK_SECRET", userDetailsService);
JWTAuthenticationSuccessHandler athenticationSuccessHandler = new JWTAuthenticationSuccessHandler(tokenAuthenticationService);
JWTAuthenticationFailureHandler authenticationFailureHandler = new JWTAuthenticationFailureHandler();

http.formLogin().loginProcessingUrl("/login").successHandler(athenticationSuccessHandler).failureHandler(authenticationFailureHandler)
.and().exceptionHandling().and().anonymous().and().authorizeRequests()
.antMatchers("/login").permitAll().and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
}
}

Pontos importantes dessa configuração:

  • No construtor foi desabilitado a configuração padrão do framework.
  • Foi instanciando a classeTokenAuthenticationService responsável por adicionar e retirar o token do header, a string "REDSPARK_SECRET" é o nosso segredo, utilizado para criar e ler os tokens.

Na classe seguinte, estamos criando o nosso UserDetailsService, que é responsável por recuperar o usuário.


@Configuration
public class WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter {

@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}

@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, username, true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_USUARIO"));
}

};
}
}

Depois que autenticação é realizada com sucesso no login, a requisição passa pelo JWTAuthenticationSuccessHandler, a função dessa classe é chamar o serviço para criar o token e adicionar no header do response.


public class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler{

private TokenAuthenticationService tokenService;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
tokenService.addAuthentication(response, authentication);
}

public JWTAuthenticationSuccessHandler(TokenAuthenticationService tokenService) {
super();
this.tokenService = tokenService;
}

}

TokenAuthenticationService é responsável por adicionar e retirar o token do header da requisição.


public class TokenAuthenticationService {

private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";

private final TokenHandler tokenHandler;

public TokenAuthenticationService(String secret, UserDetailsService userService) {
tokenHandler = new TokenHandler(secret, userService);
}

public void addAuthentication(HttpServletResponse response, Authentication authentication) {
final User user = (User) authentication.getPrincipal();
response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));
}

public Authentication getAuthentication(HttpServletRequest request) {
final String token = request.getHeader(AUTH_HEADER_NAME);
if (token != null) {
final User user = tokenHandler.parseUserFromToken(token);
if (user != null) {
return new UserAuthentication(user);
}
}
return null;
}
}

A classe TokenHandler é responsável por criar e ler os tokens.


public class TokenHandler {

private final String secret;
private final UserDetailsService userService;

public TokenHandler(String secret, UserDetailsService userService) {
this.secret = secret;
this.userService = userService;
}

public User parseUserFromToken(String token) {
String username = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
return (User) userService.loadUserByUsername(username);
}

public String createTokenForUser(User user) {
return Jwts.builder().setSubject(user.getUsername()).signWith(SignatureAlgorithm.HS256, secret).compact();
}
}

StatelessAuthenticationFilter é o filtro que verifica se existe um token nas requisições.


public class StatelessAuthenticationFilter extends GenericFilterBean {

final TokenAuthenticationService authenticationService;

public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
Authentication authentication = authenticationService.getAuthentication(httpRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
}

}

No post foi descrito as principais classes para realizar a configuração, no github você pode encontrar o projeto completo.

https://github.com/caioferreira/SpringSecurityJWT

Espero que tenham gostado e até o próximo post.

CTA-ebook-transformação-digital

Comments

  • Tiago
    julho 3, 2017 Responder

    Como eu faço uma requisição de token para o servidor. Por exemplo, estou usando o angular, como eu envio o login e senha para esse servidor para que ele faça a autenticação e crie um token e me devolva o mesmo. Eu poderia passar como json os dados do usuário ou enviar pelo header, mas não vi aonde você implementou isso na configuração mostrada acima. Obs.: Dei uma olhada no código que está no github.

Leave a Comment