package io .kchoi85 .userservice .security ;
import org .springframework .context .annotation .Bean ;
import org .springframework .context .annotation .Configuration ;
import org .springframework .http .HttpMethod ;
import org .springframework .security .authentication .AuthenticationManager ;
import org .springframework .security .config .annotation .authentication .builders .AuthenticationManagerBuilder ;
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 .bcrypt .BCryptPasswordEncoder ;
import org .springframework .security .web .authentication .UsernamePasswordAuthenticationFilter ;
import io .kchoi85 .userservice .security .filter .CustomAuthenticationFilter ;
import io .kchoi85 .userservice .security .filter .CustomAuthorizationFilter ;
import lombok .RequiredArgsConstructor ;
import lombok .extern .slf4j .Slf4j ;
@ Configuration
@ EnableWebSecurity
@ RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService ;
private final BCryptPasswordEncoder bCryptPasswordEncoder ;
@ Override
protected void configure (AuthenticationManagerBuilder auth ) throws Exception {
auth .userDetailsService (userDetailsService ).passwordEncoder (bCryptPasswordEncoder );
}
@ Override
protected void configure (HttpSecurity http ) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter (
authenticationManagerBean ());
customAuthenticationFilter .setFilterProcessesUrl ("/api/login" );
http .csrf ().disable ();
http .cors ().disable ();
http .sessionManagement ().sessionCreationPolicy (SessionCreationPolicy .STATELESS );
http .authorizeRequests ().antMatchers ("/api/login" ).permitAll ();
http .authorizeRequests ().antMatchers (HttpMethod .GET ,
"/api/v1/users/**" ).hasAnyAuthority ("ROLE_ADMIN" );
http .authorizeRequests ().anyRequest ().authenticated ();
http .addFilter (customAuthenticationFilter );
// intercept all requests and check for JWT token
http .addFilterBefore (new CustomAuthorizationFilter (), UsernamePasswordAuthenticationFilter .class );
}
@ Bean
@ Override
public AuthenticationManager authenticationManagerBean () throws Exception {
return super .authenticationManagerBean ();
}
}
CustomAuthenticationFilter.java
package io .kchoi85 .userservice .security .filter ;
import java .io .IOException ;
import java .util .Date ;
import java .util .HashMap ;
import java .util .Map ;
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 .Authentication ;
import org .springframework .security .core .GrantedAuthority ;
import org .springframework .security .web .authentication .UsernamePasswordAuthenticationFilter ;
import com .auth0 .jwt .JWT ;
import com .auth0 .jwt .algorithms .Algorithm ;
import com .fasterxml .jackson .core .exc .StreamWriteException ;
import com .fasterxml .jackson .databind .DatabindException ;
import com .fasterxml .jackson .databind .ObjectMapper ;
import lombok .extern .slf4j .Slf4j ;
@ Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager ;
public CustomAuthenticationFilter (AuthenticationManager authenticationManager ) {
this .authenticationManager = authenticationManager ;
}
@ Override
public Authentication attemptAuthentication (HttpServletRequest request , HttpServletResponse response ) {
String username = request .getParameter ("username" );
String password = request .getParameter ("password" );
log .info ("Attempting authentication with username: {}" , username );
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken (
username ,
password );
return authenticationManager .authenticate (authenticationToken );
}
@ Override
protected void successfulAuthentication (HttpServletRequest request , HttpServletResponse response ,
FilterChain filter ,
Authentication authResult ) throws StreamWriteException , DatabindException , IOException , ServletException {
log .info ("Successful authentication with username: {}" , authResult .getName ());
// Userdetails type object
org .springframework .security .core .userdetails .User user = (org .springframework .security .core .userdetails .User ) authResult
.getPrincipal ();
// JWT token algorithm
Algorithm algorithm = Algorithm .HMAC256 ("secret" .getBytes ());
String access_token = JWT
.create ()
.withSubject (user .getUsername ())
.withClaim ("roles" ,
user .getAuthorities ().stream ().map (GrantedAuthority ::getAuthority ).collect (Collectors .toList ()))
// 10 minutes
.withExpiresAt (new Date (System .currentTimeMillis () + 10 * 60 * 1000 ))
.withIssuer (request .getRequestURL ().toString ())
.sign (algorithm );
String refresh_token = JWT
.create ()
.withSubject (user .getUsername ())
.withExpiresAt (new Date (System .currentTimeMillis () + 30 * 60 * 1000 ))
// 30minutes
.withIssuer (request .getRequestURL ().toString ())
.sign (algorithm );
Map <String , String > tokens = new HashMap <>();
tokens .put ("access_token" , access_token );
tokens .put ("refresh_token" , refresh_token );
response .setContentType ("application/json" );
response .setHeader ("access_token" , access_token );
response .setHeader ("refresh_token" , refresh_token );
new ObjectMapper ().writeValue (response .getOutputStream (), tokens );
}
}
CustomAuthorizationFilter.java
package io .kchoi85 .userservice .security .filter ;
import java .io .IOException ;
import java .util .ArrayList ;
import java .util .Collection ;
import java .util .stream .Stream ;
import javax .servlet .FilterChain ;
import javax .servlet .ServletException ;
import javax .servlet .http .HttpServletRequest ;
import javax .servlet .http .HttpServletResponse ;
import org .springframework .web .filter .OncePerRequestFilter ;
import com .auth0 .jwt .JWT ;
import com .auth0 .jwt .JWTVerifier ;
import com .auth0 .jwt .algorithms .Algorithm ;
import com .auth0 .jwt .interfaces .DecodedJWT ;
import lombok .extern .slf4j .Slf4j ;
import org .springframework .http .HttpHeaders .*;
import org .springframework .security .authentication .UsernamePasswordAuthenticationToken ;
import org .springframework .security .core .authority .SimpleGrantedAuthority ;
import org .springframework .security .core .context .SecurityContextHolder ;
@ Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
@ Override
protected void doFilterInternal (HttpServletRequest request , HttpServletResponse response , FilterChain filterChain )
throws ServletException , IOException {
if (request .getServletPath ().equals ("/api/login" )) {
filterChain .doFilter (request , response );
} else {
String authorizationHeader = request .getHeader ("Authorization" );
if (authorizationHeader == null || !authorizationHeader .startsWith ("Bearer " )) {
response .sendError (HttpServletResponse .SC_UNAUTHORIZED );
return ;
}
try {
String token = authorizationHeader .substring ("Bearer " .length ());
Algorithm algorithm = Algorithm .HMAC256 ("secret" .getBytes ());
JWTVerifier verifier = JWT .require (algorithm ).build ();
DecodedJWT decodedJWT = verifier .verify (token );
String username = decodedJWT .getSubject ();
String [] roles = decodedJWT .getClaims ().get ("roles" ).asArray (String .class );
Collection <SimpleGrantedAuthority > authorities = new ArrayList <>();
Stream .of (roles ).forEach (role -> {
authorities .add (new SimpleGrantedAuthority (role ));
});
UsernamePasswordAuthenticationToken authenticationTOken = new UsernamePasswordAuthenticationToken (
username , null , authorities );
SecurityContextHolder .getContext ().setAuthentication (authenticationTOken );
filterChain .doFilter (request , response );
} catch (Exception e ) {
log .error ("Error logging in: {}" , e );
response .setHeader ("error" , e .getMessage ());
response .sendError (HttpServletResponse .SC_UNAUTHORIZED );
}
}
}
}