구글, 네이버 OAuth2 로그인 구현
DTO 구현
CustomOAuth2User
OAuth2Dto
GoogleDto
NaverDto
Repository 추가
MembersRepository
Service 구현
CustomOAuth2UserService
SecurityConfig 수정
프론트 페이지 추가
로그인 페이지
로그인 버튼 가이드
메인 컨트롤러 수정
프로젝트 구조
GitHub 링크
실행화면
dto 패키지를 생성 후 CustomOAuth2User 클래스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.dto
package com.example.oauth2.dto;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.example.oauth2.entity.Members;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class CustomOAuth2User implements OAuth2User {
private final Members members;
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return members.getRole();
}
});
return collection;
}
@Override
public String getName() {
return members.getUserid();
}
}
OAuth2Dto 인터페이스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.dto
package com.example.oauth2.dto;
public interface OAuth2Dto {
String getUserid(); // Spring Security username
String getUsername();
String getEmail();
String getProvider();
String getProviderId();
}
GoogleDto 클래스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.dto
package com.example.oauth2.dto;
import java.util.Map;
public class GoogleDto implements OAuth2Dto {
private final String provider = "google";
private final Map<String, Object> attribute;
public GoogleDto(Map<String, Object> attribute) {
this.attribute = attribute;
}
@Override
public String getUserid() {
return provider + "_" + attribute.get("sub").toString();
}
@Override
public String getUsername() {
return attribute.get("name").toString();
}
@Override
public String getEmail() {
return attribute.get("email").toString();
}
@Override
public String getProvider() {
return provider;
}
@Override
public String getProviderId() {
return attribute.get("sub").toString();
}
}
NaverDto 클래스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.dto
package com.example.oauth2.dto;
import java.util.Map;
public class NaverDto implements OAuth2Dto {
private final String provider = "naver";
private final Map<String, Object> attribute;
public NaverDto(Map<String, Object> attribute) {
this.attribute = (Map<String, Object>) attribute.get("response");
}
@Override
public String getUserid() {
return provider + "_" + attribute.get("id").toString();
}
@Override
public String getUsername() {
return attribute.get("name").toString();
}
@Override
public String getEmail() {
return attribute.get("email").toString();
}
@Override
public String getProvider() {
return provider;
}
@Override
public String getProviderId() {
return attribute.get("id").toString();
}
}
repository 패키지를 생성 후 MembersRepository 인터페이스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.repository
package com.example.oauth2.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.oauth2.entity.Members;
public interface MembersRepository extends JpaRepository<Members, Integer> {
// 회원 정보 조회
Members findByUserid(String userid);
}
service 패키지를 생성 후 CustomOAuth2UserService 클래스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.service
OAuth2User oAuth2User = super.loadUser(userRequest); 공급자의 리턴 값
구글 : Name: [11147028552], Granted Authorities: [[OAUTH2_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]], User Attributes: [{sub=11147028552, name=홍길동, given_name=홍길동, picture=https://lh3.googleusercontent.com/a/ACg8ocK5gzFsvqesu2TuOLIWlBYmlIMNHFPpkhT, email=test@gmail.com, email_verified=true}]
네이버 : Name: [{id=ERShm-qmOBe2Z-vMqvkYU9EXU-DVH email=test@naver.com, name=홍길동}], Granted Authorities: [[OAUTH2_USER]], User Attributes: [{resultcode=00, message=success, response={id=ERShm-qmOBe2Z-vMqvkYU9EXU-DVH, email=test@naver.com, name=홍길동}}]
package com.example.oauth2.service;
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 com.example.oauth2.dto.CustomOAuth2User;
import com.example.oauth2.dto.GoogleDto;
import com.example.oauth2.dto.NaverDto;
import com.example.oauth2.dto.OAuth2Dto;
import com.example.oauth2.entity.Members;
import com.example.oauth2.repository.MembersRepository;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final MembersRepository membersRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
System.out.println("=============================");
System.out.println("oAuth2User : " + oAuth2User);
System.out.println("=============================");
// 공급자
String registrationId = userRequest.getClientRegistration().getRegistrationId();
OAuth2Dto oAuth2Dto = null;
if (registrationId.equals("google")) {
oAuth2Dto = new GoogleDto(oAuth2User.getAttributes());
}
else if (registrationId.equals("naver")) {
oAuth2Dto = new NaverDto(oAuth2User.getAttributes());
}
else {
return null;
}
// 회원 정보 조회
Members memberInfo = membersRepository.findByUserid(oAuth2Dto.getUserid());
if (memberInfo == null) {
memberInfo = Members.builder()
.userid(oAuth2Dto.getUserid())
.email(oAuth2Dto.getEmail())
.username(oAuth2Dto.getUsername())
.role("ROLE_USER")
.provider(oAuth2Dto.getProvider())
.providerId(oAuth2Dto.getProviderId())
.build();
membersRepository.save(memberInfo); // 회원가입
}
return new CustomOAuth2User(memberInfo);
}
}
변경된 내용 : Custom oauth2Login, 로그아웃 추가
package com.example.oauth2.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import com.example.oauth2.service.CustomOAuth2UserService;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// csrf token
http
.csrf((csrf) -> csrf.disable()); // 비활성화
// 인가
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources의 static(정적파일) 모두 허용
.requestMatchers("/", "/login", "/login/oauth2/code/*").permitAll() // 보안 검사 없이 접근을 허용
//.requestMatchers("/member/**").hasAnyRole("ADMIN", "USER") // role - ADMIN, USER만 접근 허용
//.requestMatchers("/admin").hasRole("ADMIN") // role - ADMIN만 접근 허용
.anyRequest().authenticated()
);
// Custom oauth2Login
http
.oauth2Login((oauth2) -> oauth2
.loginPage("/login")
.userInfoEndpoint((userInfoEndpointConfig) ->
userInfoEndpointConfig.userService(customOAuth2UserService))
);
// 로그아웃
http
.logout((logout) -> logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
);
return http.build();
}
}
LoginController 클래스 파일을 생성합니다.
패키지 위치 : com.example.oauth2.controller
package com.example.oauth2.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
/**
* 로그인 페이지
* @return
*/
@GetMapping("/login")
public String login() {
return "login";
}
}
templates에 login.mustache 파일을 생성합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>로그인</title>
<style>
.container {
width: 800px !important ;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 pt-5">
<div class="card mb-4">
<h5 class="card-header text-center">로그인</h5>
<div class="card-body">
<div class="row">
<div class="text-center">
<a href="/oauth2/authorization/google"><img src="/images/button/google_login.png" alt="google"></a>
<a href="/oauth2/authorization/naver"><img src="/images/button/naver_login.png" width="40px" height="40px" alt="naver"></a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 text-center">
<a href="/" class="btn btn-outline-secondary">취소</a>
</div>
</div>
</div>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
로그인 버튼 가이드
구글 버튼 가이드 링크: https://developers.google.com/identity/branding-guidelines?hl=ko
네이버 버튼 가이드 링크: https://developers.naver.com/docs/login/bi/bi.md
이미지는 static에 넣어주시면 됩니다. ex) static/images/button/google_login.png
버튼 이미지 없으신 분은 아래 이미지 활용하세요.
변경된 내용 : 로그인 여부 체크, 로그인 사용자 정보 가져오는 로직 추가
package com.example.oauth2.controller;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.example.oauth2.dto.CustomOAuth2User;
@Controller
public class MainController {
/**
* 메인 페이지
* @param customOAuth2User
* @param model
* @return
*/
@GetMapping("/")
public String index(@AuthenticationPrincipal CustomOAuth2User customOAuth2User, Model model) {
// 로그인 여부 체크
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!(principal instanceof String)) {
// 로그인 아이디
String id = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("========================");
System.out.println("아이디 : " + id);
System.out.println("========================");
// Membars 정보
System.out.println("========================");
System.out.println("아이디 : " + customOAuth2User.getMembers().getUserid());
System.out.println("이메일 : " + customOAuth2User.getMembers().getEmail());
System.out.println("이름 : " + customOAuth2User.getMembers().getUsername());
System.out.println("권한 : " + customOAuth2User.getMembers().getRole());
System.out.println("========================");
model.addAttribute("isLogin", true);
model.addAttribute("userid", customOAuth2User.getMembers().getUserid());
model.addAttribute("username", customOAuth2User.getMembers().getUsername());
}
else {
model.addAttribute("isLogin", false);
}
return "index";
}
}
OAuth2 Login | |
---|---|
2024.07.18 | [Spring Security] OAuth2 구글, 네이버 로그인 구현하기 (4) |
2024.07.17 | [Spring Security] OAuth2 구글, 네이버 로그인 구현하기 (3) |
2024.07.15 | [Spring Security] OAuth2 구글, 네이버 로그인 구현하기 (2) |
2024.07.15 | [Spring Security] OAuth2 구글, 네이버 로그인 구현하기 (1) |