개발자의 서재

Chapter.05-1 : 스프링 시큐리티와 OAuth 2.0 으로 로그인 구현하기 본문

SpringBootProject/SpringBoot_ Oauth_AWS

Chapter.05-1 : 스프링 시큐리티와 OAuth 2.0 으로 로그인 구현하기

ironmask431 2022. 2. 11. 23:27

"스프링부트와 AWS로 혼자 구현하는 웹 서비스" 라는 책을 바탕으로 학습목적의 프로젝트를 진행하고 있습니다. 

 

소스 : https://github.com/ironmask431/springboot_aws_01

Chapter 05. 스프링 시큐리티와 OAuth 2.0 으로 로그인 구현하기

스프링시큐리티는 막강한 인증과 인가 기능을 가진 프레임워크 입니다. 

스프링 시큐리티와 OAuth 2.0 을 구현한 구글 로그인을 연동하여 로그인 기능을 만들어보겠습니다. 

5.1 스프링시큐리티와 OAuth2 클라이언트 

많은 서비스에서 로그인기능을 id/pw 보다는 구글,페이스북,네이버같은 소셜 로그인을 사용합니다. 

직접 로그인 기능 만드는 것 보다 OAuth 로그인 을 구현하면 로그인관련 기능을 구글,페이스북,네이버에 맡기면 되니

서비스개발에 집중하기 좋습니다. 

5.2 구글 서비스 등록

* console.cloud.google.com 접속

프로젝트 선택

→ 새 프로젝트

→ 프로젝트명 입력하기

→ api 및 서비스

→ 사용자 인증정보 

→ 사용자 인증정보 만들기

→ OAuth 클라이언트id

→ 동의화면구성

→ 사용자 유형 : 외부선택 

→ 앱이름, 사용자지원이메일, 개발자연락처정보(이메일)등록

→ 다음  범위 추가 또는 삭제클릭 

→ email, profile, openid 체크선택 

→ 테스트사용자등록 + ADD USER

→ 내 이메일 입력.. 

→ 요약 내용 확인

→ 다시 사용자인증정보

→ 사용자 인증정보 만들기  OAuth 클라이언트id

→ 애플리케이션 유형 : "웹애플리케이션" 선택 / 이름 : "springboot_aws_01"

승인된 리디렉션 URI 추가  "http://localhost:8080/login/oauth2/code/google" 추가

→ 만들기 

→ OAuth 클라이언트 ID와 보안비밀번호 확인 (프로젝트에서 사용)

 

OAuth 클라이언트 ID 생성완료.

* 승인된 리디렉션URI :
서비스에서 파라미터로 인증정보를 주었을 때 인증이 성공하면 구글에서 리다이렉트할 URI 입니다. 
스프링부트2 버전의 시큐리티에서는 기본적으로 "{도메인}/login/oauth2/code/{소셜서비스코드}"
로 리다이렉트URI를 지원하고 있습니다. 
사용자가 별도로 리다이렉트URI 매핑 컨트롤러를 만들지않아도됩니다. 시큐리티에서 이미 구현되어 있습니다.
현재는 개발단계이므로 "http://localhost:8080/login/oauth2/code/google" 로만 등록합니다. 
실제 배포하게 되면 주소를 추가해야 합니다. 

* application-oauth 등록

src/main/resources 에 application-oauth.proerperties 생성 

spring.security.oauth2.client.registration.google.client-id=여기에 클라이언트id 입력
spring.security.oauth2.client.registration.google.client-secret=여기에 클라이언트 보안비밀번호 입력
spring.security.oauth2.client.registration.google.scope=profile,email
scope=profile, email 설정이유 
미설정 시 기본값은 openid, profile, email 인데
openid 라는 scope 가 있으면, open Id Provider 로 인식함. 이렇게 되면 Open Id Provider 인
서비스(구글)과 그렇지 않은 서비스(네이버/카카오)로 나눠서 각각
OAuth2Service 를 만들어야합니다. 
하나의  OAuth2Service를 사용하기 위해 openid scope를 제외하고 등록합니다. 

스프링부트에서 properties 의 이름을 application-xxxx.properties 로 만들면 xxxx라는 이름의

profile이 생성되어 이를통해 관리 할 수 있습니다. 

즉 profile=xxx 식으로 호출하면 해당 properties의 설정들을 가져올 수 있습니다. 

application-oauth.proerperties  설정을 사용하기 위해

application.properties 에 코드 를 추가합니다. 

spring.profiles.include=oauth

* .gitIgnore 등록

OAuth 클라이언트 ID와 보안비밀번호는 중요정보이기 때문에 

깃허브에 올라가지 않도록 해야하므로 .gitignore 에 application-oauth.proerperties 를 추가한다. 

5.3 구글 로그인 연동하기

사용자 정보를 담당할 User 엔티티 클래스 생성

package com.jojodu.book.springboot.domain.user;
/**
 * 사용자 정보 담당 엔티티
 */
@NoArgsConstructor
@Getter
@Entity
public class User extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    //JPA로 데이터베이스로 저장할때 Enum값을 어떤형태로 저장할지를 결정합니다.
    //기본적으로는 int로 된 숫자가 저장되자만, 숫자로저장되면 db에서 확인 시 무슨의미인지 알 수없음.
    //그래서 문자열(EnumType.STRING)로 저장될 수 있도록 선언
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role){
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }
    public User update(String name, String picture){
        this.name = name;
        this.picture = picture;
        return this;
    }

    public String getRoleKey(){
        return this.role.getKey();
    }
}

각 사용자의 권한을 관리할 Enum 클래스 Role 생성

package com.jojodu.book.springboot.domain.user;
/**
 * 사용자의 권한관리 클래스(enum)
 */
@Getter
@RequiredArgsConstructor
public enum Role {
    //스프링 시큐리티에서는 권한 코드에 항상 ROLE_ 가 앞에 있어야만합니다.
    GUEST("ROLE_GUEST","손님"),
    USER("ROLE_USER","일반 사용자");
    private final String key;
    private final String title;
}

User의 CRUD를 담당할 UserRepository.java 생성

package com.jojodu.book.springboot.domain.user;
/**
 * User의 CRUD 담당
 */
public interface UserRepository extends JpaRepository<User, Long> {
    //소셜로그인으로 반환되는 값중 email을 통해 이미 생성된 사용자인지
    //처음 가입하는 사용자인지 판단하기 위한 메소드
    //스트림으로 변환하기 위해 리턴타입을 Optional<User> 클래스로 생성
    Optional<User> findByEmail(String email);
}

* 스프링시큐리티 설정

build.gradle 에 spring security / oauth2관련 라이브러리 추가 

//oauth 추가 (소셜로그인등 클라이언트입장에서 소셜 기능 구현 기능)
implementation('org.springframework.boot:spring-boot-starter-oauth2-client')

* 소셜 로그인 설정 코드 작성 

SecurityConfig.java 

package com.jojodu.book.springboot.config.auth;
/**
 * Spring Security + OAuth2 로그인 설정
 */
@RequiredArgsConstructor
@EnableWebSecurity //Spring Security 설정등을 활성화시켜줍니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final CustomOAuth2UserService customOAuth2UserService;
    protected void configure(HttpSecurity http) throws Exception{
        http.csrf().disable().headers().frameOptions().disable()
                .and().authorizeRequests()
                .antMatchers("/","/css/**","/images/**","/js/**","/h2-console/**").permitAll()
                .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                .anyRequest().authenticated()
                .and().logout().logoutSuccessUrl("/")
                .and().oauth2Login().userInfoEndpoint().userService(customOAuth2UserService);
    }
    /**
     * .headers().frameOptions().disable() = h2-console 화면을 사용하기 위해 해당옵션들을 disable합니다.
     * .authorizeRequests() = URL별 권한 관리를 설정하는 옵션의 시작점입니다. 이후 antMatchers() 옵션을 사용가능합니다.
     * .antMatchers() = 권한 관리 대상을 지정하는 옵션입니다.URL,HTTP 메소드별로 관리가 가능합니다.
     * .anyRequest().authenticated() = 설정값 이외의 url에 대한 설정입니다. 이외의 url은 모두 인증된 사용자에게만 허용합니다.
     * .and().logout().logoutSuccessUrl("/") = 로그아웃 성공시 "/"로 이동
     * .oauth2Login() = oauth2 로그인 설정의 시작점
     * .userInfoEndpoint() = oauth2 로그인 성공 후 사용자정보를 가져올때 설정담당
     * .userService() = 소셜로그인성공후 후속조치를 진행할 userService 인터페이스의 구현제등록
     * 소셜서비스에서 사용자정보를 가져온 상태에서 추가로 진행하고자하는 기능을 명시할 수 있음
     */
}

OAuthAttributes.java

package com.jojodu.book.springboot.config.auth.dto;
/**
 * OAuth2UserService 를 통해서 가져온 oAuth2User의 attribute를 담을 클래스
 */
@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    @Builder
    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;
    }
    //OAuth2User 정보를 OAuthAttributes에 입력
    public static OAuthAttributes of(String registrationId, String userNameAttributeName,Map<String,Object> attributes){
        return ofGoogle(userNameAttributeName, attributes);
    }
    //OAuth2User 에서 반환하는 사용자정보는 Map이기 때문에 하나하나 변환해야합니다.
    public static OAuthAttributes ofGoogle(String userNameAttributeName,Map<String,Object> attributes){
        return OAuthAttributes.builder()
                .name((String)attributes.get("name"))
                .email((String)attributes.get("email"))
                .picture((String)attributes.get("picture"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }
    //User Entity를 생성합니다.
    //OAuthAttributes 에서 엔티티를 생성하는 시점(DB insert)은 처음 로그인할때입니다.
    //최초 생성할때의 기본 권한을 GUEST로 줍니다.
    public User toEntity(){
        return User.builder()
                .name(name)
                .email(email)
                .picture(picture)
                .role(Role.GUEST)
                .build();
    }
}

SessionUser.java

package com.jojodu.book.springboot.config.auth.dto;
/**
 * 세션에 사용자정보 담기위한 클래스
 */
@Getter
public class SessionUser implements Serializable {
    //session에 담기위해 Serializable를 구현

    private String name;
    private String email;
    private String picture;

    public SessionUser(User user){
        this.name = user.getName();
        this.email = user.getEmail();
        this.picture = user.getPicture();
    }
    //SessionUser 에는 인증된 사용자정보만 필요하므로, name,email,picture만 선언합니다.
    /**
     * 세션에 User Entity클래스를 바로 저장하면 안되는 이유.
     * -> User클래스에 직렬화를 구현하지 않았으므로, Failed to Converto from type... 에러발생
     * 엔티티클래스 자체를 직렬화 한다면 여러가지 문제가 생길 수 있음.
     * @OneToMany, @ManyToMany 등 자식엔티티를 가지고있다면 자식까지 직렬화되어
     * 성능이슈, 부수효과가 발생 할 수 있음.
     * 그래서 직렬화 기능을 가진 세션Dto(SessionUser)를 따로 만드는것이 운영유지보수에 좋음.
     */
}

CustomOAuth2UserService.java

package com.jojodu.book.springboot.config.auth;
/**
 * OAuth2 로그인 이후 가져온 사용자의 정보(email, name, picture 등)
 * 을 기반으로 가입, 정보수정, 세션저장 등의 기능 담당
 * OAuth2UserService 인터페이스를 구현
 */
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        //OAuth2UserService 를 통해 oAuth2User(로그인한 유저) 정보를 가져옴.

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        //현재 로그인 진행중인 서비스를 구분하는 코드. (구글, 네이버(=naver), 카카오(=kakao)등)

        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();
        //OAuth2 로그인 진행 시 키가되는 필드값. PK와 같은 의미
        //구글의 경우 기본적으로 코드지원하지만, 네이버,카카오는 지원하지않음 구글의 기본코드는 "sub"
        //네이버 카카오는 application-oauth.properties 에 정의한 user-name-attribute 값으로 설정됨.

        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName,oAuth2User.getAttributes());
        //OAuthAttributes = OAuth2UserService 를 통해서 가져온 oAuth2User의 attribute를 담을 클래스입니다.   

        User user = saveOrUpdate(attributes);
        //로그인 user정보 DB insert or update 실행 
        httpSession.setAttribute("user",new SessionUser(user));
        //SessionUser = 세션에 사용자정보 담기위해 만든 클래스

        return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey()))
                ,attributes.getAttributes()
                ,attributes.getNameAttributeKey());
    }

    //구글 사용자정보가 업데이트되었을때를 대비하여 update 기능도 같이구현.
    //사용자의 이름, 프로필사진이 변경되면 User엔티티에 반영됨
    private User saveOrUpdate(OAuthAttributes attributes){
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());
        //attributes의 이메일로 user정보를 userRepository통해서 조회한다음, 일치하는 정보가 있으면
        //해당 유저의 name과 picture를 업데이트하고, 해당 유저정보를 user 엔티티로 반환.
        //일치하는 정보가 없으면 attributes의 정보로 새로운 user 엔티티를 만들어서 (DB insert) 반환.
        return userRepository.save(user);
    }
}

* 로그인 테스트

index.mustache 수정

<div class="col-md-6">
    <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
    {{#userName}} <!-- userName 이 있을 경우 -->
        Logged in as : <span id="user">{{userName}}</span>
        <!-- 스프링시큐리티에서 기본 제공하는 로그이웃 url -->
        <a href="/logout" class="btn btn-info active" role="button">Logout</a>
    {{/userName}}
    {{^userName}} <!-- userName 이 없을 경우 -->
    	<!-- 스프링시큐리티에서 기본 제공하는 oauth2 로그인url  -->
        <a href="/oauth2/authorization/google" class="btn btn-success active" role="button">Google Login</a>
    {{/userName}}
</div>
머스테치는 다른언어와 같은 if문 을 제공하지않음 ( true, false만 반환 )
{{#userName}} ~ {{/userName}} =  userName 이 있을 경우
{{^userName}} ~ {{/userName}} =  userName 이 없을 경우
/logout = 스프링시큐리티에서 기본제공하는 로그아웃 url 입니다.  별도록 
컨트롤러 만들필요없음.
/oauth2/authorization/google = 스프링시큐리티에서 기본제공하는 로그인url 입니다. 
별도로 컨트롤러 만들필요없음.

IndexController.java 에 UserName을 model에 추가하는 코드 추가

@GetMapping("/")
public String index(Model model){
    model.addAttribute("posts", postsService.findAllDesc());
    SessionUser user = (SessionUser) httpSession.getAttribute("user");
    if(user != null){
        model.addAttribute("userName",user.getName());
    }
    return "index";
}

localhost:8080/ 접속

로그인버튼 클릭

로그인 성공

h2-console 에서 회원가입이 되어 있는지 확인

 

글 작성 테스트 시 현재 로그인한 사용자의 권한이 GUEST이기 때문에 

SecurityConfig에 설정한대로 "/api/v1/**" api url은 허용되지 않아서 "403" 에러 발생

해당 유저의 ROLE 를 USER로 업데이트

로그아웃 후 재로그인 (세션의 ROLE을 새로고침하기위해) 후 글등록 정상 확인

5.4 네이버 로그인

* 네이버 로그인 API 이용 등록

https://developers.naver.com/products/login/api/api.md

애플리케이션 이름 : springboot_aws_01 

네이버로그인 선택 

사용 api 체크 : 회원이름 / 이메일 / 프로필사진 선택

환경추가 : pc 웹

서비스 url : http://localhost:8080/

네이버아이디 로그인 콜백 : (구글에서 등록한 리디렉션url과 같은 역할)

http://localhost:8080/login/oauth2/code/naver

client ID / Client Secret 정보 확인

 

* application-oauth.properties 에 네이버 로그인 정보 등록

// naver login
spring.security.oauth2.client.registration.naver.client-id=여기에 client_id
spring.security.oauth2.client.registration.naver.client-secret=여기에 client_secret
// localhost:8080/login/oauth2/code/naver (스프링 시큐리티에서 기본제공하는 형태)
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

// 네이버 로그인 인증 요청 uri
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
// 네이버 접근 토큰의 발급,갱신,삭제 요청 uri
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
// 네이버 회원의 프로필을 조회 uri
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
// 기준의 되는 user-name-attribute 의 이름을 response로 등록
// 실제 유저 키깂은 reponse.id 이지만 스프링 시큐리티에서는 하위필드를 명시 할 수 없으므로, 
// 상위필드인 response라고 명시
spring.security.oauth2.client.provider.naver.user-name-attribute=response

* 유저정보 조회 api response 형태

{
  "resultcode": "00",
  "message": "success",
  "response": {
    "email": "openapi@naver.com",
    //email : 회원의 네이버아이디가 아님. 회원가입시 별도등록한 다른 email 
    "nickname": "OpenAPI",
    "profile_image": "https://ssl.pstatic.net/static/pwe/address/nodata_33x33.gif",
    "age": "40-49",
    "gender": "F",
    "id": "32742776",
    //유저의 고유 key값. 같은 사용자라도 요청하는 애플리케이션에 따라 다른값.
    "name": "오픈 API",
    "birthday": "10-01",
    "birthyear": "1900",
    "mobile": "010-0000-0000"
  }
}

* OauthAttributes.java 에 네이버 설정 등록

//OAuth2User 정보를 OAuthAttributes에 입력
public static OAuthAttributes of(String registrationId, String userNameAttributeName,Map<String,Object> attributes){

    //registrationId = 로그인 서비스 구분 코드
    //String = userNameAttributeName 은 사용자정보 조회 응답 json에서 유니크키 값.
    //구글은 "sub"로 자동
    //카카오 네이버는 application-oauth.properties 에 설정한 user-name-attribute 값
    if("naver".equals(registrationId)){
        return ofNaver(userNameAttributeName, attributes);
    }else if("kakao".equals(registrationId)){
        return ofKakao(userNameAttributeName, attributes);
    }
    return ofGoogle(userNameAttributeName, attributes);
}

//naver
public static OAuthAttributes ofNaver(String userNameAttributeName,Map<String,Object> attributes){
    //네이버의 경우 회원정보로 리턴받은 json형태가 response 내부에 유저정보가 들어있으므로 아래 코드추가함.
    Map<String, Object> response = (Map<String, Object>)attributes.get("response");
    //네이버의 경우 userNameAttributeName 이 "response"로 되어있는데 실제 키값은 response안에 id 이므로, id라고 변경해줌.
    userNameAttributeName = "id";
    return OAuthAttributes.builder()
            .name((String)response.get("name"))
            .email((String)response.get("email"))
            .picture((String)response.get("profile_image"))
            .attributes(response) //user 정보 map
            .nameAttributeKey(userNameAttributeName) // user정보map의 pk필드 (네이버 = id)
            .build();
}

* index.mustache 에 네이버 로그인 버튼 추가 

<!-- 스프링시큐리티에서 기본 제공하는 oauth2 로그인url  -->
<a href="/oauth2/authorization/naver" class="btn btn-success active" role="button">Naver Login</a>

* 로그인테스트

정상 로그인 확인. 

5.5 카카오 로그인

카카오 로그인 연동은 네이버와 유사하다. 

카카오 디벨로퍼 접속 : https://developers.kakao.com/

 

내 애플리케이션 > 애플리케이션 추가하기 > 앱이름, 사업자명 등록

 

REST API 키 확인 (구글,네이버의 client_id 와 같은 역할)

 

앱설정 > 플랫폼 > web 플랫폼 등록

사이트도메인 : http://localhost:8080

 

제품설정 > 카카오 로그인 > 활성화 설정 on > Redirect URI 생성 > 
http://localhost:8080/login/oauth2/code/kakao

 

제품설정 > 카카오 로그인 > 동의항목 > 
닉네임(필수), 프로필사진(선택), 카카오계정(이메일)(선택) 으로 설정함.

제품설정 > 보안 > client secret : 카카오는 client secret이 선택사항이므로 패스

제품설정 > 고급 > Logout Redirect URI : 스프링시큐리티에서 로그아웃은 

"/logout" 으로 기본설정되어 있으니 패스한다.

 

* application-oauth.properties 에 카카오 로그인 정보 등록

//kakao login
spring.security.oauth2.client.registration.kakao.client-id=여기에 REST API 키
// localhost:8080/login/oauth2/code/kakao (스프링 시큐리티에서 기본제공하는 형태)
spring.security.oauth2.client.registration.kakao.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.scope=profile_nickname,profile_image,account_email
spring.security.oauth2.client.registration.kakao.client-name=kakao
//kakao의 경우 별도로 추가해줘야함.
spring.security.oauth2.client.registration.kakao.client-authentication-method=POST

// 카카오 로그인 인증 요청 uri
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
//카카오 접근 토큰의 발급,갱신,삭제 요청 uri
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
//카카오 회원의 프로필을 조회 uri
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
//카카오 회원정보 조회 응답 json 형태 확인하여 pk키값 필드 입력
spring.security.oauth2.client.provider.kakao.user-name-attribute=id

* 처음 카카오 로그인 시 [invalid_token_response] An error occurred while attempting to 
retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized  오류 발생
구글링해보니 
혹시, application.yml 설정에서 “client-authentication-method: POST” 설정이 있는지도 확인 해주세요~
application.properties 에 추가하고 정상 로그인됨. 

 

* 유저정보 조회 api response 형태

{
    "id":123456789,  // key값
    "kakao_account": { 
        "profile_needs_agreement": false,
        "profile": {
            "nickname": "홍길동",
            "thumbnail_image_url": "http://yyy.kakao.com/.../img_110x110.jpg",
            "profile_image_url": "http://yyy.kakao.com/dn/.../img_640x640.jpg",
            "is_default_image":false
        },
        "name_needs_agreement":false, 
        "name":"홍길동",
        "email_needs_agreement":false, 
        "is_email_valid": true,   
        "is_email_verified": true,
        "email": "sample@sample.com",
        "age_range_needs_agreement":false,
        "age_range":"20~29",
        "birthday_needs_agreement":false,
        "birthday":"1130",
        "gender_needs_agreement":false,
        "gender":"female"
    },  
    "properties":{
        "nickname":"홍길동카톡",
        "thumbnail_image":"http://xxx.kakao.co.kr/.../aaa.jpg",
        "profile_image":"http://xxx.kakao.co.kr/.../bbb.jpg",
        "custom_field1":"23",
        "custom_field2":"여"
        ...
    }
}

* OauthAttributes.java 에 카카오 설정 등록

//OAuth2User 정보를 OAuthAttributes에 입력
public static OAuthAttributes of(String registrationId, String userNameAttributeName,Map<String,Object> attributes){

    //registrationId = 로그인 서비스 구분 코드
    //String = userNameAttributeName 은 사용자정보 조회 응답 json에서 유니크키 값.
    //구글은 "sub"로 자동
    //카카오 네이버는 application-oauth.properties 에 설정한 user-name-attribute 값
    if("naver".equals(registrationId)){
        return ofNaver(userNameAttributeName, attributes);
    }else if("kakao".equals(registrationId)){
        return ofKakao(userNameAttributeName, attributes);
    }
    return ofGoogle(userNameAttributeName, attributes);
}

//kakao
public static OAuthAttributes ofKakao(String userNameAttributeName,Map<String,Object> attributes){
    //카카오 회원정보 조회 api 리턴 json 형태를 보고 코드 작성.
    Map<String, Object> kakao_account = (Map<String, Object>)attributes.get("kakao_account");
    Map<String, Object> profile = (Map<String, Object>)kakao_account.get("profile");
    //profile 에 유저정보가 들어있지만, email과 id(pk)는 밖에있으므로, profile에 추가해준다.
    profile.put("email",kakao_account.get("email"));
    profile.put("id",attributes.get("id"));
    return OAuthAttributes.builder()
            .name((String)profile.get("nickname"))
            .email((String)profile.get("email"))
            .picture((String)profile.get("profile_image_url"))
            .attributes(profile) //user 정보 map
            .nameAttributeKey(userNameAttributeName) // user정보map의 pk필드 (카카오 = id)
            .build();
}

* index.mustache 에 카카오 로그인 버튼 추가 

<!-- 스프링시큐리티에서 기본 제공하는 oauth2 로그인url  -->
<a href="/oauth2/authorization/kakao" class="btn btn-warning active" role="button">kakao Login</a>

* 로그인테스트 

정상로그인 확인.

Comments