diff --git "a/Criar Aplica\303\247\303\243o com autentica\303\247\303\243o via Token .pdf" "b/Criar Aplica\303\247\303\243o com autentica\303\247\303\243o via Token .pdf" new file mode 100644 index 0000000..cb7de45 Binary files /dev/null and "b/Criar Aplica\303\247\303\243o com autentica\303\247\303\243o via Token .pdf" differ diff --git a/exemplos/02-1-seguranca-jwt/src/main/resources/application-dev.yaml b/exemplos/02-1-seguranca-jwt/src/main/resources/application-dev.yaml index 05c263f..c9715e8 100644 --- a/exemplos/02-1-seguranca-jwt/src/main/resources/application-dev.yaml +++ b/exemplos/02-1-seguranca-jwt/src/main/resources/application-dev.yaml @@ -1,8 +1,8 @@ spring: datasource: - url: jdbc:postgresql://localhost:5432/pweb2 + url: jdbc:postgresql://localhost:5433/pweb2-jwt username: postgres - password: secret + password: 1327 driver-class-name: org.postgresql.Driver jpa: hibernate: diff --git a/exemplos/02-seguranca/pom.xml b/exemplos/02-seguranca/pom.xml index 7c0b5df..8254ede 100644 --- a/exemplos/02-seguranca/pom.xml +++ b/exemplos/02-seguranca/pom.xml @@ -15,7 +15,7 @@ Demo project for Spring Security - 1.8 + 11 @@ -55,6 +55,23 @@ postgresql 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/ApplicationConfig.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/ApplicationConfig.java index bc36b99..427d18d 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/ApplicationConfig.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/ApplicationConfig.java @@ -1,8 +1,7 @@ package br.com.ifpb.pweb2.securitydemo.config; import lombok.Data; -import lombok.Getter; -import lombok.Setter; + import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -13,7 +12,7 @@ public class ApplicationConfig { private AutenticacaoPadrao autenticacaoPadrao = new AutenticacaoPadrao(); - private enum TipoAutenticacao { MEMORIA, BANCO }; + private enum TipoAutenticacao { MEMORIA, BANCO } @Data public class AutenticacaoPadrao { 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..7356235 --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConfig.java @@ -0,0 +1,18 @@ +package br.com.ifpb.pweb2.securitydemo.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix="security") +@Data +public class SecurityConfig { + private String authLoginUrl; + private String tokenType; + private String secret; + private String issuer; + private String audience; + private Long expiration; + +} diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConstants.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConstants.java new file mode 100644 index 0000000..12d3ef4 --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/SecurityConstants.java @@ -0,0 +1,19 @@ +package br.com.ifpb.pweb2.securitydemo.config; + +public class SecurityConstants { + + public static final String AUTH_LOGIN_URL = "/api/authenticate"; + + // Signing key for HS512 algorithm + // Use http://www.allkeysgenerator.com/ para gerar uma chave + public static final String JWT_SECRET = "E07451F5-583D-4B1A-8AF1-A3AF70AABD43"; + + // JWT token defaults + public static final String TOKEN_HEADER = "Authorization"; + public static final String TOKEN_PREFIX = "Bearer "; + public static final String TOKEN_TYPE = "JWT"; + public static final String TOKEN_ISSUER = "secure-api"; + public static final String TOKEN_AUDIENCE = "secure-app"; + + private SecurityConstants() {} +} 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..e5de4cf 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,28 +1,54 @@ package br.com.ifpb.pweb2.securitydemo.config; +import br.com.ifpb.pweb2.securitydemo.config.jwt.JwtAuthenticationFilter; +import br.com.ifpb.pweb2.securitydemo.config.jwt.JwtAuthorizationFilter; +import br.com.ifpb.pweb2.securitydemo.service.UsuarioService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; +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 SecurityConfig securityConfig; + private UsuarioService usuarioService; + + @Autowired + private ApplicationConfig applicationConfig; + private final UserDetailsService userDetailsService; + + private final PasswordEncoder passwordEncoder; + + + public WebSecurityConfig(SecurityConfig securityConfig, UsuarioService usuarioService, UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { + this.securityConfig = securityConfig; + this.usuarioService = usuarioService; + this.userDetailsService = userDetailsService; + this.passwordEncoder = passwordEncoder; } @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() + .anyRequest().authenticated() + .and() + .addFilter(new JwtAuthenticationFilter(authenticationManager(), securityConfig)) + .addFilter(new JwtAuthorizationFilter(authenticationManager(), securityConfig)). + sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .httpBasic() @@ -30,4 +56,23 @@ protected void configure(HttpSecurity http) throws Exception { .csrf().disable(); } + @Override + public void configure(AuthenticationManagerBuilder auth) throws Exception { + if(!usuarioService.isEmpty()){ + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); + }else{ + auth.inMemoryAuthentication() + .passwordEncoder(passwordEncoder) + .withUser(applicationConfig.getAutenticacaoPadrao().getLogin()) + .password(passwordEncoder.encode(applicationConfig.getAutenticacaoPadrao().getSenha())) + .authorities("ROLE_"+applicationConfig.getAutenticacaoPadrao().getPapel()); + } } + + @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/config/jwt/JwtAuthenticationFilter.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..e309925 --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,68 @@ +package br.com.ifpb.pweb2.securitydemo.config.jwt; + +import br.com.ifpb.pweb2.securitydemo.config.SecurityConfig; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + private final AuthenticationManager authenticationManager; + + private final SecurityConfig securityConfig; + + public JwtAuthenticationFilter(AuthenticationManager authenticationManager, SecurityConfig securityConfig) { + this.authenticationManager = authenticationManager; + this.securityConfig = securityConfig; + setFilterProcessesUrl(this.securityConfig.getAuthLoginUrl()); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + String username = request.getParameter("usuario"); + String password = request.getParameter("senha"); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + + return authenticationManager.authenticate(authenticationToken); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain, Authentication authentication) { + UserDetails user = ((UserDetails) authentication.getPrincipal()); + + List roles = user.getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + + String signingKey = securityConfig.getSecret(); + + String token = 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(); + + response.addHeader("Authorization", "Bearer " + token); + } + +} \ No newline at end of file diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/jwt/JwtAuthorizationFilter.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/jwt/JwtAuthorizationFilter.java new file mode 100644 index 0000000..71bf44a --- /dev/null +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/config/jwt/JwtAuthorizationFilter.java @@ -0,0 +1,90 @@ +package br.com.ifpb.pweb2.securitydemo.config.jwt; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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 br.com.ifpb.pweb2.securitydemo.config.SecurityConfig; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class JwtAuthorizationFilter extends BasicAuthenticationFilter { + + private final SecurityConfig securityConfig; + + public JwtAuthorizationFilter(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; + } + +} \ No newline at end of file diff --git a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/PublicoController.java b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/PublicoController.java index c056b1c..77e096c 100644 --- a/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/PublicoController.java +++ b/exemplos/02-seguranca/src/main/java/br/com/ifpb/pweb2/securitydemo/controller/PublicoController.java @@ -10,7 +10,7 @@ public class PublicoController { @GetMapping public String exibirMensagem() { - return "Olá, meu acesso é público"; + return "Olá, meu acesso é públicooooo"; } } 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..3c3ddd3 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 @@ -3,8 +3,10 @@ import br.com.ifpb.pweb2.securitydemo.domain.Usuario; import br.com.ifpb.pweb2.securitydemo.service.UsuarioException; import br.com.ifpb.pweb2.securitydemo.service.UsuarioService; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -15,12 +17,14 @@ public class UsuarioController { private final UsuarioService usuarioService; + private BCryptPasswordEncoder bCryptPasswordEncoder; - public UsuarioController(UsuarioService usuarioService) { + + public UsuarioController(UsuarioService usuarioService, BCryptPasswordEncoder bCryptPasswordEncoder) { this.usuarioService = usuarioService; + this.bCryptPasswordEncoder = bCryptPasswordEncoder; } - @GetMapping("/{login}") public Usuario recuperarPorLogin(@PathVariable("login") String login) { return usuarioService.recuperarPorLogin(login).orElseThrow(RuntimeException::new); @@ -28,17 +32,28 @@ public Usuario recuperarPorLogin(@PathVariable("login") String login) { @GetMapping public List listarUsuarios() { + return usuarioService.listarUsuarios(); } @PostMapping public ResponseEntity salvarUsuario(@RequestBody @Valid Usuario usuario) { try { - usuario = usuarioService.salvarUsuario(usuario); + if (usuario == null){ + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); + } + usuario.setSenha(bCryptPasswordEncoder.encode(usuario.getPassword())); + + if (usuarioService.salvarUsuario(usuario) == null){ + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); + } + + return ResponseEntity.status(HttpStatus.CREATED).build(); + + } catch(UsuarioException e) { return ResponseEntity.badRequest().header("erro", e.getMessage()).build(); } - return ResponseEntity.ok(usuario); } @PutMapping 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..0b0e899 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 @@ -1,5 +1,6 @@ package br.com.ifpb.pweb2.securitydemo.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import org.hibernate.validator.constraints.br.CPF; import org.springframework.security.core.GrantedAuthority; @@ -53,36 +54,43 @@ public class Usuario implements UserDetails { private LocalDateTime dataCadastro; @Override + @JsonIgnore public Collection getAuthorities() { return Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN")); } @Override + @JsonIgnore public String getPassword() { return senha; } @Override + @JsonIgnore public String getUsername() { return login; } + @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @Override + @JsonIgnore public boolean isAccountNonLocked() { return true; } @Override + @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @Override + @JsonIgnore public boolean isEnabled() { return true; } 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..8d2a503 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 @@ -58,7 +58,13 @@ public Optional recuperarPorNomeEIdade(String nome, Integer idade) { } public Usuario atualizarUsuario(Usuario usuario) { + usuario.setSenha(passwordEncoder.encode(usuario.getSenha())); return this.usuarioRepository.save(usuario); } + + public boolean isEmpty(){ + return usuarioRepository.findAll().isEmpty(); + } + } diff --git a/exemplos/02-seguranca/src/main/resources/application-dev.yaml b/exemplos/02-seguranca/src/main/resources/application-dev.yaml index 05c263f..821399f 100644 --- a/exemplos/02-seguranca/src/main/resources/application-dev.yaml +++ b/exemplos/02-seguranca/src/main/resources/application-dev.yaml @@ -1,8 +1,8 @@ spring: datasource: - url: jdbc:postgresql://localhost:5432/pweb2 + url: jdbc:postgresql://localhost:5433/pweb2 username: postgres - password: secret + password: 1327 driver-class-name: org.postgresql.Driver jpa: hibernate: diff --git a/exemplos/02-seguranca/src/main/resources/application.yaml b/exemplos/02-seguranca/src/main/resources/application.yaml index c5af4b7..5882cd2 100644 --- a/exemplos/02-seguranca/src/main/resources/application.yaml +++ b/exemplos/02-seguranca/src/main/resources/application.yaml @@ -7,7 +7,16 @@ spring: app: autenticacaoPadrao: - login: diego - senha: 123 + login: admin + senha: 1234 papel: ADMIN - tipoAutenticacao: BANCO \ No newline at end of file + tipoAutenticacao: BANCO + +security: + authLoginUrl: /login + tokenType: JWT + issuer: ifpb + audience: pweb2 + expiration: 3600000 + secret: s5v8y/A?D(G+KbPeShVmYq3t6w9z$C&E)H@McQfTjWnZr4u7x!A%D*G-JaNdRgUk + ## Use: https://www.allkeysgenerator.com/ para gerar o segredo \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..93055b9 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +sudo docker run -p 5433:5432 -d --name postgres --env POSTGRES_PASSWORD=1327 postgres:9.4.19 +sudo docker start postgres