image.png

회원가입 시 → UserCreateForm 입력한 내용 바탕으로 user 생성되고

로그인 시 → 사진처럼 accessToken , refreshToken 생성됨

비밀번호도 encoding 되어서 출력되고

Refresh랑 logout 하면 토큰 삭제되는것도 DB에서 확인함

image.png

회원가입 로직

1.회원가입 정보 입력 -> UserCreateForm 에 입력
2.유효성 검증 -> BindingResult를 통해 검증하고, 에러메시지 출력
3.회원가입 처리 -> userService의 createUser 메서드 사용
4.createUser 로직 -> 이메일 중복 여부, 1차 비밀번호와 2차 비밀번호 일치여부 확인
5.USER 객체 생성 -> 일치 여부가 확인되면 비밀번호는 passwordEncoding을 통해,
								 암호화 되어 이메일과 함께 저장됨
6.UserRepository -> User 객체 생성되고 그 안에 이메일과 암호화된 비밀번호 저장됨
7.User 객체 반환 -> 마지막으로 User 객체 반환

로그인 로직

1. Form 데이터 검증 -> LoginForm에 입력한 데이터들이 형식에 맞는지
2. 사용자 검증 및 비밀번호 검증 -> userService의 loginUser 메서드 사용
3. loginUser 로직 -> 기존의 userRepository에서 존재하는 이메일인지, 
										encoding된 비밀번호는 존재하는지 대조해보고
						        return userOptional.get();
										실제 User 객체 반환한다.
4.액세스 토큰, 리프레시 토큰 발급
	->각자 TokenProvider 클래스에 generateAccessToken, generateRefreshToken 메서드가 존재해서,
		해당 메서드를 사용해서 발급 받는다.
		액세스 토큰은 session.setAttribute 로 선언해서 서버가 관리는 세션이 된거고,
		리프레시 토큰은 userService에 있는 updateUserRefreshToken 메서드 사용해서,
		마지막에 userRepository에 save(user) 해서 DB에서 저장
5. 비밀번호 검증 ->passwordEncoder.matches(password, user.getPassword())를 사용하여 사용자가 입력한 비밀번호 (password)와
	 데이터베이스에 저장된 암호화된 비밀번호 (user.getPassword())가 일치하는지 확인
6. 비밀번호가 일치하면 tokenProvider.generateAccessToken(user, Duration.ofMinutes(30))를 호출하여 해당 사용자에 대한 새로운 액세스 토큰을 생성
   후에 loginResponseForm에 담아서 반환 -> 로그인 완료되면 loginResponseForm 출력

TokenProvider

//JWT 토큰의 생성, 검증, 클레임 추출 등을 처리하는 기능 제공
 private Key getSigningKey() {  // JWT 서명에 사용할 키를 생성하는 메서드
        byte[] keyBytes = jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8);  // SecretKey를 바이트 배열로 변환
        return new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName());  // SecretKeySpec으로 키를 생성
    }
    /*
    jwtProperties에 연결되어 있던 secretKey 가져와서 바이트 배열로 변환
    그걸 keyBytes에 저장하고, 실제 암호화에 사용할 대칭키 암호화 방법 HS256.
    SignatureAlgorithm.HS256.getJcaName()는 
    자바 암호화 라이브러리가 HS256 알고리즘을 인식하고 사용할 수 있게 해줌.
    대칭키 -> 암호화랑 복호화랑 동일한 비밀키 사용
    */
 
 public String generateAccessToken(User user, Duration expiry) {  // 액세스 토큰을 생성하는 메서드
        Date now = new Date();  // 현재 시간
        Date expiredAt = new Date(now.getTime() + expiry.toMillis());  // 토큰 만료 시간 계산
        return makeToken(now, expiredAt, user);  // 토큰을 생성하여 반환
    }
    /*
    accessToken
    API 호출할때 인증을 위해 사용됨.
    그래서 유효기간이 짧음.
    사용자 인증정보가 포함되며 ->makeToken 메서드에서 만듬
    보안상 민감한 정보를 담으면 안됨
    */
    
private String makeToken(Date now, Date expiredAt, User user) {  // JWT 토큰을 생성하는 메서드
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)  // JWT 헤더 설정
                .setIssuer(jwtProperties.getIssuer())  // 토큰 발급자 설정
                .setIssuedAt(now)  // 토큰 발급 시간 설정
                .setExpiration(expiredAt)  // 토큰 만료 시간 설정
                .setSubject(user.getEmail())  // 토큰 주체 설정
                .claim("username", user.getEmail())  // 추가 클레임으로 사용자 이름 포함
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)  // 서명 키와 알고리즘을 사용하여 서명
                .compact();  // 최종적으로 JWT를 압축하여 반환
    }
    
    /*
    makeToken에서는 헤더, 발급자 이외에 액세스 토큰이 각 요청 인증할때마다 필요한 정보 포함되어야 해서 
    refreshToken에 비해 많다.
    */
    
public String generateRefreshToken(User user, Duration expiry) {  // 리프레시 토큰을 생성하는 메서드
        Date now = new Date();  // 현재 시간
        Date expiredAt = new Date(now.getTime() + expiry.toMillis());  // 리프레시 토큰 만료 시간 계산
        return Jwts.builder()  // JWT 토큰을 빌더 패턴으로 생성
                .setSubject(user.getEmail())  // 토큰의 주체로 사용자 이름 설정
                .setExpiration(expiredAt)  // 토큰 만료 시간 설정
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)  // 서명 키와 알고리즘을 사용하여 토큰에 서명
                .compact();  // 토큰을 문자열로 압축하여 반환
    }
    
   /*
   refreshToken
   보통 유효기간 길고, 서버에 저장, 서버에서만 관리되어야 해서 더 안전.
   HS512 사용이유는 더 높은 보안 수준때문
   액세스 토큰에 비해 간단한 구성
   주체와 만료시간만 설정.
   */
 public boolean validateToken(String token) {  // JWT 토큰의 유효성을 검사하는 메서드
     try {
         Jwts.parserBuilder()
                 .setSigningKey(getSigningKey())  // 서명 키 설정
                 .build()
                 .parseClaimsJws(token);  // 토큰의 클레임을 파싱하여 유효성 검증
         return true;  // 토큰이 유효하면 true 반환
     }
     /*
     validateToken
     token을 매개변수로 받고, parserBuilder() 함(Jwt의 파서를 빌드하는 첫번째 단계)
     jwt 토큰을 파싱하고 검증하는 역할
     .setSigningKey(getSigningKey()) 은 처음에 사용했던 키(복호화랑 암호화에 동일한 키 사용해야 해서)
     해당 키를 가져와서 파서 빌더에 설정하여, 파서가 jwt 서명을 검증할 수 있게
     build() 파서 빌더에서 설정된 모든 옵션을 적용한 parser 객체 생성 -> jwt 토큰을 파싱하고 검증하는데 사용
     parseClaimsJws(token) -> 입력된 token을 받아서 토큰을 파싱(서버에 입력된 비밀키와 검증)
     오류 없이 검증 완료 되면 jwt 페이로드에 포함된 클레임 반환됨(사용자 정보, 발급자, 만료시간 포함됨)
     */
     
 public Claims getClaims(String token) {  // JWT 토큰에서 클레임을 추출하는 메서드
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())  // 서명 키 설정
                .build()
                .parseClaimsJws(token)  // 토큰의 클레임을 파싱
                .getBody();  // 클레임의 바디 부분 반환
    }
    
   /*
   getClamis
   validateToken과 유사하지만, 마지막에 getBody()
   클레임부분을 추출하기 위함->페이로드에 해당 되며 사용자의 정보나 만료 시간 같은 중요한 데이터 포함
   */ 
   
public UsernamePasswordAuthenticationToken getAuthentication(String token) {  // JWT 토큰에서 인증 정보를 추출하는 메서드
     1.   Claims claims = getClaims(token);  // 토큰에서 클레임 추출
     2.   Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")); // 기본 권한 설정
     3.   return new UsernamePasswordAuthenticationToken(claims.getSubject(), token, authorities);  // 사용자의 인증 정보 담음
  1. -> 토큰에서 클레임 정보 추출.
  2. -> 기본 권한 설정(authorities)
  3. -> UsernamePasswordAuthenticationToken 에 getSubject(사용자 ID), token, authorities 포함) 
  => 토큰에서 사용자 정보를 추출하고, 인증된 사용자를 나타내는 UsernamePasswordAuthenticationToken객체 생성

TokenAuthenticationFilter

1. 토큰 검사가 필요없는 경로 설정
	회원가입, 로그인, 로그아웃 -> 해당 경로들은 필터링 건너뜀
2. 토큰 받아옴
	-> getAccessToken 메서드 사용해서 헤더에 담겨오는토큰 중에 Bearer 로 시작하는 토큰 가져옴
	-> 존재하는 토큰이면, validateToken으로 유효성 검사
	-> 유효하면 getAuthentication 사용해서 인증정보 추출