├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── application.properties │ │ └── static │ │ │ ├── login.js │ │ │ ├── login.html │ │ │ ├── index.html │ │ │ ├── fetch.js │ │ │ └── index.js │ └── java │ │ └── com │ │ └── quasarconsultoria │ │ └── jwtspringsec │ │ ├── comum │ │ └── AcessoNegadoException.java │ │ ├── login │ │ ├── CredenciaisInvalidasException.java │ │ ├── CredenciaisDTO.java │ │ ├── JWTUsernamePasswordAuthenticationFilter.java │ │ ├── LoginController.java │ │ ├── JWTBasicAuthenticationFilter.java │ │ └── LoginFilter.java │ │ ├── model │ │ ├── TarefasRepository.java │ │ ├── UsuariosRepository.java │ │ ├── Usuario.java │ │ └── Tarefa.java │ │ ├── tarefas │ │ ├── NovaTarefaDTO.java │ │ ├── TarefaDTO.java │ │ ├── TarefaDetalhadaDTO.java │ │ └── TarefasController.java │ │ ├── JwtspringsecApplication.java │ │ ├── MimeMappingsConfigurer.java │ │ └── WebSecurityConfig.java └── test │ └── java │ └── com │ └── quasarconsultoria │ └── jwtspringsec │ └── JwtspringsecApplicationTests.java ├── README.md ├── .gitignore ├── banco.sql ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jwtspringsec' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelgrigolato/jwtspringsec/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | spring.datasource.url=jdbc:postgresql://localhost:5432/tarefas 3 | spring.datasource.username=postgres 4 | spring.datasource.password=postgres 5 | 6 | spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false 7 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/comum/AcessoNegadoException.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.comum; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.FORBIDDEN) 7 | public class AcessoNegadoException extends RuntimeException { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/CredenciaisInvalidasException.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | class CredenciaisInvalidasException extends RuntimeException { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/model/TarefasRepository.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.model; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import java.util.List; 6 | 7 | public interface TarefasRepository extends CrudRepository { 8 | 9 | List findByUsuario(Usuario usuario); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/tarefas/NovaTarefaDTO.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.tarefas; 2 | 3 | class NovaTarefaDTO { 4 | 5 | private String descricao; 6 | 7 | String getDescricao() { 8 | return descricao; 9 | } 10 | 11 | void setDescricao(String descricao) { 12 | this.descricao = descricao; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/model/UsuariosRepository.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.model; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface UsuariosRepository extends CrudRepository { 8 | 9 | Optional findByLogin(String login); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/static/login.js: -------------------------------------------------------------------------------- 1 | 2 | function entrar() { 3 | const data = { 4 | usuario: document.getElementsByName("usuario")[0].value, 5 | senha: document.getElementsByName("senha")[0].value 6 | }; 7 | Fetch.post("/api/login", data) 8 | .then(() => { 9 | window.location = "/"; 10 | }) 11 | .catch(_ => { 12 | alert("Credenciais inválidas."); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autenticação com JWT e Spring Security 2 | 3 | Exemplo de implementação de JWT e Spring Security. Este repositório foi utilizado como base para a seguinte apresentação: https://www.youtube.com/watch?v=idnwOWqUsEQ. 4 | 5 | O código presente aqui não é livre de brechas de segurança (principalmente o filtro usado como exemplo) nem é garantido que segue todas as melhores práticas de codificação, portanto use-o com moderação e responsabilidade :). 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/JwtspringsecApplication.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JwtspringsecApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JwtspringsecApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /src/test/java/com/quasarconsultoria/jwtspringsec/JwtspringsecApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class JwtspringsecApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Login 5 | 6 | 7 |
8 |

9 | Usuário: 10 | 11 |

12 |

13 | Senha: 14 | 15 |

16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/CredenciaisDTO.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | class CredenciaisDTO { 4 | 5 | private String usuario; 6 | private String senha; 7 | 8 | String getUsuario() { 9 | return usuario; 10 | } 11 | 12 | void setUsuario(String usuario) { 13 | this.usuario = usuario; 14 | } 15 | 16 | String getSenha() { 17 | return senha; 18 | } 19 | 20 | void setSenha(String senha) { 21 | this.senha = senha; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/tarefas/TarefaDTO.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.tarefas; 2 | 3 | import com.quasarconsultoria.jwtspringsec.model.Tarefa; 4 | 5 | class TarefaDTO { 6 | 7 | private Integer id; 8 | private String descricao; 9 | 10 | TarefaDTO(Tarefa entidade) { 11 | this.id = entidade.getId(); 12 | this.descricao = entidade.getDescricao(); 13 | } 14 | 15 | public Integer getId() { 16 | return id; 17 | } 18 | 19 | public String getDescricao() { 20 | return descricao; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tarefas 5 | 6 | 7 |
    8 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /banco.sql: -------------------------------------------------------------------------------- 1 | create table usuarios ( 2 | id int not null primary key, 3 | login varchar(50) not null unique, 4 | senha varchar(100) not null 5 | ); 6 | insert into usuarios (id, login, senha) 7 | values (1, 'admin', '7288e2461e79f7347a2596d93a5d598ae61d786b'), 8 | (2, 'samuel', '3f583a54949f5b43223c81315b80efb1b3dd160d'); 9 | create table tarefas ( 10 | id serial not null primary key, 11 | descricao varchar(500) not null, 12 | criada_em timestamp with time zone not null, 13 | usuario_id int not null, 14 | 15 | constraint fk_tarefas_usuario 16 | foreign key (usuario_id) 17 | references usuarios (id) 18 | ); 19 | insert into tarefas (descricao, created_at, usuario_id) 20 | values ('Excluir o usuário "samuel".', '2005-03-18 11:59:58', 1), 21 | ('Excluir o usuário "admin".', '2005-03-18 11:59:59', 2); 22 | -- select * from usuarios; 23 | -- select * from tarefas; 24 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/tarefas/TarefaDetalhadaDTO.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.tarefas; 2 | 3 | import com.quasarconsultoria.jwtspringsec.model.Tarefa; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | class TarefaDetalhadaDTO { 8 | 9 | private String descricao; 10 | private LocalDateTime criadaEm; 11 | 12 | TarefaDetalhadaDTO(Tarefa entidade) { 13 | this.descricao = entidade.getDescricao(); 14 | this.criadaEm = entidade.getCriadaEm(); 15 | } 16 | 17 | public String getDescricao() { 18 | return descricao; 19 | } 20 | 21 | void setDescricao(String descricao) { 22 | this.descricao = descricao; 23 | } 24 | 25 | public LocalDateTime getCriadaEm() { 26 | return criadaEm; 27 | } 28 | 29 | void setCriadaEm(LocalDateTime criadaEm) { 30 | this.criadaEm = criadaEm; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/model/Usuario.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.model; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Id; 5 | import javax.persistence.Table; 6 | 7 | @Entity 8 | @Table(name = "usuarios") 9 | public class Usuario { 10 | 11 | @Id 12 | private Integer id; 13 | private String login; 14 | private String senha; 15 | 16 | public Integer getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Integer id) { 21 | this.id = id; 22 | } 23 | 24 | public String getLogin() { 25 | return login; 26 | } 27 | 28 | public void setLogin(String login) { 29 | this.login = login; 30 | } 31 | 32 | public String getSenha() { 33 | return senha; 34 | } 35 | 36 | public void setSenha(String senha) { 37 | this.senha = senha; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/MimeMappingsConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec; 2 | 3 | import org.springframework.boot.web.server.MimeMappings; 4 | import org.springframework.boot.web.server.WebServerFactoryCustomizer; 5 | import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class MimeMappingsConfigurer implements WebServerFactoryCustomizer { 10 | 11 | @Override 12 | public void customize(ConfigurableServletWebServerFactory factory) { 13 | MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); 14 | mappings.add("html", "text/html; charset=utf-8"); 15 | mappings.add("js", "application/javascript; charset=utf-8"); 16 | mappings.add("css", "text/css; charset=utf-8"); 17 | factory.setMimeMappings(mappings); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/static/fetch.js: -------------------------------------------------------------------------------- 1 | 2 | Fetch = { 3 | 4 | get: url => { 5 | return new Promise((resolve, reject) => { 6 | fetch(url) 7 | .then(resp => { 8 | if (resp.status == 401) { 9 | window.location = "/login.html"; 10 | } else if (resp.status !== 200) { 11 | reject("Não foi possível executar a operação."); 12 | } else { 13 | resp.json().then(dados => resolve(dados)); 14 | } 15 | }) 16 | .catch(_ => { 17 | alert("Servidor indisponível."); 18 | }); 19 | }); 20 | }, 21 | 22 | post: (url, dados) => { 23 | return new Promise((resolve, reject) => { 24 | fetch(url, { 25 | headers: { 26 | 'Content-Type': 'application/json' 27 | }, 28 | method: "POST", 29 | body: JSON.stringify(dados) 30 | }) 31 | .then(resp => { 32 | if (resp.status == 401) { 33 | window.location = "/login.html"; 34 | } else if (resp.status !== 200) { 35 | reject("Não foi possível executar a operação."); 36 | } else { 37 | resolve(); 38 | } 39 | }) 40 | .catch(_ => { 41 | alert("Servidor indisponível."); 42 | }); 43 | }); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/model/Tarefa.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.model; 2 | 3 | import javax.persistence.*; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | import static javax.persistence.GenerationType.IDENTITY; 8 | 9 | @Entity 10 | @Table(name = "tarefas") 11 | public class Tarefa { 12 | 13 | @Id 14 | @GeneratedValue(strategy = IDENTITY) 15 | private Integer id; 16 | 17 | private String descricao; 18 | 19 | private LocalDateTime criadaEm; 20 | 21 | @ManyToOne 22 | @JoinColumn(name = "usuario_id") 23 | private Usuario usuario; 24 | 25 | public Integer getId() { 26 | return id; 27 | } 28 | 29 | public void setId(Integer id) { 30 | this.id = id; 31 | } 32 | 33 | public String getDescricao() { 34 | return descricao; 35 | } 36 | 37 | public void setDescricao(String descricao) { 38 | this.descricao = descricao; 39 | } 40 | 41 | public LocalDateTime getCriadaEm() { 42 | return criadaEm; 43 | } 44 | 45 | public void setCriadaEm(LocalDateTime criadaEm) { 46 | this.criadaEm = criadaEm; 47 | } 48 | 49 | public Usuario getUsuario() { 50 | return usuario; 51 | } 52 | 53 | public void setUsuario(Usuario usuario) { 54 | this.usuario = usuario; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/JWTUsernamePasswordAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.Cookie; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | public class JWTUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 17 | 18 | @Override 19 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 20 | 21 | User user = (User) authResult.getPrincipal(); 22 | String login = user.getUsername(); 23 | 24 | String jwt = JWT.create() 25 | .withClaim("login", login) 26 | .sign(Algorithm.HMAC256("algosecretoaqui")); 27 | 28 | Cookie cookie = new Cookie("token", jwt); 29 | cookie.setPath("/"); 30 | cookie.setHttpOnly(true); 31 | cookie.setMaxAge(60 * 30); // 30 minutos 32 | response.addCookie(cookie); 33 | 34 | super.successfulAuthentication(request, response, chain, authResult); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/static/index.js: -------------------------------------------------------------------------------- 1 | 2 | function carregarTarefas() { 3 | Fetch.get("/api/tarefas").then(tarefas => { 4 | const $ul = document.getElementById("tarefas"); 5 | $ul.innerHTML = ""; // limpa o elemento 6 | tarefas.forEach(tarefa => { 7 | const $li = document.createElement("li"); 8 | $li.innerText = tarefa.descricao; 9 | const $a = document.createElement("a"); 10 | $a.onclick = () => detalhar(tarefa.id); 11 | $a.innerText = " (detalhar)"; 12 | $li.appendChild($a); 13 | $ul.appendChild($li); 14 | }); 15 | }); 16 | } 17 | 18 | function detalhar(id) { 19 | Fetch.get(`/api/tarefas/${id}`).then(tarefa => { 20 | const $detalhes = document.getElementById("detalhes"); 21 | $detalhes.innerHTML = ""; // limpa o elemento 22 | 23 | const $titulo = document.createElement("h1"); 24 | $titulo.innerText = `Tarefa ${id}`; 25 | $detalhes.appendChild($titulo); 26 | 27 | const $descricao = document.createElement("p"); 28 | $descricao.innerText = tarefa.descricao; 29 | $detalhes.appendChild($descricao); 30 | 31 | const $cadastradaEm = document.createElement("p"); 32 | $cadastradaEm.innerText = `Cadastrada em ${tarefa.criadaEm}`; 33 | $detalhes.appendChild($cadastradaEm); 34 | }); 35 | } 36 | 37 | function cadastrar() { 38 | const dados = { 39 | descricao: document.getElementsByName("descricao")[0].value 40 | }; 41 | Fetch.post("/api/tarefas", dados).then(() => { 42 | const $form = document.getElementById("formulario"); 43 | $form.reset(); 44 | carregarTarefas(); 45 | }); 46 | } 47 | 48 | carregarTarefas(); 49 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.quasarconsultoria.jwtspringsec.model.Usuario; 6 | import com.quasarconsultoria.jwtspringsec.model.UsuariosRepository; 7 | import org.apache.commons.codec.digest.DigestUtils; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import javax.servlet.http.Cookie; 11 | import javax.servlet.http.HttpServletResponse; 12 | import javax.servlet.http.HttpSession; 13 | import java.util.Optional; 14 | 15 | //@RestController 16 | //@RequestMapping("/api/login") 17 | class LoginController { 18 | 19 | private UsuariosRepository usuariosRepository; 20 | 21 | LoginController(UsuariosRepository usuariosRepository) { 22 | this.usuariosRepository = usuariosRepository; 23 | } 24 | 25 | @PostMapping 26 | void login(@RequestBody CredenciaisDTO credenciais, HttpServletResponse response) { 27 | Optional talvezUsuario = this.usuariosRepository 28 | .findByLogin(credenciais.getUsuario()); 29 | if (talvezUsuario.isEmpty()) { 30 | throw new CredenciaisInvalidasException(); 31 | } 32 | String senhaCriptografada = criptografar(credenciais.getSenha()); 33 | Usuario usuario = talvezUsuario.get(); 34 | if (!usuario.getSenha().equals(senhaCriptografada)) { 35 | throw new CredenciaisInvalidasException(); 36 | } 37 | 38 | String jwt = JWT.create() 39 | .withClaim("idUsuarioLogado", usuario.getId()) 40 | .sign(Algorithm.HMAC256("algosecretoaqui")); 41 | 42 | Cookie cookie = new Cookie("token", jwt); 43 | cookie.setPath("/"); 44 | cookie.setHttpOnly(true); 45 | cookie.setMaxAge(60 * 30); // 30 minutos 46 | response.addCookie(cookie); 47 | } 48 | 49 | private String criptografar(String senha) { 50 | return DigestUtils.sha1Hex(senha + "algoaqui"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/JWTBasicAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.auth0.jwt.exceptions.JWTVerificationException; 6 | import com.auth0.jwt.interfaces.DecodedJWT; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 14 | import org.springframework.web.util.WebUtils; 15 | 16 | import javax.servlet.FilterChain; 17 | import javax.servlet.ServletException; 18 | import javax.servlet.http.Cookie; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | public class JWTBasicAuthenticationFilter extends BasicAuthenticationFilter { 26 | 27 | public JWTBasicAuthenticationFilter(AuthenticationManager authenticationManager) { 28 | super(authenticationManager); 29 | } 30 | 31 | @Override 32 | protected void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException { 33 | 34 | Cookie token = WebUtils.getCookie(httpRequest, "token"); 35 | if (token == null) { 36 | chain.doFilter(httpRequest, httpResponse); 37 | return; 38 | } 39 | 40 | try { 41 | 42 | String jwt = token.getValue(); 43 | 44 | DecodedJWT decodedJwt = JWT.require(Algorithm.HMAC256("algosecretoaqui")) 45 | .build() 46 | .verify(jwt); 47 | 48 | String login = decodedJwt.getClaim("login").asString(); 49 | 50 | List authorities = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); 51 | UsernamePasswordAuthenticationToken authentication = 52 | new UsernamePasswordAuthenticationToken(login, null, authorities); 53 | SecurityContextHolder.getContext().setAuthentication(authentication); 54 | 55 | // chamada autenticada 56 | chain.doFilter(httpRequest, httpResponse); 57 | 58 | } catch (JWTVerificationException ex) { 59 | httpResponse.sendError(HttpStatus.UNAUTHORIZED.value()); 60 | return; 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/tarefas/TarefasController.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.tarefas; 2 | 3 | import com.quasarconsultoria.jwtspringsec.comum.AcessoNegadoException; 4 | import com.quasarconsultoria.jwtspringsec.model.Tarefa; 5 | import com.quasarconsultoria.jwtspringsec.model.TarefasRepository; 6 | import com.quasarconsultoria.jwtspringsec.model.Usuario; 7 | import com.quasarconsultoria.jwtspringsec.model.UsuariosRepository; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import javax.servlet.http.HttpSession; 11 | import java.security.Principal; 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @RestController 17 | @RequestMapping("/api/tarefas") 18 | class TarefasController { 19 | 20 | private TarefasRepository tarefasRepository; 21 | private UsuariosRepository usuariosRepository; 22 | 23 | TarefasController(TarefasRepository tarefasRepository, 24 | UsuariosRepository usuariosRepository) { 25 | this.tarefasRepository = tarefasRepository; 26 | this.usuariosRepository = usuariosRepository; 27 | } 28 | 29 | @GetMapping 30 | List buscarTodas(Principal principal) { 31 | String loginDoUsuario = principal.getName(); 32 | Usuario usuario = this.usuariosRepository.findByLogin(loginDoUsuario).get(); 33 | return this.tarefasRepository 34 | .findByUsuario(usuario).stream() 35 | .map(TarefaDTO::new) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | @GetMapping("/{id}") 40 | TarefaDetalhadaDTO buscarPorId(@PathVariable("id") Integer id, Principal principal) { 41 | String loginDoUsuario = principal.getName(); 42 | Usuario usuario = this.usuariosRepository.findByLogin(loginDoUsuario).get(); 43 | Tarefa tarefa = this.tarefasRepository.findById(id).get(); 44 | if (!tarefa.getUsuario().getId().equals(usuario.getId())) { 45 | throw new AcessoNegadoException(); 46 | } 47 | return new TarefaDetalhadaDTO(tarefa); 48 | } 49 | 50 | @PostMapping 51 | void cadastrar(@RequestBody NovaTarefaDTO tarefa, Principal principal) { 52 | String loginDoUsuario = principal.getName(); 53 | Usuario usuario = this.usuariosRepository.findByLogin(loginDoUsuario).get(); 54 | Tarefa entidade = new Tarefa(); 55 | entidade.setUsuario(usuario); 56 | entidade.setDescricao(tarefa.getDescricao()); 57 | entidade.setCriadaEm(LocalDateTime.now()); 58 | this.tarefasRepository.save(entidade); 59 | } 60 | 61 | private Usuario getUsuarioLogado(HttpSession session) { 62 | Integer idUsuarioLogado = (Integer)session.getAttribute("idUsuarioLogado"); 63 | return this.usuariosRepository.findById(idUsuarioLogado).get(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/login/LoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec.login; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.auth0.jwt.exceptions.JWTVerificationException; 6 | import com.auth0.jwt.interfaces.DecodedJWT; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.util.WebUtils; 11 | 12 | import javax.servlet.*; 13 | import javax.servlet.http.Cookie; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import javax.servlet.http.HttpSession; 17 | import java.io.IOException; 18 | 19 | //@Component 20 | //@Order(1) 21 | public class LoginFilter implements Filter { 22 | 23 | @Override 24 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 25 | 26 | HttpServletResponse httpResponse = (HttpServletResponse)response; 27 | HttpServletRequest httpRequest = (HttpServletRequest)request; 28 | 29 | if (!httpRequest.getServletPath().startsWith("/api")) { 30 | // requisição para recurso estático 31 | chain.doFilter(request, response); 32 | return; 33 | } 34 | 35 | if (httpRequest.getServletPath().startsWith("/api/login")) { 36 | // o usuário está tentando se autenticar 37 | chain.doFilter(request, response); 38 | return; 39 | } 40 | 41 | // HttpSession session = httpRequest.getSession(false); 42 | // if (session == null || session.getAttribute("idUsuarioLogado") == null) { 43 | // // chamada sem autenticação 44 | // httpResponse.sendError(HttpStatus.UNAUTHORIZED.value()); 45 | // return; 46 | // } 47 | 48 | Cookie token = WebUtils.getCookie(httpRequest, "token"); 49 | if (token == null) { 50 | httpResponse.sendError(HttpStatus.UNAUTHORIZED.value()); 51 | return; 52 | } 53 | 54 | try { 55 | 56 | String jwt = token.getValue(); 57 | 58 | DecodedJWT decodedJwt = JWT.require(Algorithm.HMAC256("algosecretoaqui")) 59 | .build() 60 | .verify(jwt); 61 | 62 | Integer idUsuarioLogado = decodedJwt.getClaim("idUsuarioLogado").asInt(); 63 | httpRequest.setAttribute("idUsuarioLogado", idUsuarioLogado); 64 | 65 | // chamada autenticada 66 | chain.doFilter(request, response); 67 | 68 | } catch (JWTVerificationException ex) { 69 | httpResponse.sendError(HttpStatus.UNAUTHORIZED.value()); 70 | return; 71 | } 72 | } 73 | 74 | @Override 75 | public void init(FilterConfig filterConfig) { 76 | } 77 | 78 | @Override 79 | public void destroy() { 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/main/java/com/quasarconsultoria/jwtspringsec/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.quasarconsultoria.jwtspringsec; 2 | 3 | import com.quasarconsultoria.jwtspringsec.login.JWTBasicAuthenticationFilter; 4 | import com.quasarconsultoria.jwtspringsec.login.JWTUsernamePasswordAuthenticationFilter; 5 | import org.apache.commons.codec.digest.DigestUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.config.http.SessionCreationPolicy; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | 16 | import javax.sql.DataSource; 17 | 18 | @Configuration 19 | @EnableWebSecurity 20 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 21 | 22 | @Autowired 23 | private DataSource dataSource; 24 | 25 | @Override 26 | protected void configure(HttpSecurity http) throws Exception { 27 | http 28 | .csrf().disable() 29 | .authorizeRequests() 30 | .anyRequest().authenticated() 31 | .and() 32 | .formLogin() 33 | .permitAll() 34 | .and() 35 | .addFilter(jwtUsernamePasswordAuthenticationFilter()) 36 | .addFilter(jwtBasicAuthenticationFilter()) 37 | .sessionManagement() 38 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 39 | .and() 40 | .logout() 41 | .permitAll(); 42 | } 43 | 44 | @Bean 45 | public JWTUsernamePasswordAuthenticationFilter jwtUsernamePasswordAuthenticationFilter() throws Exception { 46 | JWTUsernamePasswordAuthenticationFilter jwtUsernamePasswordAuthenticationFilter = new JWTUsernamePasswordAuthenticationFilter(); 47 | jwtUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager()); 48 | return jwtUsernamePasswordAuthenticationFilter; 49 | } 50 | 51 | @Bean 52 | public JWTBasicAuthenticationFilter jwtBasicAuthenticationFilter() throws Exception { 53 | return new JWTBasicAuthenticationFilter(authenticationManager()); 54 | } 55 | 56 | @Override 57 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 58 | auth.jdbcAuthentication() 59 | .dataSource(this.dataSource) 60 | .usersByUsernameQuery("select login, senha, 1 from usuarios where login = ?") 61 | .authoritiesByUsernameQuery("select ?, 'ROLE_USER';"); 62 | } 63 | 64 | @Bean 65 | public PasswordEncoder passwordEncoder() { 66 | return new PasswordEncoder() { 67 | @Override 68 | public String encode(CharSequence rawPassword) { 69 | return DigestUtils.sha1Hex(rawPassword + "algoaqui"); 70 | } 71 | @Override 72 | public boolean matches(CharSequence rawPassword, String encodedPassword) { 73 | return DigestUtils.sha1Hex(rawPassword + "algoaqui").equals(encodedPassword); 74 | } 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | --------------------------------------------------------------------------------