@Getter
@AllArgsConstructor
@JsonPropertyOrder({"code", "result", "message", "data"})
public class ApiResponse<T> {
private final String code;
private final String result;
private final String message;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T data;
// 성공한 경우 응답 생성
public static <T> ApiResponse<T> onSuccess(Status status, T data){
return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), data);
}
// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(Status status){
return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), null);
}
// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(Status status, T data){
return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), data);
}
}
Status.java
@Getter
@RequiredArgsConstructor(access = PRIVATE)
public enum Status {
//예시
TEMP_SUCCESS("200", "SUCCESS", "임시 API 접근에 성공했습니다."),
KAKAO_CODE_SUCCESS("200", "SUCCESS", "카카오 인가코드를 발급받았습니다."),
KAKAO_LOGIN_SUCCESS("200", "SUCCESS", "카카오 로그인을 성공했습니다.");
private final String code;
private final String result;
private final String message;
}
🏁 인가코드 발급 확인하기
아래의 주소로 접속하여 확인합니다. 중괄호{ } 안에 해당하는 값을 넣어주셔야 합니다.
https://kauth.kakao.com/oauth/authorize?client_id={이곳에 REST API 키를 넣어주세요}&redirect_uri={이곳에 redirect_uri를 넣어주세요}&response_type=code
해당 화면이 보이면 성공입니다. 동의 버튼을 누르면 인가코드를 확인할 수 있습니다.
프론트와 백이 연동했다고 가정했을 때, 프론트가 백에게 이 인가코드를 넘겨준다고 생각하시면 됩니다.
지금 저희는 직접 인가코드 발급받는 것을 구현한 것입니다.
2. 토큰 발급받기
토큰을 발급받기 위해서 순서대로 코드를 작성해 주세요.
KakaoLoginDto.java
public class KakaoLoginDto {
@Getter
@AllArgsConstructor
@NoArgsConstructor
public static class KakaoTokenResponseDto{
private String access_token;
private String token_type;
private String refresh_token;
private int expires_in;
private String scope;
private int refresh_token_expires_in;
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
public static class KakaoUserInfoResponseDto{
private Long id;
private String connected_at;
private KakaoAccount kakao_account;
private Properties properties;
@Getter
@AllArgsConstructor
@NoArgsConstructor
public static class KakaoAccount{
private Boolean has_email;
private Boolean email_needs_agreement;
private Boolean is_email_valid;
private Boolean is_email_verified;
private String email;
}
}
}
KakaoTokenResposneDto는 토큰을 발급받을 때 사용하고, KakaoUserInfoResponseDto는 사용자 정보를 읽을 때 사용합니다.
미리 작성해 두겠습니다.
❗️ 주의 ❗️
저는 로그인 시 이메일 값만 필요해서 이메일 값만 추가했습니다. 이메일 값 외에 다른 값을 로그인 동의항목에 추가했다면, KakaoUserInfoResponseDto에도 추가해주셔야 합니다. 속성 이름은 공식문서를 참고하세요.
https://kauth.kakao.com/oauth/authorize?client_id={이곳에 REST API 키를 넣어주세요}&redirect_uri={이곳에 redirect_uri를 넣어주세요}&response_type=code
그리고 Swagger에 접속합니다.
http://localhost:8080/swagger-ui/index.html#/
data에 사용자 정보가 담긴 것을 확인할 수 있습니다.
저는 이메일만 로그인 동의항목에 추가했기 때문에 이메일 값 하나만 보이는 것입니다.
3. 사용자 정보 저장 & JWT 토큰 발급
지금부터는 여러분의 서비스에 맞춰서 구현이 필요합니다.
사용자 정보를 저장하는 방법은 일반 회원가입 구현할 때와 동일합니다.
JWT 토큰 발급에 대한 자세한 설명은 이 글에서 하지 않겠습니다. 글이 너무 길어질 거 같아요 😭
대신 코드는 모두 작성해 두었습니다.
📌 구현 순서
1. 사용자 정보(이메일)를 이용하여 이미 회원가입이 된 사용자인지 확인한다.
1-1. 회원가입이 되어 있지 않다면 데이터베이스에 저장한 뒤, 로그인하고 jwt 토큰을 발급한다.
1-2. 회원가입이 이미 되어 있다면, 로그인하고 jwt 토큰을 발급한다.
📌 구현에 필요한 클래스
User.java ➡️ user 테이블을 구현한 entity입니다. UserCustomDto.java ➡️ 사용자 인증에 필요한 정보를 담습니다. KakaoLoginDto.java➡️(위에서 사용함) 사용자 정보를 읽을 때 필요합니다. JWTUtil.java ➡️ JWT 토큰 생성, 파싱, 검증 기능을 수행합니다. AppLoginFilter.java ➡️ 로그인 요청 시 인증을 처리하는 필터입니다. JWTFilter.java ➡️ 사용자 요청이 들어올 때 JWT 토큰을 검증하는 필터입니다. SecurityConfig.java ➡️ Spring Security의 보안 설정을 담당합니다.
Build.gradle
dependencies {
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱을 위해 Jackson 사용
implementation 'org.springframework.boot:spring-boot-starter-security'
}
JWT를 사용하기 위해서 Build.gradle에 코드를 추가해 주세요.
UserCustomDto.java
public class UserCustomDto implements UserDetails {
private final User user;
public UserCustomDto(User user) {this.user = user;}
@Override
public Collection<? extends GrantedAuthority> getAuthorities(){
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return null;
}
});
return collection;
}
@Override
public String getPassword() { return user.getEmail(); }
@Override
public String getUsername() { return user.getEmail(); }
public Long getUserId(){
return user.getId();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
KakaoLoginDto.java
public class KakaoLoginDto {
// (생략) 위에서 구현함
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class KakaoUserInfoDto{ // 카카오 토큰을 이용해서 읽어온 유저 정보
private String email;
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class LoginResponseDto{
private Long userId;
private String token;
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
public static class LoginRequestDto{
public String email;
}
}
KakaoLoginDto는 이미 생성한 클래스인데요, 3개의 static class를 추가해 줍니다.
JWTUtil.java
@Component
public class JWTUtil {
private SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public Long getUserId(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return claims.get("userId", Long.class);
}
public Boolean isExpired(String token) {
Date expiration = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody()
.getExpiration();
return expiration.before(new Date());
}
public String createJwt(Long userId, String nickname, Long expiredMs) {
return Jwts.builder()
.claim("userId", userId)
.claim("nickname", nickname)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
}