๐Ÿ”ฅ TIL - Day 50

Kim Dae Hyunยท2021๋…„ 11์›” 8์ผ
1

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
60/93

๐Ÿ“Œ ์ง€๊ธˆ๊นŒ์ง€์˜ ํƒ€์ž„์–ดํƒ ํ…Œ์ŠคํŠธ Flask์—์„œ Spring์œผ๋กœ ์˜ฎ๊ธฐ๊ธฐ

์˜ค๋Š˜ ์ง‘์ค‘ํ•ด์„œ ํ•˜๋ฉด ๋งˆ๋ฌด๋ฆฌ ํ•  ์ค„ ์•Œ์•˜๋Š”๋ฐ ์ธ์ฆ ๋ถ€๋ถ„์—์„œ ๋„ˆ๋ฌด ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๋‹ค.
ํ”„๋ก ํŠธ ์ฝ”๋“œ์™€ API ์ŠคํŽ™์„ Flask์™€ ๊ฑฐ์˜ ๋™์ผํ•˜๊ฒŒ ํ•˜๋ ค๋‹ค ๋ณด๋‹ˆ ์„ธ์‚ผ Spring์ด ๋ถˆํŽธํ•˜๊ฒŒ ๋Š๊ปด์กŒ๋‹ค..

์ผ๋‹จ ์ธ์ฆ๋ถ€๋ถ„.
์›๋ž˜ ํ–ˆ๋˜ ๋Œ€๋กœ JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์˜ localStorage์— ์ €์žฅํ•˜๊ณ  ๋งค ajax ์š”์ฒญ๋งˆ๋‹ค ํ† ํฐ์„ ๋“ค๊ณ ์˜ค๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

์šฐ์„  JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•œ๋‹ค.
์ด ๋ถ€๋ถ„์„ ์™„์ „ํžˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ œ๊ณตํ•ด์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ์ด ์Šฌํ”„๋‹ค..ใ… 

@Slf4j
@Component
public class JwtUtils {

    private final String SECRET = "secret";
    private final SignatureAlgorithm SIGNATURE = SignatureAlgorithm.HS256;
    
    // ํ† ํฐ ์ƒ์„ฑ
    // Claim์€ subject ์—ญํ• ์˜ username๊ณผ ๋งŒ๋ฃŒ์‹œ๊ฐ„ ๋‘๊ฐ€์ง€๋ฅผ ์„ค์ •
    public String createToken(String subject) {
        Claims claims = Jwts.claims();
        claims.put("username", subject);

        return Jwts.builder()
                .claim("username", subject)
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
                .signWith(SIGNATURE, SECRET)
                .compact();
    }
    
    // ํ† ํฐ์˜ Claim์—์„œ username์„ ๊ฐ€์ ธ์˜จ๋‹ค.
    public String getUsernameFromToken(String token) {
        Claims claims = getAllClaims(token);

        return String.valueOf(claims.get("username"));
    }
    
    // ํ† ํฐ ์œ ํšจ์—ฌ๋ถ€ ํ™•์ธ
    // ํ˜„์žฌ๋Š” ํ† ํฐ์˜ ๋งŒ๋ฃŒ์‹œ๊ฐ„๋งŒ์„ ๊ฒ€์ฆ
    public Boolean isValidToken(String token) {
        Date expiration = getAllClaims(token).getExpiration();
        return expiration.after(new Date());
    }

    private Claims getAllClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }
}


Spring Security์˜ ํ•„ํ„ฐ์ฒด์ธ์— JWT๋ฐฉ์‹์˜ ์ธ์ฆ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•„ํ„ฐ๋ฅผ ์ •์˜ํ•˜์—ฌ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
์•„์ง ๋งŽ์ด ๊ฐœ์„ ์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ค.
์ตœ๋Œ€ํ•œ ๋ž˜ํผ๋Ÿฐ์Šค ์—†์ด ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„์„ ์™„๋ฃŒํ–ˆ๋Š”๋ฐ ๋‹ค ํ•˜๊ณ  ์—ฌ๋Ÿฌ ์—ฌ๋Ÿฌ ์ฝ”๋“œ๋ฅผ ์ฐพ์•„๋ณด๋‹ˆ ์ง€๊ธˆ์˜ ํ•„ํ„ฐ๋Š” ์กฐ๊ธˆ ๋ถ€์กฑํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์•„๋ฌดํŠผ!

๊ฐ„๋‹จํ•˜๊ฒŒ ์ ˆ์ฐจ๋ฅผ ์ •๋ฆฌํ•ด๋ณด์ž.

  • ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์— Authorization ํ—ค๋”๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”๊ฐ€?
  • ํฌํ•จ๋๋‹ค๋ฉด?
    • ํ† ํฐ ์œ ํšจ์—ฌ๋ถ€ ๊ฒ€์ฆ
    • ํ† ํฐ์˜ subject ํŒŒ์‹ฑ (username)
    • username์œผ๋กœ UserDetails๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
    • UserDetails๋กœ ํ† ํฐ(ํ†ตํ–‰์ฆ) ์„ ์ƒ์„ฑํ•œ๋‹ค. (UsernamePasswordAuthenticationToken)
    • ํ† ํฐ์— ์ž„์˜์œผ๋กœ ์ธ์ฆ์ฒดํฌ๋ฅผ ํ•œ๋‹ค. (์ด ๋ถ€๋ถ„ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค.)
    • ์ธ์ฆ๋œ ํ† ํฐ์„ SecurityContextHolder์— ์ €์žฅํ•œ๋‹ค.
  • ํฌํ•จ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด?
    • ๊ทธ๋ƒฅ ์ธ์ฆ์ด ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ๋„˜์–ด๊ฐ€๋„๋ก doFilter ์ฒ˜๋ฆฌ
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticateFilter extends OncePerRequestFilter {

    private final UserDetailsServiceImpl userDetailsService;
    private final JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorization = request.getHeader("Authorization");

        if (authorization != null && authorization.startsWith("Bearer ")) {

            String token = authorization.substring(7);
            if (jwtUtils.isValidToken(token)) {
                log.info("JWT ํ•„ํ„ฐ = {}", token);
                String username = jwtUtils.getUsernameFromToken(token);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                        = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}


๋งˆ์ง€๋ง‰์œผ๋กœ Controller์—์„œ์˜ ์ฒ˜๋ฆฌ์ด๋‹ค.
์ด ์—”ํŠธํฌ์ธํŠธ์— ์™”๋‹ค๋Š” ๊ฒƒ์€ ํ˜„์žฌ JWT ํ† ํฐ์ด ์—†๋Š” ์ƒํƒœ์—์„œ ๋กœ๊ทธ์ธ์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
์ „๋‹ฌ๋ฐ›์€ ์•„์ด๋””, ํŒจ์Šค์›Œ๋“œ๋ฅผ ๊ฒ€์ฆํ•ด๋ณด๊ณ  ์ด์ƒ์—†๋‹ค๋ฉด ์ „๋‹ฌ๋ฐ›์€ ๋กœ๊ทธ์ธ ์ •๋ณด๋กœ UserDetails ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ์ด๋ฅผ ์ด์šฉํ•ด์„œ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๋Š” ๋ฐ›์•„์„œ ์ž˜ ์ €์žฅํ•˜๊ณ  ์ž˜ ๋ณด๋‚ด์ฃผ๋ฉด ๋œ๋‹ค.

    @PostMapping("/signin")
    public TokenResponseDto signin(@RequestBody SignupRequestDto requestDto) {
        log.info("๋กœ๊ทธ์ธ ์š”์ฒญ = {}", requestDto.getUsername());
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(requestDto.getUsername(), requestDto.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new BadCredentialsException("๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
        }

        UserDetails userDetails = userDetailsService.loadUserByUsername(requestDto.getUsername());
        String token = jwtUtils.createToken(userDetails.getUsername());
        log.info("๋กœ๊ทธ์ธ ํ›„ ํ† ํฐ = {}", token);
        return new TokenResponseDto(token, "๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }



์ด ๋ฐฉ๋Œ€ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ฃฐ์„ ๋”ฐ๋ผ์•ผ ํ•˜๋Š” ๊ฒƒ์ด ๊ฐœ๋ฐœ์„ ํž˜๋“ค๊ฒŒ ํ•˜์ง€๋งŒ ๋‹ค ์ด์œ ๊ฐ€ ์žˆ์ง€ ์•Š์„๊นŒ ์‹ถ๋‹ค..ใ…Ž ๊ผญ ์ด์œ ๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค ..

profile
์ข€ ๋” ์ฒœ์ฒœํžˆ ๊นŒ๋จน๊ธฐ ์œ„ํ•ด ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๐Ÿง

0๊ฐœ์˜ ๋Œ“๊ธ€