[build.gradle]
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
[SecurityConfig.java]
package com.ll.gramgram.base.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin(
formLogin -> formLogin
.loginPage("/member/login")
)
//이 부분을 추가
.oauth2Login(
oauth2Login -> oauth2Login
.loginPage("/member/login")
)
//끝
.logout(
logout -> logout
.logoutUrl("/member/logout")
);
return http.build();
}
}
[CustomOAuth2UserService.java]
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 {
private final MemberService memberService;
// 로그인이 성공할 때 마다 이 함수가 실행된다.
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String oauthId = oAuth2User.getName();
String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
String username = providerTypeCode + "__%s".formatted(oauthId);
Member member = memberService.whenSocialLogin(providerTypeCode, username).getData();
return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
}
}
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();
}
}
[login.html]
<div>
<a class="btn btn-link" href="/oauth2/authorization/kakao">
카카오로 로그인하기
</a>
<a class="btn btn-link" href="/oauth2/authorization/google">
구글로 로그인하기
</a>
</div>
[MemberService.java]
// 내부 처리함수, 일반회원가입, 소셜로그인을 통한 회원가입(최초 로그인 시 한번만 발생)에서 이 함수를 사용함
private RsData<Member> join(String providerTypeCode, String username, String password) {
if (findByUsername(username).isPresent()) {
return RsData.of("F-1", "해당 아이디(%s)는 이미 사용중입니다.".formatted(username));
}
// 소셜 로그인을 통한 회원가입에서는 비번이 없다.
if (StringUtils.hasText(password)) password = passwordEncoder.encode(password);
Member member = Member
.builder()
.providerTypeCode(providerTypeCode)
.username(username)
.password(password)
.build();
memberRepository.save(member);
return RsData.of("S-1", "회원가입이 완료되었습니다.", member);
}
[MemberService.java]
// 소셜 로그인(카카오, 구글, 네이버) 로그인이 될 때 마다 실행되는 함수
@Transactional
public RsData<Member> whenSocialLogin(String providerTypeCode, String username) {
Optional<Member> opMember = findByUsername(username); // username 예시 : KAKAO__1312319038130912, NAVER__1230812300
if (opMember.isPresent()) return RsData.of("S-2", "로그인 되었습니다.", opMember.get());
// 소셜 로그인를 통한 가입시 비번은 없다.
return join(providerTypeCode, username, ""); // 최초 로그인 시 딱 한번 실행
}
[Member.java]
private String providerTypeCode;
추가@Builder // Member.builder().providerTypeCode(providerTypeCode) .. 이런식으로 쓸 수 있게 해주는
@NoArgsConstructor // @Builder 붙이면 이거 필수
@AllArgsConstructor // @Builder 붙이면 이거 필수
@EntityListeners(AuditingEntityListener.class) // @CreatedDate, @LastModifiedDate 작동하게 허용
@ToString // 디버그를 위한
@Entity // 아래 클래스는 member 테이블과 대응되고, 아래 클래스의 객체는 테이블의 row와 대응된다.
@Getter // 아래 필드에 대해서 전부다 게터를 만든다. private Long id; => public Long getId() { ... }
public class Member {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
@CreatedDate // 아래 칼럼에는 값이 자동으로 들어간다.(INSERT 할 때)
private LocalDateTime createDate;
@LastModifiedDate // 아래 칼럼에는 값이 자동으로 들어간다.(UPDATE 할 때 마다)
private LocalDateTime modifyDate;
private String providerTypeCode; // 일반회원인지, 카카오로 가입한 회원인지, 구글로 가입한 회원인지
@Column(unique = true)
private String username;
private String password;
@OneToOne // 1:1
@Setter // memberService::updateInstaMember 함수 때문에
private InstaMember instaMember;
}
카카오 OAuth API 환경 만들기
내 애플리케이션
- 애플리케이션 추가하기
카카오 로그인
- 로그인 활성화
카카오 로그인
- Redirect URI
요약 정보
- 앱 키
- REST API 키
복사 -> application.yml
- cliendId
에 넣음구글 OAuth API 환경 만들기
프로젝트 선택
-새 프로젝트
만들기
클릭OAuth 동의 화면
에서 1, 2, 3 순서대로 작성사용자 인증 정보
리디렉션 URI
-> rediret-uri
, 클라이언드 ID
-> clientId
, 클라이언트 보안 비밀번호
-> client-secret
[application.yml]
spring:
security:
oauth2:
client:
registration:
kakao:
clientId: [REST API 키]
scope:
client-name: Kakao
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
client-authentication-method: POST
google:
clientId: [클라이언드 ID]
client-secret: [클라이언트 보안 비밀번호]
scope:
- email
- profile
redirect-uri: http://localhost:8080/login/oauth2/code/google
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