implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
spring:
security:
oauth2:
client:
registration:
kakao:
clientId: 2d3b2262f9b1320d66b91a31d866c35b
scope:
client-name: Kakao
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
client-authentication-method: POST
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
spring:
security:
oauth2:
client:
registration:
kakao:
clientId: # REST API 키
scope: # Kakao 로 부터 받
client-name: Kakao
authorization-grant-type: authorization_code
redirect-uri: # redirect UrL - 요청완료후 응답이 리다이렉트 되는 url
client-authentication-method: # HTTP method
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
application yml 에 등록했던 url 을 입력
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
kakao:
clientId: 2d3b2262f9b1320d66b91a31d866c35b
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin( // Security 자체 로그인
formLogin -> formLogin
.loginPage("/member/login")
)
.oauth2Login( // 소셜 로그인
oauth2Login -> oauth2Login
.loginPage("/member/login")
)
.logout(
logout -> logout
.logoutUrl("/member/logout")
);
return http.build();
}
<a href="/oauth2/authorization/kakao">
카카오로 로그인하기
</a>
// 일반회원인지, 카카오로 가입한 회원인지, 구글로 가입한 회원인지
private String providerTypeCode;
private
주의!!//-- login 처리 로직 --//
// 내부 처리함수, 일반회원가입, 소셜로그인을 통한 회원가입(최초 로그인 시 한번만 발생)에서 이 함수를 사용함
private RsData<Member> join(String providerTypeCode, String username, String password) {
// name 중복 체크 //
if (findByUsername(username).isPresent())
return RsData.of("F-1", "해당 아이디(%s)는 이미 사용중입니다.".formatted(username));
// 소셜 로그인을 통한 회원가입에서는 비번이 없다. //
if (StringUtils.hasText(password))
password = passwordEncoder.encode(password);
// 수동으로 새로운 member 객체 생성 //
Member member = Member
.builder()
.providerTypeCode(providerTypeCode)
.username(username)
.password(password)
.build();
// member 객체 db 에 저장 //
memberRepository.save(member);
return RsData.of("S-1", "회원가입이 완료되었습니다.", member);
}
providerTypeCode
값을 매개변수로 받는다.//-- security join --//
@Transactional
public RsData<Member> join(String username, String password) {
// "S-1.1" 해당 회원이 일반회원가입으로 인해 생성되었다는걸 나타내기 위해서
return join("S-1.1", username, password);
}
//-- social join --//
// 소셜 로그인(카카오, 구글, 네이버) 로그인이 될 때 마다 실행되는 함수
@Transactional
public RsData<Member> whenSocialLogin(String providerTypeCode, String username) {
// username 예시 : KAKAO__1312319038130912, NAVER__1230812300
Optional<Member> opMember = findByUsername(username);
// 아이디가 있는경우
if (opMember.isPresent())
return RsData.of("S-2", "로그인 되었습니다.", opMember.get());
// 소셜 로그인를 통한 가입시 비번은 없다.
return join(providerTypeCode, username, ""); // 최초 로그인 시 딱 한번 실행
}
DefaultOAuth2UserService
를 상속받았다.package com.ll.gramgram.base.security;
import com.ll.gramgram.boundedContext.member.entity.Member;
import com.ll.gramgram.boundedContext.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Map;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
// 소셜 로그인을 할 Member 의 Service 계층 객체를 DI 해야 한다.
private final MemberService memberService;
// 소셜 로그인을 처리하기 위해 필요한 라이브러리
// 자세한 역할은 모르겠다.
class CustomOAuth2User extends User implements OAuth2User {
public CustomOAuth2User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getName() {
return getUsername();
}
}
CustomOAuth2UserService
의 method 로 생성해주면 된다.@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
// id
String oauthId = oAuth2User.getName();
// providerTypeCode
String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
// name
String username = providerTypeCode + "__%s".formatted(oauthId);
// service 로직을 통한 객체 생성
Member member = memberService.whenSocialLogin(providerTypeCode, username).getData();
return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
}
⚠️ 추후에 카카오에 정식적으로 승인받기 위해선 공식 버튼 ui 를 사용해야 한다.
🔗 카카오 디자인 리소스