카카오 로그인을 하면 블로그에서 자동 회원가입이 되도록 한다.
= 카카오 로그인 시 만들어지는 블로그와 유저정보 통합을 시킨다
▼ 카카오 로그인 시 회원정보 들어가면 자동 회원가입되어 나오는 정보
[User]
public class User {
@Id //Primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // 프로젝트에서 연결된 DB의 넘버링 전략을 따라간다.
private int id; // 시퀀스, auto_increment
@Column(nullable = false, length = 100, unique = true)
private String username; // 아이디
@Column(nullable = false, length = 100) // 123456 => 해쉬 (비밀번호 암호화)
private String password;
@Column(nullable = false, length = 50)
private String email; // myEmail, my_email
// @ColumnDefault("user")
// DB는 RoleType이라는 게 없다.
@Enumerated(EnumType.STRING)
private RoleType role; // Enum을 쓰는게 좋다. // ADMIN, USER
private String oauth; // kakao, google
// 내가 직접 시간을 넣으려면 Timestamp.valueOf(LocalDateTime.now())
@CreationTimestamp
private Timestamp createDate;
}
카카오 로그인했을때 User 객체의 username, password, email 이 세가지만 구성하면 된다.
(id 는 시퀀스로 자동적용, role 은 최초가입자는 USER , createDate 는 현재시간)
카카오 로그인 시 만들어지는 유저 정보들(자동 회원가입)
- username은 중복이 안되도록 '이메일 + 카카오 아이디'
(길이가 길어지니까 length = 100 으로 늘려준다. )
- email 은 그대로 쓰기
- 카카오 로그인을 하는 사람들이 회원가입될 때 만들어지는 password는 cos1234 로 통일 시켜주기
비밀번호 cos1234 통일하는 법
[application.yml]
가장 끝에 추가
cos:
key: cos1234
** yml 만들때의 규칙
(스페이스 두번)key:(스페이스 한번)cos1234
그리고 cos1234 값은 실제로 서비스를 한다면 절대로! 노출되면 안된다.
위의 유저 정보들 콘솔창에 테스트
[UserController]
패스워드 값을 주입시키기
@Value("${cos.key}")
private String cosKey;
[UserController]
System.out.println("블로그 서버 유저네임 : "+kakaoProfile.getKakao_account().getEmail()+"_"+kakaoProfile.getId());
System.out.println("블로그 서버 이메일 : "+kakaoProfile.getKakao_account().getEmail());
System.out.println("블로그 서버 패스워드 : "+cosKey);
위 정보들로 강제로 회원가입 시키자
[UserController]
@Autowired
private AuthenticationManager authenticationManager; // 로그인 처리
@Autowired
UserService userService;
@GetMapping("/auth/kakao/callback")
public String kakaoCallback(String code) {
//생략
User kakaoUser = User.builder()
.username(kakaoProfile.getKakao_account().getEmail()+"_"+kakaoProfile.getId())
.password(cosKey)
.email(kakaoProfile.getKakao_account().getEmail())
.build();
// 가입자 혹은 비가입자 체크 해서 처리
User originUser = userService.회원찾기(kakaoUser.getUsername());
if(originUser.getUsername() == null) {
System.out.println("기존 회원이 아니기에 자동 회원가입을 진행합니다");
userService.회원가입(kakaoUser);
}
System.out.println("자동 로그인을 진행합니다.");
// 로그인 처리
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(kakaoUser.getUsername(), cosKey));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/";
}
바로 회원가입 하지 않고 비회원인지 회원인지 체크하기
- userService.회원가입() X
- userService.회원찾기(kakaoUser.getUsername()); O
[UserService]
회원찾기는 username으로 찾는다.
@Transactional(readOnly = true)
public User 회원찾기(String username) {
User user = userRepository.findByUsername(username).orElseGet(()->{
return new User();
});
return user;
}
.orElseGet → username이 없으면 빈 객체 new User() 를 리턴한다.
빈객체를 리턴하니 못찾음
username으로 회원을 찾고 난 뒤 if 문
// 회원가입자 비가입자 구분해서 처리
User originUser = userService.회원찾기(kakaoUser.getUsername());
// 가입 돼 있는 username 없으면 회원가입 진행
if(originUser == null) {
System.out.println("기존 회원이 아닙니다!");
userService.회원가입(kakaoUser);
}
System.out.println("자동 로그인을 진행합니다.");
//로그인 처리
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(kakaoUser.getUsername(),cosKey));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/";
- if(originUser == null)
username이 없어서 빈 객체를 리턴하면 기존회원이 아닌걸로 회원가입() 진행 / username 있으면 로그인 처리
- 로그인 처리는 UserApiController 에서 쓴 그대로
Authentication authentication =authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
kakaoUser.getUsername(),cosKey));
SecurityContextHolder.getContext().setAuthentication(authentication);
- return "redirect:/";
슬러시 / 를 붙이면 @ResponseBody 를 지워야 viewResolver를 호출해서 파일을 찾아간다.
* @ResponseBody 는 데이터를 리턴해주는 컨트롤러 함수
@GetMapping("/auth/kakao/callback")
public@ResponseBodyString kakaoCallback(String code) {
...
}
[UserController]
@GetMapping("/auth/kakao/callback")
public String kakaoCallback(String code) { // Data를 리턴해주는 컨트롤러 함수
RestTemplate rt = new RestTemplate();
// HttpHeader 오브젝트 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HttpBody 오브젝트 생성
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", "b344701c3ff69917f13cd47bb45df871");
params.add("redirect_uri", "http://localhost:8000/auth/kakao/callback");
params.add("code", code);
// HttpHeader와 HttpBody를 하나의 오브젝트에 담기
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
new HttpEntity<>(params, headers);
// Http 요청하기 - Post방식으로 - 그리고 response 변수의 응답 받음.
ResponseEntity<String> response = rt.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
kakaoTokenRequest,
String.class
);
// Gson, Json Simple, ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
OAuthToken oauthToken = null;
try {
oauthToken = objectMapper.readValue(response.getBody(), OAuthToken.class);
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println("카카오 엑세스 토큰 : "+oauthToken.getAccess_token());
RestTemplate rt2 = new RestTemplate();
// HttpHeader 오브젝트 생성
HttpHeaders headers2 = new HttpHeaders();
headers2.add("Authorization", "Bearer "+oauthToken.getAccess_token());
headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HttpHeader와 HttpBody를 하나의 오브젝트에 담기
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest2 =
new HttpEntity<>(headers2);
// Http 요청하기 - Post방식으로 - 그리고 response 변수의 응답 받음.
ResponseEntity<String> response2 = rt2.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoProfileRequest2,
String.class
);
System.out.println(response2.getBody());
ObjectMapper objectMapper2 = new ObjectMapper();
KakaoProfile kakaoProfile = null;
try {
kakaoProfile = objectMapper2.readValue(response2.getBody(), KakaoProfile.class);
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// User 오브젝트 : username, password, email
System.out.println("카카오 아이디(번호) : "+kakaoProfile.getId());
System.out.println("카카오 이메일 : "+kakaoProfile.getKakao_account().getEmail());
System.out.println("블로그서버 유저네임 : "+kakaoProfile.getKakao_account().getEmail()+"_"+kakaoProfile.getId());
System.out.println("블로그서버 이메일 : "+kakaoProfile.getKakao_account().getEmail());
// UUID란 -> 중복되지 않는 어떤 특정 값을 만들어내는 알고리즘
System.out.println("블로그서버 패스워드 : "+cosKey);
User kakaoUser = User.builder()
.username(kakaoProfile.getKakao_account().getEmail()+"_"+kakaoProfile.getId())
.password(cosKey)
.email(kakaoProfile.getKakao_account().getEmail())
.oauth("kakao")
.build();
// 가입자 혹은 비가입자 체크 해서 처리
User originUser = userService.회원찾기(kakaoUser.getUsername());
if(originUser.getUsername() == null) {
System.out.println("기존 회원이 아니기에 자동 회원가입을 진행합니다");
userService.회원가입(kakaoUser);
}
System.out.println("자동 로그인을 진행합니다.");
// 로그인 처리
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(kakaoUser.getUsername(), cosKey));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/";
}
- 카카오 로그인 시 결과
회원정보에 들어갔을때 자동 회원가입 돼 있음
DB에도 회원가입 정보 들어감 (cos1234는 암호화해서 들어가있다)
다른문제
그러나 회원정보에서 password 수정이 가능하면 다시 카카오 로그인이 안된다.
회원정보 수정을 할 수 없도록 만들어야한다.
그러기 위해선 카카오 사용자인지 일반사용자인지 구분을 해야한다.
카카오 유저 vs 일반 유저 구분하기
[User]
카카오, 구글 등 사용자 구분을 할 수 있는 변수를 추가한다. (Null 허용)
private String oauth;
[UserController]
User kakaoUser = User.builder()
.username(kakaoProfile.getKakao_account().getEmail()+"_"+kakaoProfile.getId())
.password(cosKey)
.email(kakaoProfile.getKakao_account().getEmail())
.oauth("kakao")
.build();
// 회원가입자 비가입자 구분해서 처리
User originUser = userService.회원찾기(kakaoUser.getUsername());
if(originUser == null) {
System.out.println("기존 회원이 아닙니다!");
userService.회원가입(kakaoUser);
}
DB로 oauth 필드에 kakao가 있으면 카카오회원 vs 없으면 일반유저 회원으로 구분된다.
그래서 oauth 값이 있으면 패스워드 수정을 막게한다.
[user.updateForm.jsp]
// 일반유저라면
<c:if test="${empty principal.user.oauth}">
<div class="form-group">
<label for="pwd">Password : </label>
<input type="password" class="form-control" placeholder="Enter password" id="password">
</div>
<div class="form-group">
<label for="email">Email : </label>
<input type="email" value="${principal.user.email }" class="form-control" placeholder="Enter email" id="email" >
</div>
</c:if>
// 카카오 유저라면
<c:if test="${not empty principal.user.oauth}">
<div class="form-group">
<label for="email">Email : </label>
<input type="email" value="${principal.user.email }" class="form- control" placeholder="Enter email" id="email" readonly>
</div>
</c:if>
<c:if test="${empty principal.user.oauth} }">
oauth값이 없으면(=일반 유저라면) 패스워드와 필드가 나타나고 이메일 수정가능
<c:if test="${not empty principal.user.oauth}">
oauth값이 있으면(=카카오 유저라면) 패스워드 필드 없애고 이메일 필드도 수정못하게 readonly
- 카카오 로그인 시 결과
- 일반 유저 로그인 시 결과
postman으로 공격을 할 수 있어서 서버쪽으로도 막아줘야한다.
[UserSerivce]
@Transactional
public void 회원수정(User user) {
User persistance = userRepository.findById(user.getId()).orElseThrow(()->{
return new IllegalArgumentException("회원 찾기 실패");
});
// Validate 체크 => oauth 필드에 값이 없으면 수정 가능
if(persistance.getOauth() == null || persistance.getOauth().equals("")) {
String rawPassword = user.getPassword();
String encPassword = encoder.encode(rawPassword);
persistance.setPassword(encPassword);
persistance.setEmail(user.getEmail());
}
// 회원수정 함수 종료시 = 서비스 종료 = 트랜잭션 종료 = commit 이 자동으로 됩니다.
// 영속화된 persistance 객체의 변화가 감지되면 더티체킹이 되어 update문을 날려줌.
}
oauth값이 null이거나 "" 빈값일때는 일반 유저니까 패스워드,이메일 수정이 가능하다는 뜻
카카오 유저는 수정 못한다.
내가 카카오로 로그인 했던 사이트들 확인하는 방법
https://developers.kakao.com/console/app
카카오계정 로그인
여기를 눌러 링크를 확인하세요.
accounts.kakao.com
카카오 계정 설정 - 디벨로퍼스 프로필 정보 - 카카오 계정정보 확인하기 클릭 - 계정 이용 - 외부서비스 전체보기 클릭
전체 목록이 나오는데 각각 들어가서 연결끊기 하면 된다.
'Spring boot | 블로그 만들기' 카테고리의 다른 글
블로그 만들기 | 무한참조 해결법 2가지 @JsonIgnoreProperties , 객체 다이렉트 호출 (0) | 2022.11.24 |
---|---|
블로그 만들기 | 게시글 댓글 기능과 무한 참조 (0) | 2022.11.24 |
블로그 만들기 | [카카오API] 사용자 정보 요청하기(4) (0) | 2022.11.18 |
블로그 만들기 | [카카오API] .getBody() , ObjectMapper 라이브러리(3) (1) | 2022.11.18 |
블로그 만들기 | [카카오 API] 엑세스 토큰 받기(2) (0) | 2022.11.17 |