Contents
OAuthOAuth

구조 만들기


server:
servlet:
encoding:
charset: utf-8
force: true
port: 8080
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test;MODE=MySQL
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create # none, update
show-sql: true
properties:
hibernate:
format_sql: true
엔티티

package shop.mtcoding.loginapp.user;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Getter
@Table(name = "user_tb")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique = true, nullable = false)
private String username;
private String password;
private String email;
private String provider; // facebook, kakao, apple, naver (OAuth 제공자)
@Builder
public User(Integer id, String username, String password, String email, String provider) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.provider = provider;
}
}

package shop.mtcoding.loginapp.shop;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Getter
@Table(name = "Shop_tb")
@Entity
public class Shop {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique = true, nullable = false)
private String name; //상품명
private String price; //상품 가격
private String qty; //재고
@Builder
public Shop(Integer id, String name, String price, String qty) {
this.id = id;
this.name = name;
this.price = price;
this.qty = qty;
}
}


Repository

package shop.mtcoding.loginapp.user;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(@Param("username")String username);
}
service
package shop.mtcoding.loginapp.user;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public void 회원가입(String username, String password, String email){
User user = User.builder()
.username(username)
.password(password)
.email(email)
.build();
userRepository.save(user);
}
public User 로그인(String username, String password){
User user = userRepository.findByUsername(username);
if(user == null){
throw new RuntimeException("아이디가 없습니다.");
}else{
if (user.getPassword().equals(password)){
return user;
}else{
throw new RuntimeException("비밀번호가 틀렸습니다.");
}
}
}
}
UserController
package shop.mtcoding.loginapp.user;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
private final HttpSession session; //Ioc 등록 돼있음
@GetMapping("/join-form")
public String joinForm() {
return "join-form";
}
@GetMapping("/login-form")
public String loginForm() {
return "login-form";
}
@PostMapping("/join")
public String join(String username, String password, String email) {
userService.회원가입(username, password, email);
return "redirect:/login-form";
}
@PostMapping("/login")
public String login(String username, String password) {
User sessionUser = userService.로그인(username, password);
session.setAttribute("sessionUser", sessionUser);
return "redirect:/shop";
}
}
ShopService
package shop.mtcoding.loginapp.shop;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor
@Service
public class ShopService {
private final ShopRepository shopRepository;
public List<Shop> 상품목록() {
List<Shop> shopList = shopRepository.findAll();
return shopList;
}
}
코드방식
임시코드를 브라우저가 받아서 서버에전달한다.
사용자가 서버에 전달할줄 모르니까 OAuth 제공자가 302 로케이션이랑 같이 제공해주면서 자동으로 리다이렉션 하게끔 해준다.
그 회사의 pk + 프로바이더를 넣는다
OAuth 로그인 만으로 정보가 충족하지 않아 추가정보가 필요한데 OAuth로그인후에 바로 추가적인 정보를 입력하는(ex:주소) ux가 나오면 별로다. 추후에 배송할 때 받을 수 도 있다.


jwt (Access Token, Refresh Token)
결론:jwt를 발급 받으면 Access Token이랑 Refresh Token이 발급되는데 Access Token은 사용자의 정보가 담겨있는 토큰이고
Refresh Token은 Access Token의 유효기간이 만료 됐을 때 재발급 해주는 역할을 하는 토큰 입니다.
서비스 로그인 과정 순서
로그인 요청 → 인증 및 동의 요청 → 인가 코드 발급 → 토큰 발급
앱 설정









그래서 스코프 설정만 추가


인가 코드 받기










rfc 문서 읽기


토큰 받기



사용자 정보 받기




클라이언트가 서버에 직접 던지는 정보를 신뢰 할 수 없다.
검증을 해봐야 한다.
DTO만들기

JsonProperty 라이브러리
@JsonProperty
어노테이션은 Jackson 라이브러리에서 사용되는 어노테이션으로, JSON 데이터와 Java 객체 간의 매핑을 커스터마이징할 때 사용됩니다. 이 어노테이션을 통해 필드나 메서드의 JSON 프로퍼티 이름을 명시적으로 지정할 수 있습니다. 주로 직렬화(Serialization)와 역직렬화(Deserialization) 과정에서 JSON 필드 이름을 Java 객체 필드 이름과 다르게 지정해야 할 때 유용합니다.주요 기능
- JSON 필드 이름 지정: JSON의 필드 이름과 Java 객체의 필드 이름이 다를 때 이를 매핑.
- 직렬화 제어: 특정 필드를 JSON 출력에 포함시키거나 제외.
- 역직렬화 제어: JSON 입력 시 특정 필드를 Java 객체에 매핑하거나 제외.
사용 예시
1. JSON 필드 이름 지정
JSON의 필드 이름이 Java 객체의 필드 이름과 다를 때 사용합니다.
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
@JsonProperty("user_name")
private String userName;
@JsonProperty("user_age")
private int age;
// Getter와 Setter 생략
}
이 경우, JSON 데이터가 다음과 같이 매핑됩니다:
{
"user_name": "John",
"user_age": 30
}
2. 직렬화 및 역직렬화 제어
필드를 직렬화하거나 역직렬화할 때 제어할 수 있습니다.
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
private String userName;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
// Getter와 Setter 생략
}
위의 예제에서
password
필드는 JSON 역직렬화 시에만 사용되고 직렬화 시에는 제외됩니다.
카카오로그인 Service
public User 카카오로그인(String code) {
// 1. code로 카카오에서 토큰 받기 (위임완료) - oauth2.0
// 1.1 RestTemplate 설정
RestTemplate rt = new RestTemplate();
// 1.2 http header 설정
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// 1.3 http body 설정
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", "3d03bec0f109fbfea32461ee7fd4f9a0");
body.add("redirect_uri", "http://localhost:8080/oauth/callback");
body.add("code", code);
// 1.4 body+header 객체 만들기
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<>(body, headers);
// 1.5 api 요청하기 (토큰 받기)
ResponseEntity<KakaoResponse.TokenDTO> response = rt.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
request,
KakaoResponse.TokenDTO.class);
// 1.6 값 확인
System.out.println(response.getBody().toString());
// 2. 토큰으로 사용자 정보 받기 (PK, Email)
HttpHeaders headers2 = new HttpHeaders();
headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
headers2.add("Authorization", "Bearer "+response.getBody().getAccessToken());
HttpEntity<MultiValueMap<String, String>> request2 =
new HttpEntity<>(headers2);
ResponseEntity<KakaoResponse.KakaoUserDTO> response2 = rt.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.GET,
request2,
KakaoResponse.KakaoUserDTO.class);
System.out.println("response2 : "+response2.getBody().toString());
// 3. 해당정보로 DB조회 (있을수, 없을수)
String username = "kakao_"+response2.getBody().getId();
User userPS = userRepository.findByUsername(username);
// 4. 있으면? - 조회된 유저정보 리턴
if(userPS != null){
System.out.println("어? 유저가 있네? 강제로그인 진행");
return userPS;
}else{
System.out.println("어? 유저가 없네? 강제회원가입 and 강제로그인 진행");
// 5. 없으면? - 강제 회원가입
// 유저네임 : (provider_pk)
// 비밀번호 : UUID
// 이메일 : email 받은 값
// 프로바이더 : kakao
User user = User.builder()
.username(username)
.password(UUID.randomUUID().toString())
.email(response2.getBody().getProperties().getNickname()+"@nate.com")
.provider("kakao")
.build();
User returnUser = userRepository.save(user);
return returnUser;
}
}


Share article