diff --git a/exemplos/02-seguranca/docs/QUESTOES.ME b/exemplos/02-seguranca/docs/QUESTOES.ME new file mode 100644 index 0000000..18b0286 --- /dev/null +++ b/exemplos/02-seguranca/docs/QUESTOES.ME @@ -0,0 +1,12 @@ +1 - + + +2 - Toda vez que uma requisição é realizada, a aplicação do lado do servidor interceptará o cabeçalho de autorização da requisição + e extrairá a informação do token. Uma consulta à base de dados será feita usando este token. Se ele for válido e possuir a + permissão necessário para acessar a área requisitada, tudo continuará. Caso contrário, será retornado um código de resposta do + tipo 403 + + + +3 - Não, a API não precisa ir até o banco de dados consultar as informações do usuário, pois contido no próprio token + já temos suas credenciais de acesso. \ No newline at end of file diff --git a/exemplos/02-seguranca/pom.xml b/exemplos/02-seguranca/pom.xml index 7c0b5df..3db60a8 100644 --- a/exemplos/02-seguranca/pom.xml +++ b/exemplos/02-seguranca/pom.xml @@ -56,6 +56,24 @@ 42.2.8 + + io.jsonwebtoken + jjwt-api + 0.10.5 + + + io.jsonwebtoken + jjwt-impl + 0.10.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.10.5 + runtime + + diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtFilter.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtFilter.java new file mode 100644 index 0000000..e5e97fe --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtFilter.java @@ -0,0 +1,84 @@ +package br.com.ifpb.pweb2.securitydemo.config; + +import io.jsonwebtoken.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author raul on 27/10/19 + */ +@Slf4j +public class JwtFilter extends BasicAuthenticationFilter { + + private final SecurityConfig securityConfig; + + public JwtFilter(AuthenticationManager authenticationManager, SecurityConfig securityConfig) { + super(authenticationManager); + this.securityConfig = securityConfig; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException { + UsernamePasswordAuthenticationToken authentication = getAuthentication(request); + if (authentication == null) { + filterChain.doFilter(request, response); + return; + } + + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } + + private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + if (token != null && !token.isEmpty() && token.startsWith("Bearer")) { + try { + String signingKey = securityConfig.getSecret(); + + Jws parsedToken = Jwts.parser() + .setSigningKey(signingKey.getBytes()) + .parseClaimsJws(token.replace("Bearer ", "")); + + String username = parsedToken + .getBody() + .getSubject(); + + List authorities = ((List) parsedToken.getBody() + .get("roles")).stream() + .map(authority -> new SimpleGrantedAuthority((String) authority)) + .collect(Collectors.toList()); + + if (username != null && !username.isEmpty()){ + return new UsernamePasswordAuthenticationToken(username, null, authorities); + } + + } catch (ExpiredJwtException exception) { + log.warn("Request to parse expired JWT : {} failed : {}", token, exception.getMessage()); + } catch (UnsupportedJwtException exception) { + log.warn("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage()); + } catch (MalformedJwtException exception) { + log.warn("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage()); + } catch (SignatureException exception) { + log.warn("Request to parse JWT with invalid signature : {} failed : {}", token, exception.getMessage()); + } catch (IllegalArgumentException exception) { + log.warn("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage()); + } + } + + return null; + } + +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtUtil.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtUtil.java new file mode 100644 index 0000000..66a88cb --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/JwtUtil.java @@ -0,0 +1,47 @@ +package br.com.ifpb.pweb2.securitydemo.config; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author raul on 23/10/19 + */ +@Configuration +public class JwtUtil { + + private final SecurityConfig securityConfig; + private final String signingKey; + + public JwtUtil(SecurityConfig securityConfig) { + this.securityConfig = securityConfig; + signingKey = securityConfig.getSecret(); + } + + public String generateToken(Authentication authentication) { + UserDetails user = ((UserDetails) authentication.getPrincipal()); + + List roles = user.getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + + return Jwts.builder() + .signWith(Keys.hmacShaKeyFor(signingKey.getBytes()), SignatureAlgorithm.HS512) + .setHeaderParam("type", securityConfig.getTokenType()) + .setIssuer(securityConfig.getIssuer()) //emissor + .setAudience(securityConfig.getAudience()) //destinatario + .setSubject(user.getUsername()) + .setExpiration(new Date(System.currentTimeMillis() + securityConfig.getExpiration())) + .claim("roles", roles) + .compact(); + } +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConfig.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConfig.java new file mode 100644 index 0000000..5abbae3 --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConfig.java @@ -0,0 +1,19 @@ +package br.com.ifpb.pweb2.securitydemo.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author raul on 23/10/19 + */ +@Configuration +@ConfigurationProperties(prefix="security") +@Data +public class SecurityConfig { + private String tokenType; + private String secret; + private String issuer; + private String audience; + private Long expiration; +} \ No newline at end of file diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/WebSecurityConfig.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/WebSecurityConfig.java index 43671e8..f6b9e83 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/WebSecurityConfig.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/WebSecurityConfig.java @@ -1,33 +1,67 @@ package br.com.ifpb.pweb2.securitydemo.config; +import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @EnableWebSecurity @EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true, prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - public WebSecurityConfig() { + private final UserDetailsService userDetailsService; + + private final PasswordEncoder passwordEncoder; + + private final SecurityConfig securityConfig; + + public WebSecurityConfig(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, SecurityConfig securityConfig) { + this.userDetailsService = userDetailsService; + this.passwordEncoder = passwordEncoder; + this.securityConfig = securityConfig; } + @Override protected void configure(HttpSecurity http) throws Exception { - http + http.cors().and() .authorizeRequests() - .antMatchers("/api/**").authenticated() .antMatchers("/publico").permitAll() - .antMatchers("/usuarios").hasRole("ADMIN") - .and() - .sessionManagement() + .antMatchers("/login").permitAll() + .anyRequest().authenticated() + .and() + .addFilter(new JwtFilter(authenticationManager(), securityConfig)) + .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .httpBasic() - .and() + .and() .csrf().disable(); } + public void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); + } + + + @Bean + public AuthenticationManager getAuthenticationManager() throws Exception { + return authenticationManager(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); + return source; + } + } diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Login.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Login.java new file mode 100644 index 0000000..5ff42dd --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Login.java @@ -0,0 +1,14 @@ +package br.com.ifpb.pweb2.securitydemo.controller; + +import lombok.Data; + +/** + * @author raul on 25/10/19 + */ +@Data +public class Login { + + private String login; + private String password; + +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/LoginController.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/LoginController.java new file mode 100644 index 0000000..8e6a0be --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/LoginController.java @@ -0,0 +1,46 @@ +package br.com.ifpb.pweb2.securitydemo.controller; + + +import br.com.ifpb.pweb2.securitydemo.config.JwtUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author raul on 22/10/19 + */ + +@RestController +public class LoginController { + + private final JwtUtil jwtUtil; + + private final AuthenticationManager authenticationManager; + + private final PasswordEncoder passwordEncoder; + + public LoginController(AuthenticationManager authenticationManager, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) { + this.jwtUtil = jwtUtil; + this.authenticationManager = authenticationManager; + this.passwordEncoder = passwordEncoder; + } + + @PostMapping("login") + public ResponseEntity login(@RequestBody Login login) { + + System.out.println(passwordEncoder.encode(login.getPassword())); + + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(login.getLogin(), login.getPassword()); + + String token = this.jwtUtil.generateToken(authenticationManager.authenticate(authenticationToken)); + + return ResponseEntity.ok(new Token(token)); + } + + +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Token.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Token.java new file mode 100644 index 0000000..65c0e5d --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/Token.java @@ -0,0 +1,13 @@ +package br.com.ifpb.pweb2.securitydemo.controller; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author raul on 25/10/19 + */ +@Data +@AllArgsConstructor +public class Token { + private String token; +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/UsuarioController.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/UsuarioController.java index b1d2d8c..feef4da 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/UsuarioController.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/UsuarioController.java @@ -4,51 +4,52 @@ import br.com.ifpb.pweb2.securitydemo.service.UsuarioException; import br.com.ifpb.pweb2.securitydemo.service.UsuarioService; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; +/** + * @author raul on 18/10/19 + */ + @RestController -@RequestMapping("/usuarios") +@RequestMapping("usuario") public class UsuarioController { - private final UsuarioService usuarioService; + private PasswordEncoder passwordEncoder; - public UsuarioController(UsuarioService usuarioService) { + public UsuarioController(UsuarioService usuarioService, PasswordEncoder passwordEncoder) { this.usuarioService = usuarioService; - } - - - @GetMapping("/{login}") - public Usuario recuperarPorLogin(@PathVariable("login") String login) { - return usuarioService.recuperarPorLogin(login).orElseThrow(RuntimeException::new); + this.passwordEncoder = passwordEncoder; } @GetMapping - public List listarUsuarios() { + public List listar(){ return usuarioService.listarUsuarios(); } @PostMapping - public ResponseEntity salvarUsuario(@RequestBody @Valid Usuario usuario) { + public ResponseEntity salvar(@RequestBody @Valid Usuario usuario){ try { + usuario.setSenha(passwordEncoder.encode(usuario.getSenha())); usuario = usuarioService.salvarUsuario(usuario); - } catch(UsuarioException e) { - return ResponseEntity.badRequest().header("erro", e.getMessage()).build(); + } catch (UsuarioException ex) { + return ResponseEntity.badRequest().header("erro", ex.getMessage()).build(); } return ResponseEntity.ok(usuario); } @PutMapping - public Usuario atualizarUsuario(@RequestBody @Valid Usuario usuario) { - return usuarioService.atualizarUsuario(usuario); + public ResponseEntity atualizar(@RequestBody @Valid Usuario usuario){ + usuario.setSenha(passwordEncoder.encode(usuario.getSenha())); + return ResponseEntity.ok(usuarioService.atualizarUsuario(usuario)); } - @DeleteMapping("/{id}") - public void removerUsuario(@PathVariable("id") Long id) { + @DeleteMapping("{id}") + public ResponseEntity remover(@PathVariable("id") Long id){ usuarioService.removerUsuario(id); + return ResponseEntity.accepted().build(); } - } diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/domain/Usuario.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/domain/Usuario.java index ad72737..0791bcb 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/domain/Usuario.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/domain/Usuario.java @@ -52,6 +52,11 @@ public class Usuario implements UserDetails { private LocalDateTime dataCadastro; + public Usuario(String login, String senha){ + this.login = login; + this.senha = senha; + } + @Override public Collection getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN")); diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/UsuarioService.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/UsuarioService.java index 08636e1..dfe4713 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/UsuarioService.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/UsuarioService.java @@ -28,7 +28,6 @@ public List listarUsuarios() { return this.usuarioRepository.findAll(); } - @PreAuthorize("hasRole('ADMIN')") public Usuario salvarUsuario(Usuario usuario) throws UsuarioException { Usuario usuarioCriado = null; try { diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/auth/UserDetailsServiceImpl.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/auth/UserDetailsServiceImpl.java index 00a1db8..bd91d43 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/auth/UserDetailsServiceImpl.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/service/auth/UserDetailsServiceImpl.java @@ -1,9 +1,12 @@ package br.com.ifpb.pweb2.securitydemo.service.auth; +import br.com.ifpb.pweb2.securitydemo.config.ApplicationConfig; +import br.com.ifpb.pweb2.securitydemo.domain.Usuario; import br.com.ifpb.pweb2.securitydemo.repository.UsuarioRepository; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service("userDetailsService") @@ -11,12 +14,21 @@ public class UserDetailsServiceImpl implements UserDetailsService { private final UsuarioRepository usuarioRepository; - public UserDetailsServiceImpl(UsuarioRepository usuarioRepository) { + private final ApplicationConfig applicationConfig; + + private final PasswordEncoder passwordEncoder; + + public UserDetailsServiceImpl(UsuarioRepository usuarioRepository, ApplicationConfig applicationConfig, PasswordEncoder passwordEncoder) { this.usuarioRepository = usuarioRepository; + this.applicationConfig = applicationConfig; + this.passwordEncoder = passwordEncoder; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + if(username.equals(this.applicationConfig.getAutenticacaoPadrao().getLogin())){ + return new Usuario(this.applicationConfig.getAutenticacaoPadrao().getLogin(), passwordEncoder.encode(this.applicationConfig.getAutenticacaoPadrao().getSenha())); + } return usuarioRepository.findByLogin(username).orElseThrow( () -> new UsernameNotFoundException(username)); } } diff --git a/exemplos/02-seguranca/src/main/resources/application.yaml b/exemplos/02-seguranca/src/main/resources/application.yaml index c5af4b7..6ec22f1 100644 --- a/exemplos/02-seguranca/src/main/resources/application.yaml +++ b/exemplos/02-seguranca/src/main/resources/application.yaml @@ -10,4 +10,11 @@ app: login: diego senha: 123 papel: ADMIN - tipoAutenticacao: BANCO \ No newline at end of file + tipoAutenticacao: BANCO + +security: + tokenType: JWT + issuer: ifpb + audience: pweb2 + expiration: 3600000 + secret: n2r5u8x/A%D*G-KaPdSgVkYp3s6v9y$B&E(H+MbQeThWmZq4t7w!z%C*F-J@NcRf \ No newline at end of file