jwt_oauth2설명 부족한점 적용 내역

jwt_oauth2설명 부족한점.md에서 지적한 항목들을 코드와 설명 모두에 적용한 내역.


코드 변경

1. InMemoryAuthorizationRequestRepository — new Thread()ScheduledExecutorService

문제: 매 요청마다 new Thread()로 스레드를 생성하는 것은 운영 환경에서 부적절.

변경:

// Before
new Thread(() -> {
    try {
        TimeUnit.MINUTES.sleep(5);
        authorizationRequests.remove(state);
    } catch (InterruptedException ignored) {}
}).start();

// After
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
// ...
scheduler.schedule(() -> authorizationRequests.remove(state), 5, TimeUnit.MINUTES);

2. JwtUtil.getTokenType() — JwtException 처리 추가

문제: ExpiredJwtException만 catch하고, 변조/잘못된 형식 등의 JwtException은 uncaught.

변경:

// Before: ExpiredJwtException만 catch

// After: JwtException | IllegalArgumentException 추가
} catch (ExpiredJwtException e) {
    Claims claims = e.getClaims();
    return claims != null ? claims.get("token_type", String.class) : null;
} catch (JwtException | IllegalArgumentException e) {  // 변조, 잘못된 형식 등
    return null;
}

3. RefreshController — 만료 검증 → 삭제 순서 변경

문제: 삭제를 먼저 하고 만료를 나중에 검증하면, 만료된 토큰으로 재시도 시 “discarded” 에러가 나와 혼란.

변경:

// Before: 존재 확인 → 삭제 → 만료 검증
// After:  존재 확인 → 만료 검증(실패 시 삭제 후 에러) → 삭제(Rotation)

if (!refreshService.existsByToken(token)) { ... }  // 1. 존재 확인

if (!jwtUtil.validateToken(token)) {                // 2. 만료 검증 (삭제 전)
    refreshService.deleteRefresh(token);             //    만료된 토큰도 DB 정리
    return "Refresh Token expired";
}

refreshService.deleteRefresh(token);                 // 3. 정상일 때만 Rotation 삭제

4. CustomOAuth2UserService — {noop}oauth2user → 랜덤 비밀번호

문제: 모든 OAuth2 사용자가 동일한 비밀번호 oauth2user를 가져, 일반 로그인 폼으로 접근 가능.

변경:

// Before
user.setPassword("{noop}oauth2user");

// After
@Autowired
private PasswordEncoder passwordEncoder;
// ...
user.setPassword(passwordEncoder.encode(UUID.randomUUID().toString()));

5. Oauth2LoginController — @RequestMapping@GetMapping

변경: @RequestMapping("/kakao")@GetMapping("/kakao")


설명(MD) 변경

1. 섹션 1: 개념 정리 문구 수정

2. 섹션 2-1: state 파라미터 설명 보강

3. 섹션 2-3: redirect-uri 요청 주체 구분

4. 섹션 2-4: 카카오 access token vs JWT access token 구분

5. 섹션 3: token_type claim 설명 + Refresh Token Rotation 이유

6. 섹션 4: 로그인 검증 흐름 추가

7. 섹션 5: RefreshController 코드 순서 변경 반영

8. 섹션 10: 참고사항 보강

9. 코드 블록 업데이트


추가 적용 (최종 검증 시 나만의참고.md 규칙 반영)

코드 변경

1. JwtAccessTokenCheckAndSaveUserInfoFilter — NPE 방지

// Before
tokenType.equals("refresh")   // tokenType이 null이면 NPE

// After
"refresh".equals(tokenType)   // null-safe

2. RefreshController / LogoutController — HTTP 메소드 명시

// Before
@RequestMapping("/reissue")   // 모든 HTTP 메소드 허용
@RequestMapping("/logout")

// After
@PostMapping("/reissue")      // POST만 허용
@PostMapping("/logout")

3. MainController — @RestController + @GetMapping + JSON 응답

// Before: @Controller + @ResponseBody + @RequestMapping + HTML 문자열 (비밀번호 포함!)
sb.append("password : "+ customUserAccount.getPassword());

// After: @RestController + @GetMapping + ResponseEntity<Map> (비밀번호 제거)
@RestController
@RequestMapping("/api")
public class MainController {
    @GetMapping("/my/info")
    public ResponseEntity<?> myInfo(@AuthenticationPrincipal CustomUserAccount customUserAccount) {
        Map<String, Object> info = Map.of(
            "username", customUserAccount.getUsername(),
            "email", ..., "nickname", ..., "roles", ...
        );
        return ResponseEntity.ok(info);
    }
}

4. JoinController — @RestController

// Before: @Controller + @ResponseBody
// After: @RestController (불필요한 @GetMapping import도 제거)

5. OAuthProvider — {noop}oauth2user → PasswordEncoder + UUID

// Before
user.setPassword("{noop}oauth2user");
public abstract UserEntity toUserEntity(Map<String, Object> attributes);

// After
user.setPassword(passwordEncoder.encode(UUID.randomUUID().toString()));
public abstract UserEntity toUserEntity(Map<String, Object> attributes, PasswordEncoder passwordEncoder);

MD 변경