포트폴리오/ohouseClone

7. 게시판 소셜로그인 만들기(google, naver, kakao)-spring

가끔개발 2023. 11. 1. 17:37

0. Kanbanboard

1. 로그인 form

<a class="btn-login join_btn btn_click" id="kakao-login-btn" href="/oauth2/authorization/kakao">
                    <img alt="카카오" src="https://www.gb.go.kr/Main/Images/ko/member/certi_kakao_login.png"
                         style="max-width: 40%; height: auto; display: block; margin: 10px auto;">
                </a>
                <a href="/oauth2/authorization/google">
                    <img alt="구글" src="data:image/png;base64,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"
                         style="max-width: 40%; height: auto; display: block; margin: 10px auto;">
                </a>
                <a href="/oauth2/authorization/naver">
                    <img alt="네이버" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAdCAYAAABPGImpAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVUSURBVGhD7ZoNTNRlHMe//7uD452D450QAXnxBVIsDSVNoVmpy6ZrZnMFZblVS6xGM0tbodWWa2u2Zk2YVGaW1rCWFpYx302nYmGBvCvE+/v7Xb/nuQc44AjI+2/8t//n9tx+z++5e+74f3+/3/P7s5N0Z9ctMveZMyFJ4VCZvJjNNySzlCJpT68rpGmExasyySnS0JMqlnKIYIKpKAhVMIWhCqYwVMEUhiqYwlAFUxiTWjAXjR5eOlcYaITp/YR3JBKNeNcwy2QU2D5T9b5iJj8hjkb46NzFzH6wG2ezsG+LRPdo7InYwO3jTdfwfHEmt1d534UdU9Zye2fld8iuyeO2LV4MfBCfVOei3dTN56u953EhfmspwAqvOXihOAuRTgF4OWgFesx9eIf2q+iuh1bSoGD2LkRe2oSHDLNR1l2H/PZyTHcORqjeBz82XsZyen+yZyzSSvbxvRl6jQN2hKyFhiluxfWOW/i4+mdu3+sRg2lO/ty25ljjVVTSZwc4GPBu6GMkkA8utZXg9fID/Ptvu2M1rnfewpe1p8Q77IPdMsxVq0eUUyAfG/2TscY4n/s9tM4DfoPWhftG40nfRXDSOIrZSJxp7WDUJmTVnEBOw+/4JjqN/oChVzvJcxai6bMYUSTuEo+Z3LYFEz27Ng/7KIj6Ry4FGwuyobDPGBwpvvch3MmS8VnTNuJIwyUk/fE2anubkSGCUy5kK4m7w1IR6GgQs4nh5+CJ+ykb4lynCI+FxR7TcaHtBk63/I2jjVdQ1d2EWJcQsTpItHMQFrpHIYYyTJIknoHsMRwt+ZJJYJZ5/YNVCmvymguQ+c+vQwbLrMbedr7vXNdwHK4/DzM9Pq89iQXukeKd8iCbYEadGz4Nf4bicVi9GQfedN7Mc4ugUhQgPBaCHb1R3lUnZqDSV4sQKnnDiXWZQoLHccHXGhNwYsYb2Gkj8vUaHZ71T8L51qKBwcpneul+8Qrb+Dp44CaJ1mc24XJ7KVZ6xdOFlPCo8R6coz3kRBbBzrYW8ohbZrgTT/stFd7xU9BxExmV3+JQ3TnhsdDQ20ZiuokZE9aNfK1iNsiButPYXvE19tP5wUbite1IL/tCrA7FUdIhyplKttVgwaKTtOIVI2HB2NDXzu0nCj/iQp2JfYs3Na+OIfbtIotgv9A58GHVUW4nTLBEPOJ9N4/6XaHreVNgTV7Ln1jqOROedBYG0mE/x2UqLrTeEKuDsDMsgZfEIOGxTaepB5tLPkM9ib7Gez51or7cZoHBAs4W7DsxkU2UXYzK7gasL9yNhfnbeKPVZurifrmQrSS+VnaAZ8pEeO9mDvR0MZr6OpBJjUWXqVesWKjpacFW2vfYjC04FL0ZzxXvRZd56Gt+aroKF2qAHqDsdqeGJ7c5X6wMhZ0/bBxpvEgNzEVeXlk5Y3YO+ViGsdacdZnWY4FbFKp7mrhtFG374z6JeCVoJbf7yW3Kx5W2UjGzH3Zr65cZ4vB9TDq3Wbu9tfwrOpDDcHLWmwPlhbXU/Zk3Hmy19bawbutHY3hb/7DXXGygTO5HS2cQuxAmq8yq6WlGH3lG41TLX9hLTUiK72IE643IqDgsVvoZLU//P7Lch+2pPo4Pbv3A7ZeCluMpvyXcHus+bDisRWdlrbCzigSLH0Ow90mwNOEZiUWwWSRYtvDYDxZYW4JXjSiHB+vOTChAx4PdBJMTXr6oencPK3/WsHu0DnHDbYvx7KEEZDvD7Alrn8e60P8lFmM8eygBRQimMogqmMJQBVMYqmAKQxVMYaiCKQwmmLz/XlaxJ0UayYRU9rtt4VCZrLDf1puQ+i931dlOPsjCpAAAAABJRU5ErkJggg=="
                         style="max-width: 40%; height: auto; display: block; margin: 10px auto;">
                </a>

2. build.gradle

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

3. application.yml

security:
    oauth2:
      client:
        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: 
          naver:
            authorization-uri: https://nid.naver.com/oauth2.0/authorize
            token-uri: https://nid.naver.com/oauth2.0/token
            user-info-uri: https://openapi.naver.com/v1/nid/me
            user-name-attribute: response
        registration:
          kakao:
              client-id: 
              client-secret: 
              client-authentication-method: POST
              redirect-uri: http://localhost:8080/login/oauth2/code/kakao
              authorization-grant-type: authorization_code
              client-name: Kakao
              scope:
                - account_email
                - profile_nickname
          naver:
            client-id: 
            client-secret: 
            redirect-uri: http://localhost:8080/login/oauth2/code/naver
            authorization-grant-type: authorization_code
            client-name: Naver
            scope:
              - email
              - nickname
              - name
          google:
            client-id: 
            client-secret:
            redirect-uri: http://localhost:8080/login/oauth2/code/google
            scope:
              - email

https://lotuus.tistory.com/104

 

[Spring Security] OAuth 카카오 로그인하기

목차 이전글 https://lotuus.tistory.com/80 [Spring Security] OAuth 네이버 로그인하기 목차 이전글 https://lotuus.tistory.com/79 [Spring Security] OAuth 구글 로그인하기 목차 [이전 게시글] 꼭! 봐주세여 [Spring Security] 동

lotuus.tistory.com

작성한 코드를 반영하여 작성하였습니다.

4. 각각의 개발자 센터


1. 카카오 

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

2.  Naver

https://developers.naver.com/main/

 

NAVER Developers

네이버 오픈 API들을 활용해 개발자들이 다양한 애플리케이션을 개발할 수 있도록 API 가이드와 SDK를 제공합니다. 제공중인 오픈 API에는 네이버 로그인, 검색, 단축URL, 캡차를 비롯 기계번역, 음

developers.naver.com

3. 구글

https://console.cloud.google.com/apis/dashboard?pli=1&project=tranquil-osprey-380908

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

5. 디자인 가이드


1. kakao

https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

2.  Naver

https://developers.naver.com/docs/login/bi/bi.md

 

로그인 버튼 사용 가이드 - LOGIN

네이버 로그인은 애플리케이션에 사용할 수 있는 네이버 로그인 버튼 기본 이미지를 제공합니다. 애플리케이션의 상황에 맞게 버튼 이미지의 디자인을 변경할 수 있지만 네이버 고유의 아이덴

developers.naver.com

3. 구글

https://developers.google.com/identity/branding-guidelines?hl=ko

 

로그인 브랜드 가이드라인  |  Google ID 플랫폼  |  Google for Developers

로그인 브랜드 가이드라인 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 모바일 또는 웹 앱에서 기본적인 profile 또는 email 범위로 Google 로그인을 사용하고

developers.google.com

7. Oauth2 interface

package com.portfolio.ohousev1.auth;

import java.util.Map;

public interface OAuth2UserInfo {
    Map<String, Object> getAttributes();
    String getProviderId();
    String getProvider();
    String getEmail();
    String getName();

    String getNickname();

8. Oauth2 Userinfo


이메일을 PK로 지정했기때문에 member table 이메일기준으로 저장된다.

1. kakao

package com.portfolio.ohousev1.auth;

import java.util.Map;

public class KakaoUserInfo implements OAuth2UserInfo{
    private  Map<String, Object> attributes;
    private  Map<String, Object> attributesAccount;
    private Map<String, Object> attributesProfile;

    public KakaoUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
        this.attributesAccount = (Map<String, Object>) attributes.get("kakao_account");
        this.attributesProfile = (Map<String, Object>) attributesAccount.get("profile");
    }
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    @Override
    public String getProviderId() {
        return attributes.get("id").toString();

    }

    @Override
    public String getProvider() {
        return "kakao";
    }



    @Override
    public String getEmail() {
        return attributesAccount.get("email").toString();
    }

    @Override
    public String getName() {
        return attributesAccount.containsKey("name") ? attributesAccount.get("name").toString() : attributesProfile.get("nickname").toString();
    }


    @Override
    public String getNickname() {
        return attributesProfile.get("nickname").toString();

    }
}

2.  Naver

package com.portfolio.ohousev1.auth;

import java.util.Map;

public class NaverUserInfo implements OAuth2UserInfo{
    private final Map<String, Object> attributes; //OAuth2User.getAttributes();
    private final Map<String, Object> attributesResponse;

    public NaverUserInfo(Map<String, Object> attributes) {
        this.attributes = (Map<String, Object>) attributes.get("response");
        this.attributesResponse = (Map<String, Object>) attributes.get("response");
    }
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    @Override
    public String getProviderId() {
        return attributesResponse.get("id").toString();

    }

    @Override
    public String getProvider() {
        return  "naver";
    }



    @Override
    public String getEmail() {
        return attributes.get("email").toString();
    }

    @Override
    public String getName() {
        return attributes.get("name").toString();
    }

    @Override
    public String getNickname() {
        return attributes.get("nickname").toString();
    }




}

3. 구글

package com.portfolio.ohousev1.auth;

import java.util.Map;

public class GoogleUserInfo implements  OAuth2UserInfo{

    private final Map<String, Object> attributes;


    public GoogleUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;

    }
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    @Override
    public String getProviderId() {
        return attributes.get("sub").toString();
    }

    @Override
    public String getProvider() {
        return "google";
    }

    @Override
    public String getEmail() {
        return attributes.get("email").toString();
    }

    @Override
    public String getName() {
        return attributes.get("name").toString();
    }

    @Override
    public String getNickname() {
        return (attributes.get("nickname") != null) ? attributes.get("name").toString() : null;    }


}

9. OAuth2UserService

package com.portfolio.ohousev1.service.member;

import com.portfolio.ohousev1.auth.GoogleUserInfo;
import com.portfolio.ohousev1.auth.KakaoUserInfo;
import com.portfolio.ohousev1.auth.NaverUserInfo;
import com.portfolio.ohousev1.auth.OAuth2UserInfo;
import com.portfolio.ohousev1.dto.post.PostPrincipal;
import com.portfolio.ohousev1.entity.constant.RoleType;
import lombok.extern.slf4j.Slf4j;
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.Set;
import java.util.UUID;


@Service
@Slf4j
@Transactional(readOnly = true)
public class OAuth2UserService extends DefaultOAuth2UserService {


    private final MemberService memberService;

    public OAuth2UserService(MemberService memberService) {
        this.memberService = memberService;
    }

    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        OAuth2UserInfo oAuth2UserInfo = null;
        String provider = userRequest.getClientRegistration().getRegistrationId();
        switch (provider) {
            case "google" -> oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
            case "naver" -> oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
            case "kakao" -> oAuth2UserInfo = new KakaoUserInfo(oAuth2User.getAttributes());
        }

        // nameAttributeKey
        String providerId = oAuth2UserInfo != null ? oAuth2UserInfo.getProviderId() : null;
        String name = provider + "_" + providerId;
        String dummyPassword = "{bcrypt}" + UUID.randomUUID();
        String email = oAuth2UserInfo != null ? oAuth2UserInfo.getEmail() : null;
        String nickname = oAuth2UserInfo.getNickname();
        Set<RoleType> roleTypes = Set.of(RoleType.USER);
        log.info("cause" + providerId, name, dummyPassword, email, nickname, roleTypes);
        return memberService.searchEmail(email).map(PostPrincipal::from)
                .orElseGet(() -> {
                    assert email != null;
                    return PostPrincipal.from(memberService.saveMember(
                            email,
                            dummyPassword,
                            roleTypes,
                            name,
                            nickname,
                            null
                    ));
                });
    }

}

유정정보를 받아오고 그 받아오는 값을  권한을 주고 DB에저장시킵니다.

1. kakao

  • email
  • 이름

2. naver

  • email
  • 이름

3. Google

  • email

비밀번호는 dummy 값으로 넣고 bcypt형태로 암호화한후 저장하고,

닉네임은 못가져올경우 앞에 가져오는 oauth2(ex naver, kakao,google)앞에 붙이고 provideID값을 넣고,

권한은 USER

생일은 null 값을 넣어준다.

10.SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfig {

    private final OAuth2UserService oAuth2UserService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    	http
                .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
                .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .ignoringRequestMatchers(new AntPathRequestMatcher("/mysql/**")))
                .headers((headers) -> headers
                        .addHeaderWriter(new XFrameOptionsHeaderWriter(
                                XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)));
        // login 설정
        http
                .oauth2Login(httpSecurityOAuth2LoginConfigurer -> httpSecurityOAuth2LoginConfigurer
                        .loginPage("/login")
                        .userInfoEndpoint()
                        .userService(oAuth2UserService));
                        
        http
                .logout((logout) -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
                        .permitAll()
                        .logoutSuccessUrl("/")// logout에 성공하면 /로 redirect
                        .invalidateHttpSession(true));

        return http.build();

 

 

 

https://github.com/nodwon/OhouseV1

 

GitHub - nodwon/OhouseV1: 지금까지 공부한것을 기반으로 포트폴리오 제작

지금까지 공부한것을 기반으로 포트폴리오 제작. Contribute to nodwon/OhouseV1 development by creating an account on GitHub.

github.com