[Spring]스프링 시큐리티 소셜로그인 처리

Big One·2021년 12월 20일
1

spring

목록 보기
2/2
post-thumbnail

Spring Boot와 Spring Security 연동.

· 스프링 시큐리티 로그인 처리방식의 이해
· JPA와 연동 커스텀 로그인 처리
· 로그인 정보 활용하기

우선 프로젝트를 생성하겠습니다.



프로젝트를 진행하여 이용할 의존성을 추가해줍니다.
저는 mariaDB를 이용하겠습니다. build.gradle 파일에 의존성을 추가합니다.

implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.3'

application.properties 파일에는 JPA관련 설정과 DB정보를 추가하고, 시큐리티 로그를 자세히 볼 수 있도록 설정합니다.

#DB 설정
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/married
spring.datasource.username=root
spring.datasource.password=root

#JPA 관련 설정
spring.profiles.include=oauth 
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true


logging.level.org.springframework.security.web = debug

이후 확인차 작동이 잘 되는지 프로젝트를 실행시켜 확인해봅니다.

Using generated security password: ef~~ 
출력되는 것이 확인된다면 정상 동작하는겁니다.

시큐리티가 기본적으로 제공해주는 로그인 사이트가 있으므로 별도의 로그인 페이지 없이 진행하겠습니다.

시큐리티 설정을 하기위해 클래스를 작성하겠습니다. 설정을 하기위해 'WebSecurityConfigurerAdapter' 라는 클래스를 상속받아 override를 통해 설정을 조정합니다.

노란색 형관펜 색칠 되어있는 config, controller 폴더를 생성한 후 각 폴더에 빨간 밑줄로 된 SecurityConfig, SecurityController 클래스를 생성해줍니다.

구글 소셜로그인 만들기
-> 클릭!

새프로젝트 생성 -> 프로젝트 이름 입력 후 생성




맨 밑 개발자 연락처 정보에도 이메일 주소를 적어 준 후 계속 저장 후 계속 버튼을 눌러줍니다. 그 후 사용자 인증정보 -> 사용자 인증정보 만들기 -> OAuth 클라이언트 ID

만들기 누르면

이와같이 클라이언트 ID와 클라이언트 보안비밀 코드가 생성됩니다.

application-oauth.properties 파일 추가하신 후 생성한 클라이언트ID 와 보안비밀코드를 를 적어줍니다.

spring.security.oauth2.client.registration.google.client-id=클라이언트ID
spring.security.oauth2.client.registration.google.client-secret=클라이언트 보안 비밀
spring.security.oauth2.client.registration.google.scope=email

이제 정상동작을 확인하기위해 SecurityController 클래스를 간단하게 작성하겠습니다.

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SecurityController {

    @GetMapping("/all")
    public void guest(){
        System.out.println("guest 접속");
    }

    @GetMapping("/member")
    public void member() {
        System.out.println("member 접속!");
    }

    @GetMapping("/admin")
    public void admin(){
        System.out.println("admin 접속!");
    }
}

확인을 위한 controller 에서 3개의 메서드를 작성하였습니다.
ex) 주소창에 ~/all이 호출되면 콘솔창에 guest 접속이라는 문구가 출력.

member, admin, all 호출되는 페이지마다 권한을 주어야합니다. 방법은 여러가지가 있지만 @PreAuthorize 어노테이션을 사용하여 지정하겠습니다.
위 작성 코드에 각 @PreAuthorize("permitAll()"), @PreAuthorize("hasRole('MEMBER')"), @PreAuthorize("hasRole('ADMIN')") 추가해줍니다.

그 후 entity, dto, repository, .. 작성하겠습니다.

@Service
@Log4j2
@RequiredArgsConstructor
public class MemberService{

    private final MemberRepository memberRepository;

    private final PasswordEncoder passwordEncoder;

    /*
    회원 가입
     */
    @Transactional
    public String register(MemberDTO memberDTO) {
        Random random = new Random();
        random.setSeed(System.currentTimeMillis());

        Member member = Member.builder()
                .email(memberDTO.getEmail())
                .password(passwordEncoder.encode(memberDTO.getPassword()))
                .name(memberDTO.getName())
                .fromSocial(false)
                .build();
        member.addMemberRole(MarriedMemberRole.MEMBER);

        memberRepository.save(member);
        log.info(memberDTO +"가입");

        return member.getEmail();
    }

    @Override
    public String authUpdate(MemberDTO memberDTO) {
        memberRepository.findByEmail(memberDTO.getEmail()); // findByEmpId로 변경 -> empId 로 조회 쿼리 생성
        return null;
    }

@Service
@Log4j2
@RequiredArgsConstructor
public class MemberDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        log.info("loadUserByUsername : " + username);

        Optional<Member> result = memberRepository.findByEmail(username, false);

        if (result.isPresent()==false) {
            throw new UsernameNotFoundException("Email을 확인해주세요.");
        }
        Member member = result.get();

//        log.info(member);

        MemberAuthDTO memberAuthDTO = new MemberAuthDTO(member.getEmail(), member.getPassword(),
                member.isFromSocial(), member.getRoleSet().stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())).collect(Collectors.toSet()));

        memberAuthDTO.setName(member.getName());
        memberAuthDTO.setFromSocial(member.isFromSocial());

        return memberAuthDTO;
    }
}


@Log4j2
@Service
@RequiredArgsConstructor
public class MemberOAuth2UserDetailsService extends DefaultOAuth2UserService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

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


        log.info("----------------------------");
        log.info("userRequest : " + userRequest);

        String clientName = userRequest.getClientRegistration().getClientName();
        log.info("clientName : " + clientName);
        log.info(userRequest.getAdditionalParameters());

        OAuth2User oAuth2User = super.loadUser(userRequest);
        log.info("============================");

        String email = null;

        if (clientName.equals("Google")){
            email = oAuth2User.getAttribute("email");
        }

        log.info("EMAIL : " + email);
        
        Member member = registerSocialMember(oAuth2User);
        MemberAuthDTO memberAuthDTO = new MemberAuthDTO(
                member.getEmail(),
                member.getPassword(),
                true,
                member.getRoleSet().stream().map(
                        role -> new SimpleGrantedAuthority("ROLE_" + role.name())
                ).collect(Collectors.toList()),
                oAuth2User.getAttributes()
        );
        memberAuthDTO.setName(member.getName());

        System.out.println("=======================================================================");
        System.out.println("=======================================================================");

        System.out.println(oAuth2User.getAttributes());
        for (String key : oAuth2User.getAttributes().keySet()){
            System.out.println("key : " + key + ", value : " + oAuth2User.getAttributes().get(key));
        }

        System.out.println("=======================================================================");
        System.out.println("=======================================================================");

        return memberAuthDTO;
    }

    private Member registerSocialMember(OAuth2User oAuth2User) {
        Optional<Member> result = memberRepository.findByEmail(oAuth2User.getAttribute("email"), true);

        if (result.isPresent()) return result.get();

        Member member = Member.builder()
                .email(oAuth2User.getAttribute("email"))
//                .name(oAuth2User.getAttribute("name"))
//                .age(oAuth2User.getAttribute("age"))
                .fromSocial(true)
//                .password(passwordEncoder.encode(oAuth2User.getAttribute("password")))
                .password(passwordEncoder.encode("1234"))
                .build();

        member.addMemberRole(MarriedMemberRole.MEMBER);

        memberRepository.save(member);

        return member;
    }
}

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

    @Query("select m from Member m where m.email=:email and m.fromSocial=:social")
    Optional<Member> findByEmail(String email, boolean social);
}

public enum MarriedMemberRole {

    USER, MEMBER, ADMIN;

}

@Entity
@ToString
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Member {

    @Id
    @Column(unique = true)
    private String email;

    private String password;

    private String empId;

    private String name;

    private boolean fromSocial;

    @ElementCollection(fetch = FetchType.EAGER)
    @Builder.Default
    @Enumerated(EnumType.STRING)
    private Set<MarriedMemberRole> roleSet = new HashSet<>();

    public void addMemberRole(MarriedMemberRole marriedMemberRole) {
        System.out.println("print!!!: " + marriedMemberRole);
        roleSet.add(marriedMemberRole);
    }

}

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MemberDTO {

    private String email;

    private String password;

    private String empId;

    private String name;

    private boolean fromSocial;

}

@Getter @Setter
@ToString
@Log4j2
public class MemberAuthDTO extends User implements OAuth2User {

    private String email;

    private String name;

    private boolean fromSocial;

    private Map<String, Object> attr;

    public MemberAuthDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);

        this.email = username;
        this.fromSocial = fromSocial;
    }

    public MemberAuthDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities, Map<String, Object> attr) {
        this(username, password, fromSocial, authorities);
        this.attr = attr;
    }

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

@Configuration
@Log4j2
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MemberDetailsService memberDetailsService;


    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

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

        http.formLogin();// LoginPage 따로 있을경우 LoginUrl 사용 
        http.csrf().disable(); // csrf 계속 생성 개발자 도구에서 다 보여서 생성안하게 disable
        http.oauth2Login().successHandler(successHandler()); //소셜로그인(1)
        http.logout();  // logout 페이지 있으면 logoutUrl
        http.rememberMe().tokenValiditySeconds(60 * 60 * 7).userDetailsService(memberDetailsService);
    }

    @Bean
    public MarriedLoginSuccessHandler successHandler() {
        return new MarriedLoginSuccessHandler(passwordEncoder());
    }

}


위와같이 소셜로그인 하여도 회원가입이 되는것을 확인할 수 있습니다.

profile
이번생은 개발자

0개의 댓글

관련 채용 정보