소셜 로그인 흐름 정리

savannah030·2022년 4월 25일
0

넘블

목록 보기
4/4
post-thumbnail

카카오 로그인

1. 사용자가 로그인을 누르면 accessToken을 받아와서 서버에 넘긴다.

  • 카카오 javascript SDK의 Kakao.Auth.login 이용(프론트 팀원분께서 구현해주셨음!!)

2. AccessToken으로 카카오 서버에서 사용자 정보 가져오기

프론트에서 보내준 AccessToken으로 카카오 '사용자 정보 가져오기' API에 보낼 http 요청을 만든다
사용자 정보 가져오기 API 카카오 공식문서

요청

응답(예시)

주의할 점

WebSecurityConfig andMatchers/auth/login/kakao 등록해야 프론트에서 AccessToken을 받을 수 있음

.antMatchers("/api/auth/kakao-login/**").permitAll() 

내 계정으로 테스트해본 결과


이 정보를 우리 서버의 객체에 담는다.

3. 프론트에 회원가입 창으로 가야 하는지, 메인페이지로 가야하는지 알려주기

흐름

  • 카카오 서버에서 받은 사용자 정보(이메일)로 우리 DB에서 해당 이메일로 가입한 유저가 있는지 찾는다.
  • 없다면 이 사람은 회원가입을 해야 하므로 프론트에게 /step1으로 가야한다는 정보를 보낸다. (User 엔티티는 step2에서 받아온 정보로 만듦)
  • 있으면 이 사람은 로그인 요청을 한 것이므로, 로그인 처리를 하고 /(메인 페이지)로 가야 한다는 정보를 보낸다.
  • 이때, 프론트의 쿠키에 jwt 만들어 보낸다. 그러면 프론트의 헤더에 쿠키정보가 남기 때문에 요청마다 jwt를 보내지 않아도 된다.

쿠키 보내기

4-1. 회원가입

  • 프론트의 step2에서 받은 유저 정보로 User 엔티티를 만들어 저장한다.

4-2. 로그인/로그아웃 처리

로그인

로그인이 되어있다. = 1. access_token이 쿠키에 저장되어 있다 2. local storage에서도 user값이 저장되어있다

로그아웃

로그아웃이 됐다 = 1. cookie 삭제 2. local storage도 삭제

5. 모든 API에 대해 권한이 있는 사용자인지 확인

5-1. 스프링 시큐리티 필터가 매 요청마다 검사

매 요청마다 스프링 시큐리티 필터(jwtAuthenticationFilter) 가 토큰을 검사한다.
우리 프로젝트에서는 CorsFilter를 실행한 후에(프론트가 3000번 포트에서 요청하니까) 토큰 필터(jwtAuthenticationFilter)가 실행되도록 설정하였다.

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

	@Override
    protected void configure(HttpSecurity http) throws Exception {
    	
        ...
        // cors 설정(코드 생략)
		...
        
        // filter 등록
        // 매 요청마다
        // CorsFilter 실행한 후에
        // jwtAuthenticationFilter 실행한다
        http.addFilterAfter(
                jwtAuthenticationFilter,
                CorsFilter.class
        );

    }
}

5-2. JwtAuthenticationFilter 는 쿠키->토큰->인증된 이메일 순으로 변환하여 이메일을 세션에 넣는다.

ublic class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final TokenProvider tokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 1.
            String token = parseCookie(request);
            log.info("Filter is running... token: {}",token);
            
            if (token != null && !token.equalsIgnoreCase("null")) {
                // 2. jwt이 위조된 경우 예외처리
                String userEmail = tokenProvider.getEmailfromJwt(token);
                log.info("Authenticated user ID : " + userEmail );
                // 3.
                AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userEmail, // 인증된 사용자의 정보
                        null, //
                        AuthorityUtils.NO_AUTHORITIES
                );
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
               //4.
               SecurityContextHolder.getContext().setAuthentication(authentication); // 세션에서 계속 사용하기 위해 securityContext에 Authentication 등록
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    public String parseCookie(HttpServletRequest request){
        String bearerToken = request.getHeader("Set-Cookie");

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("access_token")) {
            return bearerToken.substring(13);
        }
        return null;
    }
  1. request에서 쿠키 중 access_token의 정보를 가져온다. (JwtAuthenticationFilter 클래스의 parseCookie 함수)

  2. 토큰 -> 이메일로 디코딩해 사용자 이메일을 가져온다.
    (디코딩하는 함수는 tokenProvider 클래스의 getEmailfromJwt 참고)
    이때 userEmail은 디코딩한 정보니까 인증된 정보이다!

    💥 즉, 웹 브라우저에서 요청한 사용자가 userEmail을 가진 User엔티티의 주인임을 인증한 것!!!

  3. 인증된 이메일을 SecurityContextHolder에 등록한다.

    💥 요청에 쿠키가 없으면, 즉 로그인한 사용자가 아니면, 세션에는 아무것도 등록되지 않는다.

5-3. 모든 컨트롤러의 함수마다 @AuthenticationPrincipal을 붙인다.

@AuthenticationPrincipal의 역할은 스프링이 SecurityContextHolder(4번)에서 authentication(3번, 인증된 이메일)을 가져와 컨트롤러의 userEmail에 넘겨주는 것이다.

예를 들어 게시판 생성 컨트롤러를 다음과 같이 짜면,

@Postmapping
public ResponseEntity<?> postBoard(@AuthenticationPrincipal String userEmail){
	if(userEmail==null) {
    	return new ResponseEntity<>(new Message(ReturnCode.PLEASE_LOGIN, "/login"), HttpStatus.OK);
    }
	userRepository.findByEmail(userEmail);
    userService.createBoard(userEmail);
    ...
}

userEmail은 쿠키(로그인할 때 생성)로부터 얻은 인증된 이메일, 즉 웹 브라우저에서 요청한 사용자가 userEmail을 가진 User엔티티의 주인임을 인증한 것이라고 하였다
인증이 됐으므로, 이제부터 이 인증된 userEmail을 갖고 컨트롤러, 서비스, 레포지토리 단에서 userEmail에 해당하는 User 엔티티를 마음껏 쓰면 된다!!

로그인된 상태가 아니면, 쿠키가 없으니까 userEmail을 받지 못할테고, 그럼 로그인하라고 리턴하면 된다!

지금 코드에선 다 구현된 상태!! 5번 기능 쓰려면 컨트롤러에 @AuthenticationPrincipal 만 붙이면 된당~0~

React.js, 스프링 부트, AWS로 배우는 웹 개발 101 책을 참고+제 생각을 정리하여 작성하였습니다.

profile
백견이불여일타

0개의 댓글