API RESTful com a JAX-RS API que demostrei no meu artigo passado.
Para gerar o token, utilizaremos a biblioteca jjwt, assim, a primeira coisa a ser feita na aplicação é a adição das dependências dela:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
Com as dependências adicionadas, criaremos uma nova entidade chamada Usuario
:
public class Usuario {
private int idUsuario;
private String usuario;
private String senha;
public int getIdUsuario() {
return idUsuario;
}
public String getSenha() {
return senha;
}
public void setSenha(String senha) {
this.senha = senha;
}
public String getUsuario() {
return usuario;
}
public void setUsuario(String usuario) {
this.usuario = usuario;
}
public void setIdUsuario(int idUsuario) {
this.idUsuario = idUsuario;
}
}
E o recurso abaixo:
@Path("/login")
public class LoginResource {
private final SecretKey CHAVE = Keys.hmacShaKeyFor(
"7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
.getBytes(StandardCharsets.UTF_8));
@POST
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Usuario usuario)
{
try{
if(
usuario.getUsuario().equals("teste@treinaweb.com.br")
&&
usuario.getSenha().equals("1234")
)
{
String jwtToken = Jwts.builder()
.setSubject(usuario.getUsuario())
.setIssuer("localhost:8080")
.setIssuedAt(new Date())
.setExpiration(
Date.from(
LocalDateTime.now().plusMinutes(15L)
.atZone(ZoneId.systemDefault())
.toInstant()))
.signWith(CHAVE, SignatureAlgorithm.RS512)
.compact();
return Response.status(Response.Status.OK).entity(jwtToken).build();
}
else
return Response.status(Response.Status.UNAUTHORIZED)
.entity("Usuário e/ou senha inválidos").build();
}
catch(Exception ex)
{
return Response.status(
Response.Status.INTERNAL_SERVER_ERROR
).entity(ex.getMessage())
.build();
}
}
}
Note que nele há apenas um método, que realizará o login do usuário. Por esta ser uma aplicação simples, o usuário e senha válidos já estão definidos no código. Ao autenticar, será gerado um token:
String jwtToken = Jwts.builder()
.setSubject(usuario.getUsuario())
.setIssuer("localhost:8080")
.setIssuedAt(new Date())
.setExpiration(
Date.from(
LocalDateTime.now().plusMinutes(15L)
.atZone(ZoneId.systemDefault())
.toInstant()))
.signWith(CHAVE, SignatureAlgorithm.RS512)
.compact();
Onde, é informado:
Das informações, a mais importante é a assinatura do token. Neste exemplo ela utiliza uma chave textual visível:
private final SecretKey CHAVE = Keys.hmacShaKeyFor(
"7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
.getBytes(StandardCharsets.UTF_8));
Entretanto em uma aplicação real, esta chave precisa ser bem protegida, pois é através dela que os tokens serão validados pela aplicação. Assim, se uma pessoa mal-intencionada obter esta informação, ela poderá gerar tokens válidos e terá acesso aos recursos protegidos da API.
Ao testar o endpoint, se for informado o usuário e senha corretos, um token será gerado:
Caso contrário será retornado 401 Unauthorized:
Com a geração do token pronta, temos que proteger nossos endpoints, o que veremos a seguir.
Como vimos acima, o token será enviado no header Authorization da solicitação. Assim, para autenticá-las é necessário analisar este header e verificar se o token informado é valido. Apenas nessas situações as solicitações devem ser permitidas.
Para fazer isso, podemos utilizar o @NameBinding
, que é uma meta annotation que nos permite definir filtros e interceptadores no pipeline da solicitação. Estes filtros são implementados via anotação, assim, inicialmente uma anotação deve ser definida:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface Authorize { }
Onde utilizamos o @NameBinding
e indicamos que a anotação será aplicada em métodos.
É na implementação desta anotação que verificamos a validade do token:
@Provider
@Authorize
@Priority(Priorities.AUTHENTICATION)
public class AuthorizeFilter implements ContainerRequestFilter {
private final SecretKey CHAVE =
Keys.hmacShaKeyFor("7f-j&CKk=coNzZc0y7_4obMP?#TfcYq%fcD0mDpenW2nc!lfGoZ|d?f&RNbDHUX6"
.getBytes(StandardCharsets.UTF_8));
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authorizationHeader = requestContext
.getHeaderString(HttpHeaders.AUTHORIZATION);
try {
String token = authorizationHeader.substring("Bearer".length()).trim();
Jwts.parserBuilder()
.setSigningKey(CHAVE)
.build()
.parseClaimsJws(token);
} catch (Exception e) {
requestContext
.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.build());
}
}
}
Inicialmente é indicado que a classe irá prover (@Provider
) a implementação a nossa anotação (@Authorize
) e que a prioridade dela no pipeline do JAX-RS é de autenticação (AUTHENTICATION
). Ou seja, ela será executada antes dos endpoints.
Na classe, é obtido o token do header da solicitação:
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
String token = authorizationHeader.substring("Bearer".length()).trim();
Em seguida é validado:
Jwts.parserBuilder()
.setSigningKey(CHAVE)
.build()
.parseClaimsJws(token);
O método parseClaimsJws
obtém os dados presentes no token como: Subject, Issuer, etc; caso haja algo errado, é gerada uma exceção. Assim, caso ocorra qualquer erro é retornado que o usuário não tem permissão de acesso:
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
Com a anotação definida, ela pode ser aplicada aos endpoints:
@Path("/pessoa")
public class PessoaResource {
private PessoaRepository _repositorio = new PessoaRepository();
@Authorize
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Pessoa> get() {
return _repositorio.GetAll();
}
@Authorize
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Pessoa getById(@PathParam("id") int id) {
return _repositorio.Get(id);
}
@Authorize
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response post(Pessoa pessoa)
{
try{
_repositorio.Add(pessoa);
return Response.status(Response.Status.CREATED).entity(pessoa).build();
}
catch(Exception ex)
{
return Response.status(
Response.Status.INTERNAL_SERVER_ERROR
).entity(ex.getMessage())
.build();
}
}
@Authorize
@PUT
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response put(@PathParam("id") int id, Pessoa pessoa)
{
Pessoa p = _repositorio.Get(id);
if(p == null)
return Response.status(Response.Status.NOT_FOUND).build();
try{
pessoa.setId(id);
_repositorio.Edit(pessoa);
return Response.status(Response.Status.OK).entity(pessoa).build();
}
catch(Exception ex)
{
return Response.status(
Response.Status.INTERNAL_SERVER_ERROR
).entity(ex.getMessage())
.build();
}
}
@Authorize
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(@PathParam("id") int id)
{
Pessoa p = _repositorio.Get(id);
if(p == null)
return Response.status(Response.Status.NOT_FOUND).build();
try{
_repositorio.Delete(id);
return Response.status(Response.Status.OK).build();
}
catch(Exception ex)
{
return Response.status(
Response.Status.INTERNAL_SERVER_ERROR
).entity(ex.getMessage())
.build();
}
}
}
Para testá-la vamos utilizar o Postman.
Inicialmente gere um token:
Com o token gerado, ele deve ser informado na aba Auth do Postman:
Não se esqueça de definir o tipo como Bearer Token. Com isso, ao enviar uma solicitação, ela será aceita:
Se o token não for informado, ou caso seja inválido, será retornado 401 Unauthorized:
Neste artigo vimos uma implementação simples de autenticação utilizando o padrão JWT. Um padrão bem difundido e muito utilizado, então caso necessite implementar este tipo de autenticação na sua aplicação não deixe de dar uma olhada nele.
Mas independente do padrão utilizado, procure não deixar suas aplicações expostas, sempre adote um sistema de autenticação e conexão segura, HTTPS.
Por fim, você pode ver a aplicação demonstrada aqui no meu Github.
Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.
Todos os artigosVeja como implementar o HATEOAS em uma aplicação JAX-RS-Jersey de forma simples com os recursos forn...
Neste artigo, abordaremos a new generation, a old generation e a permanent generation na JVM, compre...
Qual linguagem escolher? Java, C# ou PHP? Quais são as vantagens de cada uma? Vamos analisar estas d...
Neste artigo veremos como instalar o Java nos sistemas Windows, Linux e MacOS, além disso vamos ver...
Quer iniciar seus estudos em JavaScript e não sabe por onde começar? Veja o que preparamos pra você.
Quer iniciar seus estudos em Java e não sabe por onde começar? Veja o que preparamos pra você.
Neste Guia da Linguagem Javascript vamos abordar aspectos fundamentais, como: tipos de dados, variáv...
Confira neste guia os principais tópicos para iniciar seus estudos na linguagem Java.
Muitas pessoas acreditam que JSON e Objeto JavaScript são a mesma coisa. Descubra a diferença.
É perceptível a vocação do Java para estruturas orientadas a objetos. Veremos um pouco mais sobre ne...
Conheça a história e as curiosidades por trás da criação do ECMAScript e JavaScript. Entenda de uma...
Veja neste artigo as principais IDEs para desenvolvimento em Java.