React 로그인 Modal 만들기 7 - JWT 적용하기 Properties에 JWT 설정 값 추가 JWT 를 암호화 하기 위한 key 값, token의 유효시간을 정의하기 위한 값, 그리고 토큰을 Http Header에 저장하기 위핸 Header Key를 정의해준다.
application.yml
jwt: header: Authorization secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK token-validity-in-seconds: 86400000
JWT를 다루기 위한 JwtUtils 생성
JwtUtils에서는 createToken 메소드를 이용해 새로운 JWT를 만들고 validateToken 메소드를 이용해 전달 받은 JWT의 유효성을 검증하고 getAuthentication 메소드를 이용해 전달 받은 JWT로부터 Authentication 객체를 가져오도록 한다.
Jwts.builder 를 이용해 JWT를 생성하고 Jwts.parser 를 이용해 JWT로부터 정보를 가져온다.
Value 어노테이션을 이용해 Properties에 저장한 token을 암호화 하기 위한 Key값과 token 유효시간 정보를 가져온다.
JwtUtils.java
@Component @Slf4j public class JwtUtils { @Value("${jwt.secret}") private String secretKey; @Value("${jwt.token-validity-in-seconds}") private Long tokenValidityInMilliseconds; public String createToken (Authentication authentication) { String authorities = authentication.getAuthorities().stream() .map((autority) -> autority.getAuthority()) .collect(Collectors.joining("," )); Long now = (new Date ()).getTime(); Date validity = new Date (now + this .tokenValidityInMilliseconds); String JsonWebToken = Jwts.builder() .setSubject(authentication.getName()) .claim("auth" , authorities) .signWith(SignatureAlgorithm.HS512, secretKey) .setExpiration(validity) .compact(); return JsonWebToken; } public Authentication getAuthentication (String token) { Claims claims = Jwts .parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); Collection<? extends GrantedAuthority > authorities = Arrays.stream(claims.get("auth" ).toString().split("," )) .map(SimpleGrantedAuthority::new ) .collect(Collectors.toList()); User principal = new User (claims.getSubject(), "" , authorities); return new JsonAuthenticationToken (principal, token, authorities); } public boolean validateToken (String token) { try { Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); return true ; }catch (Exception e){ log.info("JWT 토큰이 잘못되었습니다." ); } return false ; } }
JWT를 이용한 인증 Filter
JwtAuthenticationFilter 에서는 Request Header 정보를 확인해 JWT가 전달 받았는지 확인하고, 전달 받은 JWT 의 유효성을 검증 한 후 Authentication 객체를 생성해 SecurityContext에 저장하는 절차를 거친다.
JwtAuthenticationFilter.java
@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { public final JwtUtils jwtUtils; @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = null ; String authorizationHeader = request.getHeader("authorization" ); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer " )){ token = authorizationHeader.substring(7 ); } if (token != null && jwtUtils.validateToken(token)){ Authentication authentication = jwtUtils.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } }
인증 후 JWT 만들기
사용자 인증이 끝나고 JWT를 만들어 주기 위해 JsonAuthenticationSuccessHandler 를 이용해 인증이 정상적으로 완료된 후 생성된 Authentication 객체를 이용해 JWT 를 만들어 준다.
JsonAuthenticationSuccessHandler.java
@RequiredArgsConstructor public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final ObjectMapper objectMapper; private final JwtUtils jwtUtils; @Override public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { try { UserDetails userDetails = (UserDetails) authentication.getPrincipal(); String jwt = jwtUtils.createToken(authentication); HttpHeaders httpHeaders = new HttpHeaders (); httpHeaders.add("Authorization" , "Bearer " + jwt); response.setStatus(HttpStatus.OK.value()); objectMapper.writeValue(response.getWriter(), jwt); } catch (Exception e) { throw new ServletException ("inavalid username/password" ); } } }
JWT를 사용하기 위한 Config 구성하기
JwtSecurityConfig 에서는 JWT 를 다루기 위한 JwtUtils 와 JWT를 이용해 인증을 진행하기 위한 JwtAuthenticationFilter 를 Bean으로 등록해 UsernamePasswordAuthenticationFilter 앞에 위치하도록 한다.
@Configuration @EnableWebSecurity @RequiredArgsConstructor @Order(1) public class JwtSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public JwtAuthenticationFilter jwtAuthenticationFilter () { return new JwtAuthenticationFilter (jwtUtils()); } @Override protected void configure (HttpSecurity http) throws Exception { http .csrf().disable(); http .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public JwtUtils jwtUtils () { return new JwtUtils (); } }