카카오 로그인을 하면 블로그에서 자동 회원가입이 되도록 한다. 

= 카카오 로그인 시 만들어지는 블로그와 유저정보 통합을 시킨다

 

 

▼ 카카오 로그인 시 회원정보 들어가면 자동 회원가입되어 나오는 정보

카카오 유저일때 회원정보

 

 

 

[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 @ResponseBody String 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

카카오 계정 설정 - 디벨로퍼스 프로필 정보 - 카카오 계정정보 확인하기 클릭 - 계정 이용 -  외부서비스 전체보기 클릭

 

전체 목록이 나오는데 각각 들어가서 연결끊기 하면 된다. 

 

 

반응형
LIST

+ Recent posts