짧은 프로젝트

Springboot jwt 로그인3(redis 리프레시토큰)

디비드킴 2024. 1. 12. 20:53

1.레디스 설정

@Configuration
public class RedisConfig {



    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
//        if(mode.equals("1")){
//            RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
//            clusterConfiguration.clusterNode(host, port);
//            LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
//                    .clientOptions(ClientOptions.builder()
//                            .socketOptions(SocketOptions.builder()
//                                    .build())
//                            .build()).build();
//            return new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);
//        }
        return new LettuceConnectionFactory("localhost",6379);

    }
    @Bean
    public RedisTemplate<String,Object> redisTemplate() {
        RedisTemplate<String,Object>redisTemplate=new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

2.jwt 서비스 작성

@Service
@RequiredArgsConstructor
public class TokenService {

    private final RedisTemplate<String, Object> redisTemplate;
    private  String jwtSecret="jwtSecret";

    public String generateToken(String username, long expiration) {
        try {
            Algorithm algorithm = Algorithm.HMAC512(jwtSecret);
            Date expiryDate = new Date(System.currentTimeMillis() + expiration);

            return JWT.create()
                    .withSubject(username)
                    .withIssuedAt(new Date())
                    .withExpiresAt(expiryDate)
                    .sign(algorithm);
        } catch (JWTCreationException e) {
            throw new RuntimeException("Error creating JWT token", e);
        }
    }

    public void saveRefreshToken(String username, String refreshToken, LocalDateTime issueDate, long issueCount) {
        // TokenInfo 객체 생성
        Map<String,Object> tokenInfo = new HashMap<>();
        tokenInfo.put("id",username);
        tokenInfo.put("refreshToken",refreshToken);
        tokenInfo.put("issueDate",issueDate.toString());
        tokenInfo.put("issueCount",issueCount);
        // 여기에서 username을 사용하여 리프레시 토큰 정보를 객체로 직렬화하여 저장
        redisTemplate.opsForHash().put("refreshToken"+username, username,tokenInfo);
        // 만료 시간 설정 (예: 1시간)
        redisTemplate.expire("refreshToken" + username, 1, TimeUnit.HOURS);
    }

    public Map<String,Object> getRefreshTokenInfo(String username) {
        // 여기에서 username을 사용하여 리프레시 토큰 정보를 역직렬화하여 추출
        return (Map<String,Object>) redisTemplate.opsForHash().get("refreshToken" + username,username);
    }
}

3.로그인 필터 추가 로직 작성

@Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info("로그인 성공");

        // 로그인 성공 시 JWT 토큰 발급
        String jwtToken = tokenService.generateToken("access", jwtExpirationInMs);
        String refreshToken = tokenService.generateToken("refresh", refreshExpirationInMs);

        // JWT 토큰을 응답 쿠키에 추가
        ResponseCookie jwtCookie = ResponseCookie.from("access_token", jwtToken)
                .path("/")
                .httpOnly(true)
                .maxAge(jwtExpirationInMs / 1000)
                .sameSite("None")
                .secure(true)
                .build();
        response.addHeader("Set-Cookie", jwtCookie.toString());

        ResponseCookie refreshCookie = ResponseCookie.from("refresh_token", refreshToken)
                .path("/")
                .httpOnly(true)
                .maxAge(refreshExpirationInMs / 1000)
                .sameSite("None")
                .secure(true)
                .build();
        response.addHeader("Set-Cookie", refreshCookie.toString());

        // 로그인 성공 시 200 응답 코드만 반환
        response.setStatus(HttpServletResponse.SC_OK);

        // 응답 본문에 JSON 추가
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResponse = objectMapper.writeValueAsString(Map.of("message", "login done"));
        response.getWriter().write(jsonResponse);
        response.getWriter().flush();

        // 로그인 성공한 유저의 이름 얻어내기
        SecurityContextHolder.getContext().setAuthentication(authResult);
        PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
        String username = principalDetails.getUsername();
        LocalDateTime issueDate = LocalDateTime.now();
        long issueCount = 0;
        // 리프레시 토큰 저장
        tokenService.saveRefreshToken(username, refreshToken,issueDate,issueCount);

        // 시큐리티 세션에 인증 주입
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()));

        // 레디스에 저장된 리프레시 토큰 정보 확인
        Map<String,Object> savedTokenInfo = tokenService.getRefreshTokenInfo(username);
        if (savedTokenInfo != null) {
            // 레디스에 저장된 리프레시 토큰 정보가 있는 경우 로그 출력
            log.info("Refresh Token Info from Redis: {}", savedTokenInfo);
        } else {
            // 레디스에 저장된 리프레시 토큰 정보가 없는 경우 로그 출력
            log.info("Refresh Token Info not found in Redis for user: {}", username);
        }
    }

 

테스트

 

소스

https://github.com/novb1492/login/tree/set-auth

 

GitHub - novb1492/login

Contribute to novb1492/login development by creating an account on GitHub.

github.com

 

버그fix

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    log.info("로그인 성공");

    // 로그인 성공한 유저의 이름 얻어내기
    SecurityContextHolder.getContext().setAuthentication(authResult);
    PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
    String username = principalDetails.getUsername();

    // 로그인 성공 시 JWT 토큰 발급
    String jwtToken = tokenService.generateToken(username, jwtExpirationInMs);
    String refreshToken = tokenService.generateToken(username, refreshExpirationInMs);

    // JWT 토큰을 응답 쿠키에 추가
    ResponseCookie jwtCookie = ResponseCookie.from("access_token", jwtToken)
            .path("/")
            .httpOnly(true)
            .maxAge(jwtExpirationInMs * 60)
            .sameSite("none")
            .secure(true)
            .build();
    response.addHeader("Set-Cookie", jwtCookie.toString());

    ResponseCookie refreshCookie = ResponseCookie.from("refresh_token", refreshToken)
            .path("/")
            .httpOnly(true)
            .maxAge(refreshExpirationInMs*60)
            .sameSite("none")
            .secure(true)
            .build();
    response.addHeader("Set-Cookie", refreshCookie.toString());


    // 로그인 성공 시 200 응답 코드만 반환
    response.setStatus(HttpServletResponse.SC_OK);

    // 응답 본문에 JSON 추가
    ObjectMapper objectMapper = new ObjectMapper();
    String jsonResponse = objectMapper.writeValueAsString(Map.of("message", "login done"));
    response.getWriter().write(jsonResponse);
    response.getWriter().flush();

    LocalDateTime issueDate = LocalDateTime.now();
    long issueCount = 0;
    // 리프레시 토큰 저장
    tokenService.saveRefreshToken(username, refreshToken,issueDate,issueCount);

    // 시큐리티 세션에 인증 주입
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()));

    // 레디스에 저장된 리프레시 토큰 정보 확인
    Map<String,Object> savedTokenInfo = tokenService.getRefreshTokenInfo(username);
    if (savedTokenInfo != null) {
        // 레디스에 저장된 리프레시 토큰 정보가 있는 경우 로그 출력
        log.info("Refresh Token Info from Redis: {}", savedTokenInfo);
    } else {
        // 레디스에 저장된 리프레시 토큰 정보가 없는 경우 로그 출력
        log.info("Refresh Token Info not found in Redis for user: {}", username);
    }
}