[개인 프로젝트] 스프링 시큐리티(Spring Security), OAuth2 네이버 카카오 로그인 API 사용하기
my code archive
article thumbnail
반응형

🤍스프링 시큐리티(Spring Security)란

  • 스프링 기반의 애플리케이션의 인증, 인가 등을 담당하는 스프링 하위 프레임워크
  • 인증과 권한에 대한 부분을 Filter 흐름에 따라 처리함
  • 보안에 대해 체계적으로 다양한 옵션을 제공해주므로 개발자가 보안 관련 로직을 작성하지 않아도 됨.

스프링 시큐리티, SNS 로그인 구현하기

1. build.gradle 관련 의존성 추가

    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation('org.springframework.session:spring-session-jdbc:')

    testImplementation 'org.springframework.security:spring-security-test'

2. SecurityConfig 작성

  • WebSecurityConfigurerAdapter를 상속받는 클래스
  • @EnableWebSecurity 어노테이션 -> SpringSecurityFilterChain 자동 포함

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

            http.csrf().disable()
                    .headers().frameOptions().disable()
                    .and()
//                    .authorizeRequests(
//                            authz->authz.antMatchers("/","/css/**","/js/**")
//                                    .permitAll()
//                                    .antMatchers("/musical/**").hasRole(Role.MEMBER.name())
//                                    .anyRequest()
//                                    .authenticated().and()
//                    )
                    .formLogin()
                    .loginPage("/login.html")
                    .defaultSuccessUrl("/index.html",true)
                    .and()
                    .logout()
                    .logoutSuccessUrl("/")
                    .and()
                    .oauth2Login()
                    .userInfoEndpoint()
                    .userService(customOAuth2UserService);
    }
}

 

2. Enum 클래스 Role 작성

@Getter
@RequiredArgsConstructor
public enum Role {

    //스프링 시큐리티에서는 권한 코드에 항상 ROLE_이 앞에 있어야만 한다.
    GUEST("ROLE_GUEST","손님"),
    MEMBER("ROLE_MEMBER","회원");

    private final String key;
    private final String title;
}

 

3. 네이버 로그인 API 등록

4. 카카오 로그인 API 등록

 

5. application.properties / application-oauth.properties 작성

6. OAuth2 관련 클래스 작성

  • SessionUser

@Getter
public class SessionUser implements Serializable {

    private String name;
    private String email;

    public SessionUser(Member member){
        this.name=member.getName();
        this.email=member.getEmail();
    }
}
  • OAuthAttributes
@Getter
public class OAuthAttributes {

    private Map<String,Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;

    @Builder
    public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email){
        this.attributes = attributes;
        this.nameAttributeKey = nameAttributeKey;
        this.name = name;
        this.email = email;
    }

    //OAuth2User에서 반환하는 사용자 정보가 Map이기 때문에 값 하나하나 변환 필요.
    public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes){
        if("naver".equals(registrationId)){
            return ofNaver("id",attributes);
        }

        return ofKakao(userNameAttributeName, attributes);
    }

    public static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes){

        Map<String, Object> response = (Map<String, Object>) attributes.get("kakao_account");

        return OAuthAttributes.builder()
                .name((String)attributes.get("nickname"))
                .email((String)attributes.get("email"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes){
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");

        return OAuthAttributes.builder()
                .name((String)attributes.get("name"))
                .email((String)attributes.get("email"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public Member toEntity() {
        return Member.builder()
                .name(name).email(email).role(Role.GUEST)   //가입 시 기본 권한을 GUEST로 주겠다.
                .build();
    }
}
  • CustomOAuth2UserService

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest,OAuth2User> {

    private final MemberRepository memberRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        //현재 로그인 진행 W중인 서비스를 구분하는 코드
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        //PK와 같은 의미
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        Member member = saveOrUpdate(attributes);

        httpSession.setAttribute("member", new SessionUser(member));
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());
    }

    private Member saveOrUpdate(OAuthAttributes attributes) {

        Member member = memberRepository.findByEmail(attributes.getEmail())
                .orElse(attributes.toEntity());

        return memberRepository.save(member);
    }
}

 

7. 회원 관련 MemberController / MemberRepository / Member (도메인) 작성

  • Member

@Getter
@NoArgsConstructor
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mberId;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;

    @Builder
    public Member(Long mberId, String name, String email, Role role){
        this.mberId = mberId;
        this.name = name;
        this.email = email;
        this.role = role;
    }

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

@Controller
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/member")
public class MemberController {

    @GetMapping("login")
    public String loginMember(){
        return "member/login";
    }
}
  • MemberRepository

import com.jungahzzzang.musicalcommunity.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member,Long> {

    Optional<Member> findByEmail(String email);
}

8. 로그인 화면 login.html 작성

9. 네이버 로그인 & 카카오 로그인 버튼은 각 개발자 센터에서 다운받아 사용할 수 있다.

  • 네이버 개발자 센터

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

 

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

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

developers.naver.com

 

  • 카카오 개발자 센터

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

 

Kakao Developers

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

developers.kakao.com

 

10. 구현 완료

 

반응형
profile

my code archive

@얼레벌레 개발자👩‍💻

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

반응형