
๊ตฌ๊ธ ๋ก๊ทธ์ธ ๋ถํฐ ์งํ ํ
Google Cloud API ์ ์

๐ ์ฝ์ ํด๋ฆญ


๐ ํ๋ก์ ํธ ์ ํ > ์ฐ์ธก ์๋จ ์ ํ๋ก์ ํธ ํด๋ฆญ

๐ ๋ง๋ค๊ธฐ ํด๋ฆญ


๐ OAuth ํด๋ผ์ด์ธํธ ID ๋ง๋ค๊ธฐ


๐ ์ฑ ์ด๋ฆ, ์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ ์์ฑ ํ ์ ์ฅ ํ ๊ณ์ x 3 ๐ ๋์ ๋ณด๋

๐ ์ฌ๊ธฐ๊น์ง ๋์ํ๋ฉด ๋ง๋ ๊ฒ ์ด์ ์ง์ง๋ก ๋ก๊ทธ์ธ API๋ฅผ ์ ์ฉํด๋ณด์



๐ ๋ง๋ค๊ธฐ

๐ ์์ผ๋ก ID ์ PW๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค

๐ ๊น๋จน์ผ๋ฉด ์ด๊ฑฐ ๋๋ฅด๋ฉด ๋ค์ ๋์ด

spring.security.oauth2.client.registration.google.client-id= # ํด๋ผ์ด์ธํธ ID
spring.security.oauth2.client.registration.google.client-secret= # ํด๋ผ์ด์ธํธ PW
spring.security.oauth2.client.registration.google.scope=profile, email
๐ [๋์ด์ฐ๊ธฐ ํ์ง ๋ง ๊ฒ !] ๋ฐฉ๊ธ ๋ฐ์ ID, PW๋ฅผ ๋ฃ์ด์ฃผ๊ณ
๋งจ ์๋์
spring.profiles.include=oauth
์ถ๊ฐ
๐ ํ์ผ ์ด๋ฆ์oauth๊ฐ ๋ค์ด๊ฐ๋ฉด ์์์ ์คํํจ. ์ฐ๋ฆฐapplication-oauth๋ฅผ ๋ง๋ค์์ผ๋ฏ๋ก ์ด๊ฑธ๋ก ์คํ.
์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ก๊ทธ์ธ๊ณผ ๋ณ๊ฐ๋ก ํ ์ด๋ธ ์์ฑ
package com.shop.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name; // ๋๋ค์์ด ์๋์ผ๋ก ๋ฑ๋ก ๋จ
@Column(nullable = false)
private String email; // ์ด๋ฉ์ผ์ด ์๋์ผ๋ก ๋ฑ๋ก ๋จ
private String picture; // ํ์ฌ๊ฐ ์๋์ผ๋ก ๋ฑ๋ก ๋จ
private String role = "ROLE_USER";
public User(String name, String email, String picture) {
this.name = name;
this.email = email;
this.picture = picture;
}
public User update(String name, String picture) {
this.name = name;
this.picture = picture;
return this;
}
}
๊ธฐ๋ณธ ์ ์ผ๋ก ๋ค๋ฅธ ๊ฒ๋ค๊ณ ์ฐ๋์ด ๋์ผ ํ๊ธฐ๋๋ฌธ์
Dto๊ฐ ์์ด์ผ ํจ
package com.shop.dto;
import com.shop.entity.User;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@NoArgsConstructor
// ์ง๋ ฌํ : ์๋ฐ ์์คํ
์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ฐ์ดํธ ์คํธ๋ฆผ ํํ๋ก ์ฐ์ ์ ์ธ ํฌ๋ฉง ๋ณํ ๊ธฐ์
// ๐ ๋ฐ์ดํฐ๋ฅผ ์ค ์ธ์์ ์ญ ๋ฃ์ (๋งํฌ ์ธํฐํ์ด์ค) / ์๋ฐ Object Data ๐ Byte Stream
// ์ญ์ง๋ ฌํ : Byte Stream ๐ ์๋ฐ Object Data
public class SessionUser implements Serializable {
private String name;
private String email;
private String picture;
public SessionUser(User user) {
this.name = name;
this.email = email;
this.picture = picture;
}
}
package com.shop.repository;
import com.shop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
// ์ด๋ฉ์ผ ์ฒดํฌ
Optional<User> findByEmail(String email);
}

package com.shop.config;
import com.shop.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Map;
@Getter
@Setter
@ToString
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey,
String name, String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public OAuthAttributes(){}
public static OAuthAttributes ofGoogle(String userNameAttributeName,
Map<String, Object> attributes){
return new OAuthAttributes(attributes,userNameAttributeName,
(String) attributes.get("name"),
(String) attributes.get("email"),
(String) attributes.get("picture"));
}
public User toEntity(){
return new User(name, email, picture);
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

package com.shop.config;
import com.shop.dto.SessionUser;
import com.shop.entity.User;
import com.shop.repository.UserRepository;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collections;
@Service
public class CustomOAuth2UserService implements
OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Autowired
private UserRepository userRepository;
@Autowired
private HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest)
throws OAuth2AuthenticationException{
OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);
// String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
OAuthAttributes attributes = OAuthAttributes.ofGoogle(userNameAttributeName,
oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionUser(user));
return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
attributes.getAttributes(), attributes.getNameAttributeKey());
}
private User saveOrUpdate(OAuthAttributes attributes){
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity
.update(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}
...
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
...
// ๋ก๊ทธ์์
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) // ๋ก๊ทธ์์ ํ์ด์ง
.logoutSuccessUrl("/")) // ๋ก๊ทธ์์ ์ฑ๊ณต ํ์ด์ง ๐ "/"
// ๊ตฌ๊ธ ์ธ์ฆ
.oauth2Login(oauth2Login -> oauth2Login
.defaultSuccessUrl("/")
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig
.userService(customOAuth2UserService))
);
// ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์๋ฌ ํธ๋ค๋ง
...
๐ ์ด ๊ฒ๊น์ง ํ๊ณ ์คํํด๋ณธ๋ค. ์๋ฌ๊ฐ ๋์ง ์์์ผ ํ๋ค
...
<a href="/oauth2/authorization/google" >๊ตฌ๊ธ ์์ด๋๋ก ๋ก๊ทธ์ธ</a>
</div>
</html>


๐ ๊ตฌ๊ธ ๋ก๊ทธ์ธ ์ฐฝ์ด ๋จ๊ฒ ๋๋ค. ๋ก๊ทธ์ธ์ ํด๋ณด๋ฉด ROLE_USER ์ด๊ธฐ ๋๋ฌธ์ ์ฃผ๋ฌธ๋ง ํ ์ ์๋ค.


๐ DB์๋ ๋ด์ ์ ์์ง๋ง USER๋ก View์์ ๋ณด์ฌ์ง๋ ๋ก์ง์ ๊ตฌํํ์ง ์์๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ๋ณด์ด์ง๋ ์๋๋ค.
๐ ๐ฅTodo
๋ค์ด๋ฒ ๋ก๊ทธ์ธ API
๐ ๋ก๊ทธ์ธ ์งํ
๐
Client ID/PW๋ฅผ ์ด์ฉํด ๊ตฌ๊ธ๊ณผ ๋๊ฐ์ด ํด์ฃผ๋ฉด ๋๋ค
์นด์นด์ค ๋ก๊ทธ์ธ API
๐ ๋ก๊ทธ์ธ ์งํ
๐ ์ ์ฒญํ๊ณ ๋น์ฆ๋์ค ์ฑ ๊ฐ์ ํ๋ฉด ๋๋ค. (์ค๋ช ๋๋ก ํ๋ฉด ๋จ)
๐ ์ค์ ๊ฐ๋ฅํด ์ง
๐
Client ID
๐
Clinet PW
๊ตฌ๊ธ๊ณผ ๋ค๋ฅด๊ฒ ์กฐ๊ธ ๊น๋ค๋กญ๋ค
configํจํค์ง ๊ด๋ จ ํด๋์ค,memberLonginForm.html,application-oauth.properties์์ ์ ํด์ค๋ค
package com.shop.config;
import com.shop.dto.SessionUser;
import com.shop.entity.User;
import com.shop.repository.UserRepository;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Autowired
private UserRepository userRepository;
@Autowired
private HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException{
OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);
String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName
, oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user",new SessionUser(user));
return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
, attributes.getAttributes()
, attributes.getNameAttributeKey()
);
}
private User saveOrUpdate(OAuthAttributes attributes){
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}
package com.shop.config;
import com.shop.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Map;
@Getter
@Setter
@ToString
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name,
String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public OAuthAttributes() {
}
// ํด๋น ๋ก๊ทธ์ธ์ธ ์๋น์ค๊ฐ kakao์ธ์ง google์ธ์ง ๊ตฌ๋ถํ์ฌ, ์๋ง๊ฒ ๋งคํ์ ํด์ฃผ๋๋ก ํฉ๋๋ค.
// ์ฌ๊ธฐ์ registrationId๋ OAuth2 ๋ก๊ทธ์ธ์ ์ฒ๋ฆฌํ ์๋น์ค ๋ช
("google","kakao","naver"..)์ด ๋๊ณ ,
// userNameAttributeName์ ํด๋น ์๋น์ค์ map์ ํค๊ฐ์ด ๋๋ ๊ฐ์ด๋ฉ๋๋ค. {google="sub", kakao="id", naver="response"}
public static OAuthAttributes of(String registrationId, String userNameAttributeName,
Map<String, Object> attributes) {
if (registrationId.equals("kakao")) {
return ofKakao(userNameAttributeName, attributes);
} else if (registrationId.equals("naver")) {
return ofNaver(userNameAttributeName,attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofKakao(String userNameAttributeName,
Map<String, Object> attributes) {
Map<String, Object> kakao_account = (Map<String, Object>) attributes.get("kakao_account"); // ์นด์นด์ค๋ก ๋ฐ์ ๋ฐ์ดํฐ์์ ๊ณ์ ์ ๋ณด๊ฐ ๋ด๊ธด kakao_account ๊ฐ์ ๊บผ๋ธ๋ค.
Map<String, Object> profile = (Map<String, Object>) kakao_account.get("profile"); // ๋ง์ฐฌ๊ฐ์ง๋ก profile(nickname, image_url.. ๋ฑ) ์ ๋ณด๊ฐ ๋ด๊ธด ๊ฐ์ ๊บผ๋ธ๋ค.
return new OAuthAttributes(attributes,
userNameAttributeName,
(String) profile.get("nickname"),
(String) kakao_account.get("email"),
(String) profile.get("profile_image_url"));
}
private static OAuthAttributes ofNaver(String userNameAttributeName,
Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response"); // ๋ค์ด๋ฒ์์ ๋ฐ์ ๋ฐ์ดํฐ์์ ํ๋กํ ์ ๋ณด๋ค ๋ด๊ธด response ๊ฐ์ ๊บผ๋ธ๋ค.
return new OAuthAttributes(attributes,
userNameAttributeName,
(String) response.get("name"),
(String) response.get("email"),
(String) response.get("profile_image"));
}
private static OAuthAttributes ofGoogle(String userNameAttributeName,
Map<String, Object> attributes) {
return new OAuthAttributes(attributes,
userNameAttributeName,
(String) attributes.get("name"),
(String) attributes.get("email"),
(String) attributes.get("picture"));
}
public User toEntity() {
return new User(name, email, picture);
}
}
#Google
spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=
spring.security.oauth2.client.registration.google.scope=profile,email
#Naver
# registration
spring.security.oauth2.client.registration.naver.client-id=
spring.security.oauth2.client.registration.naver.client-secret=
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver
# provider
spring.security.oauth2.client.provider.naver.authorization_uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token_uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response
# KAKAO
spring.security.oauth2.client.registration.kakao.client-id=
spring.security.oauth2.client.registration.kakao.client-secret=
spring.security.oauth2.client.registration.kakao.scope=profile_nickname, account_email, profile_image
spring.security.oauth2.client.registration.kakao.client-name=Kakao
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post
# provider
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id
๐ ๋ณธ์ธ Client ID, PW ๋ฅผ ์ถ๊ฐํด ์ค๋ค
...
<a href="/oauth2/authorization/google">๊ตฌ๊ธ ์์ด๋๋ก ๋ก๊ทธ์ธ</a>
<a href="/oauth2/authorization/naver">๋ค์ด๋ฒ ์์ด๋๋ก ๋ก๊ทธ์ธ</a>
<a href="/oauth2/authorization/kakao">์นด์นด์ค ์์ด๋๋ก ๋ก๊ทธ์ธ</a>
</div>
</html>


๐ ์ฑ๊ณต


๐ ์ฌ์ ์ ๋ฑ๋ก์ ์ํ๊ธฐ ๋๋ฌธ์ ์์ ํ๊ฒ 2์ฐจ ์ธ์ฆ๊น์ง ๋ฐ๋๋ก ๋์์


๐ ์ฑ๊ณต