ddangelorb
3 years ago
6 changed files with 282 additions and 0 deletions
-
16src/main/java/org/waterproofingdata/wpdauth/repository/UsersRepository.java
-
40src/main/java/org/waterproofingdata/wpdauth/security/JwtTokenFilter.java
-
22src/main/java/org/waterproofingdata/wpdauth/security/JwtTokenFilterConfigurer.java
-
91src/main/java/org/waterproofingdata/wpdauth/security/JwtTokenProvider.java
-
35src/main/java/org/waterproofingdata/wpdauth/security/MyUserDetails.java
-
78src/main/java/org/waterproofingdata/wpdauth/security/WebSecurityConfig.java
@ -0,0 +1,16 @@ |
|||
package org.waterproofingdata.wpdauth.repository; |
|||
|
|||
import org.waterproofingdata.wpdauth.model.Users; |
|||
import javax.transaction.Transactional; |
|||
import org.springframework.data.jpa.repository.JpaRepository; |
|||
|
|||
|
|||
public interface UsersRepository extends JpaRepository<Users, Integer> { |
|||
boolean existsByUsername(String username); |
|||
|
|||
Users findByUsername(String username); |
|||
|
|||
//@Transactional |
|||
//void activateByUsername(String username); |
|||
|
|||
} |
@ -0,0 +1,40 @@ |
|||
package org.waterproofingdata.wpdauth.security; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import javax.servlet.FilterChain; |
|||
import javax.servlet.ServletException; |
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
|
|||
import org.waterproofingdata.wpdauth.security.JwtTokenProvider; |
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.core.context.SecurityContextHolder; |
|||
import org.waterproofingdata.wpdauth.exception.CustomException; |
|||
import org.springframework.web.filter.OncePerRequestFilter; |
|||
|
|||
public class JwtTokenFilter extends OncePerRequestFilter { |
|||
private JwtTokenProvider jwtTokenProvider; |
|||
|
|||
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) { |
|||
this.jwtTokenProvider = jwtTokenProvider; |
|||
} |
|||
|
|||
@Override |
|||
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { |
|||
String token = jwtTokenProvider.resolveToken(httpServletRequest); |
|||
try { |
|||
if (token != null && jwtTokenProvider.validateToken(token)) { |
|||
Authentication auth = jwtTokenProvider.getAuthentication(token); |
|||
SecurityContextHolder.getContext().setAuthentication(auth); |
|||
} |
|||
} catch (CustomException ex) { |
|||
//this is very important, since it guarantees the user is not authenticated at all |
|||
SecurityContextHolder.clearContext(); |
|||
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage()); |
|||
return; |
|||
} |
|||
|
|||
filterChain.doFilter(httpServletRequest, httpServletResponse); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package org.waterproofingdata.wpdauth.security; |
|||
|
|||
import org.waterproofingdata.wpdauth.security.JwtTokenFilter; |
|||
import org.waterproofingdata.wpdauth.security.JwtTokenProvider; |
|||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter; |
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|||
import org.springframework.security.web.DefaultSecurityFilterChain; |
|||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
|||
|
|||
public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { |
|||
private JwtTokenProvider jwtTokenProvider; |
|||
|
|||
public JwtTokenFilterConfigurer(JwtTokenProvider jwtTokenProvider) { |
|||
this.jwtTokenProvider = jwtTokenProvider; |
|||
} |
|||
|
|||
@Override |
|||
public void configure(HttpSecurity http) throws Exception { |
|||
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider); |
|||
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); |
|||
} |
|||
} |
@ -0,0 +1,91 @@ |
|||
package org.waterproofingdata.wpdauth.security; |
|||
|
|||
import java.util.Base64; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.servlet.http.HttpServletRequest; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
|||
import org.springframework.security.core.userdetails.UserDetails; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import io.jsonwebtoken.Claims; |
|||
import io.jsonwebtoken.JwtException; |
|||
import io.jsonwebtoken.Jwts; |
|||
import io.jsonwebtoken.SignatureAlgorithm; |
|||
|
|||
import org.waterproofingdata.wpdauth.model.Roles; |
|||
import org.waterproofingdata.wpdauth.security.MyUserDetails; |
|||
import org.waterproofingdata.wpdauth.exception.CustomException; |
|||
|
|||
@Component |
|||
public class JwtTokenProvider { |
|||
/** |
|||
* THIS IS NOT A SECURE PRACTICE! For simplicity, we are storing a static key here. Ideally, in a |
|||
* microservices environment, this key would be kept on a config-server. |
|||
*/ |
|||
@Value("${security.jwt.token.secret-key:secret-key}") |
|||
private String secretKey; |
|||
|
|||
@Value("${security.jwt.token.expire-length:3600000}") |
|||
private long validityInMilliseconds = 3600000; // 1h |
|||
|
|||
@Autowired |
|||
private MyUserDetails myUserDetails; |
|||
|
|||
@PostConstruct |
|||
protected void init() { |
|||
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); |
|||
} |
|||
|
|||
public String createToken(String username, List<Roles> roles) { |
|||
|
|||
Claims claims = Jwts.claims().setSubject(username); |
|||
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).filter(Objects::nonNull).collect(Collectors.toList())); |
|||
|
|||
Date now = new Date(); |
|||
Date validity = new Date(now.getTime() + validityInMilliseconds); |
|||
|
|||
return Jwts.builder()// |
|||
.setClaims(claims)// |
|||
.setIssuedAt(now)// |
|||
.setExpiration(validity)// |
|||
.signWith(SignatureAlgorithm.HS256, secretKey)// |
|||
.compact(); |
|||
} |
|||
|
|||
public Authentication getAuthentication(String token) { |
|||
UserDetails userDetails = myUserDetails.loadUserByUsername(getUsername(token)); |
|||
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); |
|||
} |
|||
|
|||
public String getUsername(String token) { |
|||
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); |
|||
} |
|||
|
|||
public String resolveToken(HttpServletRequest req) { |
|||
String bearerToken = req.getHeader("Authorization"); |
|||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) { |
|||
return bearerToken.substring(7); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public boolean validateToken(String token) { |
|||
try { |
|||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); |
|||
return true; |
|||
} catch (JwtException | IllegalArgumentException e) { |
|||
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
package org.waterproofingdata.wpdauth.security; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.security.core.userdetails.UserDetails; |
|||
import org.springframework.security.core.userdetails.UserDetailsService; |
|||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import org.waterproofingdata.wpdauth.model.Users; |
|||
import org.waterproofingdata.wpdauth.repository.UsersRepository; |
|||
|
|||
@Service |
|||
public class MyUserDetails implements UserDetailsService { |
|||
@Autowired |
|||
private UsersRepository userRepository; |
|||
|
|||
@Override |
|||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
|||
final Users user = userRepository.findByUsername(username); |
|||
|
|||
if (user == null) { |
|||
throw new UsernameNotFoundException("User '" + username + "' not found"); |
|||
} |
|||
|
|||
return org.springframework.security.core.userdetails.User// |
|||
.withUsername(username)// |
|||
.password(user.getPassword())// |
|||
.authorities(user.getRoles())// |
|||
.accountExpired(false)// |
|||
.accountLocked(false)// |
|||
.credentialsExpired(false)// |
|||
.disabled(false)// |
|||
.build(); |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
package org.waterproofingdata.wpdauth.security; |
|||
|
|||
import org.waterproofingdata.wpdauth.security.JwtTokenFilterConfigurer; |
|||
import org.waterproofingdata.wpdauth.security.JwtTokenProvider; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.security.authentication.AuthenticationManager; |
|||
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.builders.WebSecurity; |
|||
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.crypto.bcrypt.BCryptPasswordEncoder; |
|||
import org.springframework.security.crypto.password.PasswordEncoder; |
|||
|
|||
@Configuration |
|||
@EnableWebSecurity |
|||
@EnableGlobalMethodSecurity(prePostEnabled = true) |
|||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { |
|||
@Autowired |
|||
private JwtTokenProvider jwtTokenProvider; |
|||
|
|||
@Override |
|||
protected void configure(HttpSecurity http) throws Exception { |
|||
|
|||
// Disable CSRF (cross site request forgery) |
|||
http.csrf().disable(); |
|||
|
|||
// No session will be created or used by spring security |
|||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); |
|||
|
|||
// Entry points |
|||
http.authorizeRequests()// |
|||
.antMatchers("/users/signin").permitAll()// |
|||
.antMatchers("/users/signup").permitAll()// |
|||
.antMatchers("/h2-console/**/**").permitAll() |
|||
// Disallow everything else.. |
|||
.anyRequest().authenticated(); |
|||
|
|||
// If a user try to access a resource without having enough permissions |
|||
http.exceptionHandling().accessDeniedPage("/login"); |
|||
|
|||
// Apply JWT |
|||
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider)); |
|||
|
|||
// Optional, if you want to test the API from a browser |
|||
// http.httpBasic(); |
|||
} |
|||
|
|||
@Override |
|||
public void configure(WebSecurity web) throws Exception { |
|||
// Allow swagger to be accessed without authentication |
|||
web.ignoring().antMatchers("/v2/api-docs")// |
|||
.antMatchers("/swagger-resources/**")// |
|||
.antMatchers("/swagger-ui.html")// |
|||
.antMatchers("/configuration/**")// |
|||
.antMatchers("/webjars/**")// |
|||
.antMatchers("/public") |
|||
|
|||
// Un-secure H2 Database (for testing purposes, H2 console shouldn't be unprotected in production) |
|||
.and() |
|||
.ignoring() |
|||
.antMatchers("/h2-console/**/**");; |
|||
} |
|||
|
|||
@Bean |
|||
public PasswordEncoder passwordEncoder() { |
|||
return new BCryptPasswordEncoder(12); |
|||
} |
|||
|
|||
@Override |
|||
@Bean |
|||
public AuthenticationManager authenticationManagerBean() throws Exception { |
|||
return super.authenticationManagerBean(); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue