# Kakao OAuth2 + JWT + Redis๋ฅผ ํ†ตํ•œ ์ธ์ฆ ๊ณผ์ • ๊ตฌํ˜„ (2) - JWT ์ ์šฉ
Study Repository

Kakao OAuth2 + JWT + Redis๋ฅผ ํ†ตํ•œ ์ธ์ฆ ๊ณผ์ • ๊ตฌํ˜„ (2) - JWT ์ ์šฉ

by rlaehddnd0422

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ์นด์นด์˜ค ๋กœ๊ทธ์ธ๊ณผ ๊ทธ ํ›„์ฒ˜๋ฆฌ ๊ณผ์ •๋“ค์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ loadUser() ๋ฉ”์†Œ๋“œ์—์„œ

  • ์ตœ์ดˆ ๋กœ๊ทธ์ธ์˜ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํšŒ์›์„ ์ €์žฅํ•˜๊ณ , Details ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด Authentication ๊ฐ์ฒด์— ๋‹ด๊ณ , SecurityContext์— Authentication ๊ฐ์ฒด๋ฅผ ๋ณด๊ด€ํ•˜๋„๋ก ์„ค์ •ํ•˜์˜€๊ณ 
  • ์ตœ์ดˆ ๋กœ๊ทธ์ธ์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํšŒ์›์„ ๊ฐ€์ ธ์™€ Details ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด Authentication ๊ฐ์ฒด์— ๋‹ด๊ณ , SecurityContext์— Authentication ๊ฐ์ฒด๋ฅผ ๋ณด๊ด€ํ•˜๋„๋ก ์„ค์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” JWT๋ฅผ ํ”„๋กœ์ ํŠธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ–ˆ๋Š”์ง€ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

* ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋‹จ์ˆœ JWT ์ ์šฉ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ณ , accessToken ๋งŒ๋ฃŒ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ๊ณผ์ •์€ ๋‹ค์Œ ํฌ์ŠคํŒ…์— ๊ธฐ๋กํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

 

JWT๊ฐ€ ์–ด๋–ค ์ธ์ฆ ๋ฐฉ์‹์ธ์ง€๋Š” ์•„๋ž˜ ํฌ์ŠคํŒ…์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

 

[Security] JWT(Json Web Token) ์ธ์ฆ ๋ฐฉ์‹

JWT(JSON Web Token)์€ ์ธ์ฆ์— ํ•„์š”ํ•œ ์ •๋ณด๋“ค์„ ์•”ํ˜ธํ™” ์‹œํ‚จ JSON ํ† ํฐ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹์€ JWT์„ HTTP ํ—ค๋”์— ์‹ค์–ด ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. โ—๏ธJWT(Json Web Token) ํด๋ผ์ด

rlaehddnd0422.tistory.com


0. ์„ค์ • ์ •๋ณด 

application.yml

jwt:
  secret_key: ${jwt.secret_key}
  access-token-validity-in-seconds: 30000 # (๋ฐฐํฌ ์‹œ 30๋ถ„ -> 1800 ์„ค์ •)
  refresh-token-validity-in-seconds: 86400 # (๋ฐฐํฌ ์‹œ 1์ผ -> 86400์„ค์ •)

 

์‹œ๊ทธ๋‹ˆ์ฒ˜์— ์‚ฌ์šฉํ•  ์‹œํฌ๋ฆฟ ํ‚ค์™€, ์•ก์„ธ์Šค ํ† ํฐ ๋งŒ๋ฃŒ์‹œ๊ฐ„, ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ yml์— ์ง€์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์‹œํฌ๋ฆฟ ํ‚ค ๊ฐ’์€ ์ค‘์š”ํ•œ ์ •๋ณด์ด๋‹ˆ๋งŒํผ github๋กœ ํ˜•์ƒ๊ด€๋ฆฌํ•˜์ง€ ์•Š๊ณ , ๋ณ„๋„๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ์— ์„ค์ •ํ•ด์ฃผ๊ณ  ํ•ด๋‹น ํŒŒ์ผ์€ gitignore์— ๋“ฑ๋กํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

build.gradle์— ์˜์กด์„ฑ ์ถ”๊ฐ€

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

 


1. TokenProvider - ์‚ฌ์šฉ์ž ์ •๋ณด๋กœ JWT ํ† ํฐ ์ƒ์„ฑ

@Component
public class TokenProvider {

    private static final String AUTH_KEY = "AUTHORITY";
    private static final String AUTH_EMAIL = "EMAIL";

    private final String secretKey;
    private final long accessTokenValidityMilliSeconds;
    private final long refreshTokenValidityMilliSeconds;

    private Key secretkey;

    public TokenProvider(@Value("${jwt.secret_key}") String secretKey,
                         @Value("${jwt.access-token-validity-in-seconds}") long accessTokenValiditySeconds,
                         @Value("${jwt.refresh-token-validity-in-seconds}") long refreshTokenValiditySeconds) {
        this.secretKey = secretKey;
        this.accessTokenValidityMilliSeconds = accessTokenValiditySeconds * 1000;
        this.refreshTokenValidityMilliSeconds = refreshTokenValiditySeconds * 1000;
    }

    @PostConstruct
    public void initKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        this.secretkey = Keys.hmacShaKeyFor(keyBytes);
    }
    ...
}

 

๊ฐ ํ† ํฐ์— ๋Œ€ํ•œ ๋งŒ๋ฃŒ์‹œ๊ฐ„๊ณผ secreyKey ๊ฐ’์€ yml ์„ค์ • ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ์ƒ์„ฑ์ž ์ฃผ์ž…ํ•˜๊ณ , ์ƒ์„ฑ์ž ์ฃผ์ž… ํ›„ JWT ์ƒ์„ฑ์— ์‚ฌ์šฉ๋  Key๋Š” initKey() ๋ฉ”์†Œ๋“œ๋กœ secretKey๋ฅผ decodeํ•˜์—ฌ Key์— ์ฃผ์ž…ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

	// access, refresh Token ์ƒ์„ฑ
    public TokenDto createToken(String email, String role) {
        long now = (new Date()).getTime();

        Date accessValidity = new Date(now + this.accessTokenValidityMilliSeconds);
        Date refreshValidity = new Date(now + this.refreshTokenValidityMilliSeconds);

        String accessToken = Jwts.builder()
                .addClaims(Map.of(AUTH_EMAIL, email))
                .addClaims(Map.of(AUTH_KEY, role))
                .signWith(secretkey, SignatureAlgorithm.HS256)
                .setExpiration(accessValidity)
                .compact();

        String refreshToken = Jwts.builder()
                .addClaims(Map.of(AUTH_EMAIL, email))
                .addClaims(Map.of(AUTH_KEY, role))
                .signWith(secretkey, SignatureAlgorithm.HS256)
                .setExpiration(refreshValidity)
                .compact();

        return TokenDto.of(accessToken, refreshToken);
    }
    
    // token์ด ์œ ํšจํ•œ ์ง€ ๊ฒ€์‚ฌ
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            return false;
        } catch (UnsupportedJwtException e) {
            return false;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    // token์ด ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์‚ฌ
    public boolean validateExpire(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (ExpiredJwtException e) {
            return false;
        }
    }
 	
    // token์œผ๋กœ๋ถ€ํ„ฐ Authentication ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋ฆฌํ„ดํ•˜๋Š” ๋ฉ”์†Œ๋“œ
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody();

        List<String> authorities = Arrays.asList(claims.get(AUTH_KEY)
                .toString()
                .split(","));

        List<? extends GrantedAuthority> simpleGrantedAuthorities = authorities.stream()
                .map(auth -> new SimpleGrantedAuthority(auth))
                .collect(Collectors.toList());

        KakaoMemberDetails principal = new KakaoMemberDetails(
                (String) claims.get(AUTH_EMAIL),
                simpleGrantedAuthorities, Map.of());

        return new UsernamePasswordAuthenticationToken(principal, token, simpleGrantedAuthorities);
    }
}

 

  • TokenProvider์—์„œ ํ† ํฐ ์ƒ์„ฑ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ† ํฐ ๊ฒ€์ฆ ๊ด€๋ จ ๋ฉ”์†Œ๋“œ์™€, ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ Authentication ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

TokenProvider๋ผ๊ธฐ์—๋Š” ํ† ํฐ ์ƒ์„ฑ ์™ธ์˜ ๊ธฐ๋Šฅ๋“ค๋„ ์žˆ์–ด ์ถ”ํ›„ ํด๋ž˜์Šค ๋ถ„๋ฆฌ๋ฅผ ํ•˜๋˜์ง€, ๋ฆฌ๋„ค์ด๋ฐ ํ•˜๋˜์ง€ ์ˆ˜์ •ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค !

 

createToken()

public TokenDto createToken(String email, String role) {
    long now = (new Date()).getTime();

    Date accessValidity = new Date(now + this.accessTokenValidityMilliSeconds);
    Date refreshValidity = new Date(now + this.refreshTokenValidityMilliSeconds);

    String accessToken = Jwts.builder()
            .addClaims(Map.of(AUTH_EMAIL, email))
            .addClaims(Map.of(AUTH_KEY, role))
            .signWith(secretkey, SignatureAlgorithm.HS256)
            .setExpiration(accessValidity)
            .compact();

    String refreshToken = Jwts.builder()
            .addClaims(Map.of(AUTH_EMAIL, email))
            .addClaims(Map.of(AUTH_KEY, role))
            .signWith(secretkey, SignatureAlgorithm.HS256)
            .setExpiration(refreshValidity)
            .compact();

    return TokenDto.of(accessToken, refreshToken);
}
  • createToekn ๋ฉ”์†Œ๋“œ์—์„œ email๊ณผ role๊ณผ ์œ„์—์„œ ์ฃผ์ž…ํ•œ ์‹œํฌ๋ฆฟ ํ‚ค์™€ ํ•จ๊ป˜ accessToken๊ณผ refreshToken์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 
  • ์ด ์™ธ์˜ ๋ฉ”์†Œ๋“œ๋Š” ์–ด๋ ต์ง€ ์•Š์œผ๋ฏ€๋กœ ๋”ฐ๋กœ ์„ค๋ช…ํ•˜์ง€๋Š” ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค !

์ด์ œ ํ›„์ฒ˜๋ฆฌ ์„œ๋น„์Šค๋กœ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚œ ํ›„ ์ด ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด Token์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐœ๊ธ‰ํ•˜๋„๋ก ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 


2. OAuth2SuccessHandler 

@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private static final String REDIRECT_URI = "http://localhost:8080/api/sign/login/kakao?accessToken=%s&refreshToken=%s";

    private final TokenProvider tokenProvider;
    private final MemberRepository memberRepository;

    @Transactional
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        KakaoUserInfo kakaoUserInfo = new KakaoUserInfo(oAuth2User.getAttributes());

        Member member = memberRepository.findByEmail(kakaoUserInfo.getEmail())
                .orElseThrow(MemberNotFoundException::new);

        TokenDto tokenDto = tokenProvider.createToken(member.getEmail(), member.getRole().name());

        String redirectURI = String.format(REDIRECT_URI, tokenDto.getAccessToken(), tokenDto.getRefreshToken());
        getRedirectStrategy().sendRedirect(request, response, redirectURI);
    }
  • SimpleUrlAuthenticationSuccessHandler๋ฅผ ์ƒ์†ํ•˜์—ฌ onAuthenticaitonSuccess() ๋ฉ”์†Œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋ฉด  Authentication ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”.
  • ํ›„์ฒ˜๋ฆฌ ์„œ๋น„์Šค์ธ KakaoMemberDetailsService์—์„œ ์‹œํ๋ฆฌํ‹ฐ ์ปจํ…์ŠคํŠธ์— Authentication ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•œ ๋•๋ถ„์— ์ด๋กœ๋ถ€ํ„ฐ ์ €๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊บผ๋‚ด์™€ token ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด ํ† ํฐ ์ •๋ณด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋‹ด์•„ redirect URI๋กœ ๋ณด๋‚ด๋„๋ก ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋“ฑ๋กํ•œ ํ•ธ๋“ค๋Ÿฌ๋Š” SecurityConfig์— ์ถ”๊ฐ€์ ์œผ๋กœ ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค !

 

SecurityConfig์— ๋“ฑ๋ก

.oauth2Login(oAuth2Login -> {
    oAuth2Login.userInfoEndpoint(userInfoEndpointConfig ->
            userInfoEndpointConfig.userService(kakaoMemberDetailsService)); // 1
    oAuth2Login.successHandler(oAuth2SuccessHandler); // 2
});

 

** ์นด์นด์˜ค ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ 1๋ฒˆ์ด ๋จผ์ € ์‹คํ–‰๋˜๊ณ , ๊ทธ ํ›„ 2๋ฒˆ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. 

 

์œ„ onAuthenticationSuccess() ๋ฉ”์†Œ๋“œ์—์„œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋œ URI๋Š” ์•„๋ž˜ ์ปจํŠธ๋กค๋Ÿฌ์— ๋งคํ•‘๋˜์–ด Json์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/sign")
public class SignController {

    @GetMapping("/login/kakao")
    public ResponseEntity loginKakao(@RequestParam(name = "accessToken") String accessToken,
                                     @RequestParam(name = "refreshToken") String refreshToken) {
        return new ResponseEntity(TokenDto.of(accessToken, refreshToken), HttpStatus.OK);
    }
    ...

 

ํ† ํฐ ๋ฐœ๊ธ‰์€ ๋ชจ๋‘ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์š”์ฒญ์ด ์™”์„ ๋•Œ, ์‚ฌ์šฉ์ž๊ฐ€ ํ—ค๋”์— ์ฒจ๋ถ€ํ•œ ํ† ํฐ๋“ค์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆํ•˜๋Š” ํ•„ํ„ฐ์™€ ํ•„ํ„ฐ์— ํ†ต๊ณผํ•˜์ง€ ๋ชปํ–ˆ์„ ๋•Œ ์ฒ˜๋ฆฌํ•  handler๋ฅผ ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 


3. JWT Filter 

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

    private static final String ACCESS_HEADER = "AccessToken";
    private static final String REFRESH_HEADER = "RefreshToken";

    private final TokenProvider tokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // #1
        if (isRequestPassURI(request, response, filterChain)) {
            return;
        }

        String accessToken = getTokenFromHeader(request, ACCESS_HEADER);

        if (tokenProvider.validate(accessToken) && tokenProvider.validateExpire(accessToken)) {
            SecurityContextHolder.getContext().setAuthentication(tokenProvider.getAuthentication(accessToken));
        }

        filterChain.doFilter(request, response);
    }
    ...
}
  • ๋งŒ์•ฝ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ์š”์ฒญ URI๋ผ๋ฉด 1๋ฒˆ ๋กœ์ง์ด ์‹คํ–‰๋˜์–ด ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.
    • ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์ด๋ผ ํ•จ์€ ๋กœ๊ทธ์ธ api, ์˜ˆ์™ธ์ฒ˜๋ฆฌ end point api ๋“ฑ๋“ฑ
  • ๋งŒ์•ฝ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ํ•„์š”ํ•œ ์š”์ฒญ์ด๋ผ๋ฉด getTokenFromHeader๋ฅผ ํ†ตํ•ด ํ—ค๋”์—์„œ accessToken์„ ๊ฐ€์ ธ์˜ค๊ณ  tokenProvider์—์„œ ๊ฒ€์ฆ์„ ์œ„์ž„ํ•˜์—ฌ ํ†ต๊ณผํ•˜๋ฉด, ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ authentication ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์‹œํ๋ฆฌํ‹ฐ ์ปจํ…์ŠคํŠธ์— ๋ณด๊ด€ํ•˜๊ณ  ํ•„ํ„ฐ์— ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒ€์ฆ์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋Š” ์•„๋ž˜์—์„œ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌ
  • ์ด ํ•„ํ„ฐ ๋˜ํ•œ SecurityConfig์— ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

SecurityConfig์— ๋“ฑ๋ก

.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
  • ๋ณ„๋„์˜ ์ž์ฒด ์ธ์ฆ ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ JwtFilter๋ฅผ ์œ„์™€ ๊ฐ™์ด ๋“ฑ๋กํ•˜์—ฌ ๊ธฐ์กด ์‹œํ๋ฆฌํ‹ฐ ๋กœ์ง์˜ UsernamePasswordAuthenticationFilter๋ฅผ ๋Œ€์ฒดํ•ด์ค์‹œ๋‹ค.

4. JWT Handler 

๋งˆ์ง€๋ง‰์œผ๋กœ JWT Filter๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ์— ๋Œ€ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ†ต๊ณผ ํ•˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ๋ผ ํ•จ์€ ์ธ์ฆ์— ํ†ต๊ณผ๋˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ, ๋˜๋Š” ์ธ๊ฐ€(๊ถŒํ•œ)์— ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

1) ์ธ์ฆ ์‹คํŒจ

@Component
public class JwtAuthenticationFailEntryPoint implements AuthenticationEntryPoint {

    private static final String EXCEPTION_ENTRY_POINT = "/api/exception/entry-point";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendRedirect(EXCEPTION_ENTRY_POINT);
    }
}
  • ์ธ์ฆ์— ๋Œ€ํ•œ ์‹คํŒจ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ๋Š” AuthenticationEntryPoint ์ธํ„ฐํŽ˜์ด์Šค์˜ commence ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

2) ์ธ๊ฐ€ ์‹คํŒจ (๊ถŒํ•œ ๋ฏธ๋‹ฌ)

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    private static final String EXCEPTION_ACCESS_HANDLER = "/api/exception/access-denied";

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException {
        response.sendRedirect(EXCEPTION_ACCESS_HANDLER);
    }
}
  • ์ธ๊ฐ€์— ๋Œ€ํ•œ ์‹คํŒจ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ๋Š” AccessDeniedHandler ์ธํ„ฐํŽ˜์ด์Šค์˜ handler ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ์ธ์ฆ/์ธ๊ฐ€์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ, ์•„๋ž˜ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํ•ด๋‹น URI์„ ๋ฐ›์•„ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ์ฒ˜๋ฆฌํ•˜์˜€๊ณ , ์˜ˆ์™ธ๋Š” @ExceptionAdvisor์—์„œ ๋ฐ›์•„์„œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ตํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

@RestController
@RequestMapping("/api/exception")
public class ExceptionController {

    @GetMapping("/access-denied")
    public void accessDeniedException() {
        throw new AccessDeniedException();
    }

    @GetMapping("/entry-point")
    public void authenticateException() {
        throw new AuthenticationEntryPointException();
    }
}

 

** ์ด API์— ๋Œ€ํ•œ ์ ‘๊ทผ ๋˜ํ•œ Jwtํ•„ํ„ฐ์—์„œ ์ถ”๊ฐ€์ ์œผ๋กœ isRequestPassURI์— ๋“ฑ๋กํ•ด์ค์‹œ๋‹ค !

 

 

@RestControllerAdvice
public class ExceptionAdvisor {

    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ResponseEntity accessDeniedException(AccessDeniedException e) {
        return new ResponseEntity("์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ถŒํ•œ์ž…๋‹ˆ๋‹ค.", HttpStatus.UNAUTHORIZED);
    }

    @ExceptionHandler(AuthenticationEntryPointException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ResponseEntity authenticationEntryPointException(AuthenticationEntryPointException e) {
        return new ResponseEntity("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.", HttpStatus.UNAUTHORIZED);
    }

 

SecurityConfig์— ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก

.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)

.exceptionHandling(exceptionHandling -> {
    exceptionHandling.authenticationEntryPoint(jwtAuthenticationFailEntryPoint);
    exceptionHandling.accessDeniedHandler(jwtAccessDeniedHandler);
});
  • ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•ธ๋“ค๋Ÿฌ๊นŒ์ง€ ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •์ •๋ณด์— ์œ„์™€ ๊ฐ™์ด ๋“ฑ๋กํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ JWT๋ฅผ ๋„์ž…ํ•ด๋ณด์•˜๋Š”๋ฐ, ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋งˆ์ง€๋ง‰์œผ๋กœ redis๋ฅผ ๋„์ž…ํ•˜์—ฌ accessToken๊ฐ€ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค !

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ