๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

์Šค๋งˆ์ผ๊ฒŒ์ดํŠธ ์„œ๋ฒ„๊ฐœ๋ฐœ์บ ํ”„ 4๊ธฐ

[์„œ๋ฒ„๊ฐœ๋ฐœ์บ ํ”„] ์ธ์ฆ ์„œ๋ฒ„ - ๋กœ๊ทธ์ธ : JWT + Redis

๋กœ๊ทธ์ธ

 JWT ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ „์ฒด์ ์ธ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1. ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์š”์ฒญ
2. ์‚ฌ์šฉ์ž ํ™•์ธ ํ›„, access token๊ณผ refresh token ๋ฐœ๊ธ‰
3. refresh token์„ Redis์— ์ €์žฅ
4. ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์š”์ฒญ ์‹œ, Redis์— refresh token ์ •๋ณด๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ ํ›„ ๋ฐœ๊ธ‰
5. ๋กœ๊ทธ์•„์›ƒ ์‹œ, Redis์— refresh token ์ •๋ณด ์‚ญ์ œ

 access token์€ 30๋ถ„, refresh token์€ 2์ฃผ์˜ ์œ ํšจ๊ธฐ๊ฐ„์„ ๋‘์—ˆ๋‹ค. refresh token์˜ ๊ฒฝ์šฐ ์œ ํšจ๊ธฐ๊ฐ„์ด ๊ธธ๊ธฐ ๋•Œ๋ฌธ์—, ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก Redis์— ์ €์žฅํ–ˆ๋‹ค. refresh token์„ ํ†ตํ•ด ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์š”์ฒญ ์‹œ, refresh token์˜ ์œ ํšจ์„ฑ ์—ฌ๋ถ€ ๋ฟ๋งŒ์•„๋‹ˆ๋ผ Redis์— refresh token ์ •๋ณด๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ ํ›„ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•œ๋‹ค. ๋กœ๊ทธ์•„์›ƒ ์‹œ์— Redis์— ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ refresh token์„ ์‚ญ์ œํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ์—์„œ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ ํ•  ์ˆ˜ ์—†๋Š” ๊ตฌ์กฐ์ด๋‹ค.

 

UserService.java

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final MailUtil mailUtil;
    private final RedisUtil redisUtil;

    public TokenResponseDto signin(SigninRequestDto signinRequestDto) {

        String email = signinRequestDto.getEmail();
        String password = signinRequestDto.getPassword();

        User user = userRepository.findByEmail(email);
        if(user==null) throw new EmailNotExistException(email);

        userRepository.updateAccessedAt(user.getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        if(!passwordEncoder.matches(password, user.getPasswd())) throw new PasswordWrongException();

        String accessToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getNickname(), user.getRole(), "ACCESS_TOKEN", 30);
        String refreshToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getNickname(), user.getRole(), "REFRESH_TOKEN", 60*24*14);

        redisUtil.set(refreshToken, user.getRole(), 60*24*14);

        return TokenResponseDto.builder()
                .id(user.getId())
                .email(email)
                .nickname(user.getNickname())
                .role(user.getRole())
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    public void signout(String refreshToken) {
        if(!redisUtil.delete(refreshToken)) throw new SignoutException();
    }

    public TokenResponseDto refreshToken(String refreshToken) throws ExpiredJwtException {

        // ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ์—์„œ refresh ์š”์ฒญ
        if(!redisUtil.hasKey(refreshToken)) throw new UnauthorizedException();

        Claims claims = jwtUtil.getClaims(refreshToken);

        int userId = (int) claims.get("userId");
        String email = claims.getSubject();
        String nickname  = (String) claims.get("nickname");
        int role = (int) claims.get("role");

        String accessToken = jwtUtil.createToken(userId, email, nickname, role, "ACCESS_TOKEN", 30);

        return TokenResponseDto.builder()
                .id(userId)
                .email(email)
                .nickname(nickname)
                .role(role)
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }
}

 

ํ† ํฐ ๊ฒ€์‚ฌ ๋ฐ ์‚ฌ์šฉ

 ์ธ์ฆ์„œ๋ฒ„์—์„œ ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ† ํฐ์€ ๋‹ค๋ฅธ ์„œ๋น„์Šค ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ์ •๋ณด ์ œ๊ณตํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ๋‚˜๋Š” ํ˜„์žฌ ๊ฐœ๋ฐœ ์ค‘์ธ ์ž๊ธฐ์†Œ๊ฐœ์„œ ์„œ๋ฒ„์—์„œ, ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ํ†ตํ•ด ํ† ํฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ , ๊ฐ๊ฐ์˜ ์š”์ฒญ์— ๋งž๊ฒŒ ์„œ๋น„์Šค ๋‹จ์—์„œ ํ† ํฐ์— ๋‹ด๊ธด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ†ตํ•ด, ์š”์ฒญํ•œ ๋ฆฌ์†Œ์Šค(์ž๊ธฐ์†Œ๊ฐœ์„œ ๋“ฑ)์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€ ์—ฌ๋ถ€ ๋“ฑ์„ ํŒ๋‹จํ•œ๋‹ค.

 ํ˜„์žฌ ๊ฐœ๋ฐœ ์ค‘์ธ ์„œ๋น„์Šค์—์„œ๋Š” ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค ์„œ๋ฒ„๊ฐ€ ํ† ํฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ฐ์ž ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, API Gateway๋ฅผ ๊ฐœ๋ฐœํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ๋กœ์ง์„ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„๋“ฏ ํ•˜๋‹ค.

JwtCheckInterceptor.java

@Component
public class JwtCheckInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = jwtUtil.getToken(request);

        if(token.equals("Bearer")) throw new TokenNotExistException();

        if(!(jwtUtil.isValidToken(token) && jwtUtil.isAccessToken(token))) {
            throw new InvalidTokenException();
        }

        return true;
    }

}

 

WebMvcConfig.java

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private JwtCheckInterceptor jwtCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtCheckInterceptor).addPathPatterns("/**");
    }
}

 

ResumeService.java

@RequiredArgsConstructor
@Service
public class ResumeService {

    private final ResumeRepository resumeRepository;
    private final JwtUtil jwtUtil;

    public ResumeDetailResponseDto getResume(String token, int resumeId) {

        if(!checkAuth(token, resumeId)) return null;

        Resume resume = resumeRepository.findResumeById(resumeId);
        List<Answer> answers = resumeRepository.findAnswersByResumeId(resumeId);

        return ResumeDetailResponseDto.builder()
                .resume(resume)
                .answers(answers)
                .build();
    }

    private boolean checkAuth(String token, int resumeId) {

        Resume resume = resumeRepository.findResumeById(resumeId);
        if(resume == null) throw new ResumeNotExistException(resumeId);

        int userId = getUserId(token);
        if(userId != resume.getUserId()) throw new UnauthorizedException();

        return true;
    }

    private int getUserId(String token) {
        Claims claims = jwtUtil.getClaims(token.substring("Bearer ".length()));
        return (int) claims.get("userId");
    }
}