회원가입 시 → UserCreateForm
입력한 내용 바탕으로 user
생성되고
로그인 시 → 사진처럼 accessToken
, refreshToken
생성됨
비밀번호도 encoding 되어서 출력되고
Refresh랑 logout 하면 토큰 삭제되는것도 DB에서 확인함
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 출력
//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객체 생성
1. 토큰 검사가 필요없는 경로 설정
회원가입, 로그인, 로그아웃 -> 해당 경로들은 필터링 건너뜀
2. 토큰 받아옴
-> getAccessToken 메서드 사용해서 헤더에 담겨오는토큰 중에 Bearer 로 시작하는 토큰 가져옴
-> 존재하는 토큰이면, validateToken으로 유효성 검사
-> 유효하면 getAuthentication 사용해서 인증정보 추출