본문 바로가기
PROGRAMING📚/Spring📑

Spring : 구글 로그인 세션 만들기

Ta이니 2024. 9. 25.
728x90
반응형

구글 로그인 세션 만들기

https://developers.google.com/identity/sign-in/web/sign-in?hl=ko


OAuth 클라이언트 ID 만들기

http://localhost:8080/ex7/login/oauth2/code/google

 

 

JSON 다운로드

#OAuth2 include
spring.profiles.include = oauth

 

💡 **My Additional information**

spring.security.oauth2.client.registration.google.client-id=776858494674-fvo383dker0jierot3pjciiaon2lluqn.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.registration.google.scope=email

src/main/resources/application-oauth.properties File 생성하기

    //소셜 로그인
    httpSecurity.oauth2Login(new Customizer<OAuth2LoginConfigurer<HttpSecurity>>() {
      @Override
      public void customize(OAuth2LoginConfigurer<HttpSecurity> httpSecurityOAuth2LoginConfigurer) {
        httpSecurityOAuth2LoginConfigurer.successHandler(new AuthenticationSuccessHandler() {
          @Override
          public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            response.sendRedirect(request.getContextPath()+"/sample/all");
          }
        });
      }
    });
@Log4j2
@Service
public class ClubOAuth2userDetailsService extends DefaultOAuth2UserService {

  @Override
  public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//    String clientName = userRequest.getClientRegistration().getClientName();
//    log.info("ClientName >> ",clientName);
//    log.info("info_from_google >> ", userRequest.getAdditionalParameters());
//    OAuth2User oAuth2User = super.loadUser(userRequest);
//    oAuth2User.getAttributes().forEach((k,v) ->{log.info(k+":"+v);});

    OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
    OAuth2User oAuth2User = delegate.loadUser(userRequest);  //서비스에서 가져온 유저 정보를 담음
    return super.loadUser(userRequest);
  }
   private SocialType getSocialType(String registrationId){
    if(SocialType.NAVER.name().equals(registrationId)){
      return SocialType.NAVER;
    }
    if(SocialType.KAKAO.name().equals(registrationId)){
      return SocialType.KAKAO;
    }
    return SocialType.GOOGLE;
  }
  private  enum SocialType{
    KAKAO,NAVER,GOOGLE
  }
}

자바bean이 되어야 와이어링이 되고 전체에서 사용이 가능함

@Log4j2
@Service
@RequiredArgsConstructor
public class ClubOAuth2userDetailsService extends DefaultOAuth2UserService {
  private final ClubMemberRepository clubMemberRepository;
  private final PasswordEncoder passwordEncoder; //

  @Override
  public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    log.info("============= userRequest: " + userRequest);
    OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate =
        new DefaultOAuth2UserService();
    OAuth2User oAuth2User = delegate.loadUser(userRequest); //서비스에서 가져온 유저정보
    String registrationId = userRequest.getClientRegistration().getRegistrationId();
    SocialType socialType = getSocialType(registrationId.trim().toString());
    String userNameAttributeName = userRequest.getClientRegistration()
        .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
    log.info("userNameAttributeName >> " + userNameAttributeName);
    Map<String, Object> attributes = oAuth2User.getAttributes();
    for (Map.Entry<String, Object> entry : attributes.entrySet()) {
      System.out.println(entry.getKey() + ":" + entry.getValue());
    }
    String email = null;
    if (socialType.name().equals("GOOGLE"))
      email = oAuth2User.getAttribute("email");
    log.info("Email: " + email);
    ClubMember clubMember = saveSocialMember(email);
    ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
        clubMember.getEmail(),
        clubMember.getPassword(),
        clubMember.getCno(),
        true,
        clubMember.getRoleSet().stream().map(
                role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
            .collect(Collectors.toList())
        , attributes
    );
    clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
    clubMemberAuthDTO.setName(clubMember.getName());
    log.info("clubMemberAuthDTO: " + clubMemberAuthDTO);
    return clubMemberAuthDTO;
  }

OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService(); 는 소셜에서 가져온 유저정보(userRequest)를 담기 위한 객체(OAuth2User )를 생성 해주는 것을 의미한다

OAuth2UserService 는 social 로 부터 정보를 받기 위한 객체를 생성한다
OAuth2User 는 스프링시큐리티에서 사용하는 세션 객체를 의미함
OAuth2User loadUser(OAuth2UserRequest userRequest)

→ userRequest는 소셜 페이지에서 로그인하고 그 정보를 빌려오는 것을 의미함
유저 정보(userRequest)를 세션 객체(oAuth2User )로 변환
OAuth2User oAuth2User = delegate.loadUser(userRequest);

 private ClubMember saveSocialMember(String email) {
    Optional<ClubMember> result = clubMemberRepository.findByEmail(email);
    if (result.isPresent()) return result.get();

    ClubMember clubMember = ClubMember.builder()
        .email(email)
        .password(passwordEncoder.encode("1"))
        .fromSocial(true)
        .build();
    clubMember.addMemberRole(ClubMemberRole.USER);
    clubMemberRepository.save(clubMember);
    return clubMember;
  }

소셜에서 넘어온 정보가 DB에 없을 때 저장하는 부분

 private SocialType getSocialType(String registrationId) {
    if (SocialType.NAVER.name().equals(registrationId)) {
      return SocialType.NAVER;
    }
    if (SocialType.KAKAO.name().equals(registrationId)) {
      return SocialType.KAKAO;
    }
    return SocialType.GOOGLE;
  }
  enum SocialType {
    KAKAO, NAVER, GOOGLE
  }
}

로그인버튼 누르고 로그인 성공시

  @Bean
  protected SecurityFilterChain config(HttpSecurity httpSecurity)
      throws Exception {
    // csrf 사용안하는 설정
    httpSecurity.csrf(httpSecurityCsrfConfigurer -> {
      httpSecurityCsrfConfigurer.disable();
    });

    // authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
    // 모든 페이지가 인증을 받도록 되어 있는 상태
    // httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
    httpSecurity.authorizeHttpRequests(
        auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
            .requestMatchers("/sample/admin/**").hasRole("ADMIN")
            .requestMatchers("/sample/manager/**").access(
                new WebExpressionAuthorizationManager(
                    "hasRole('ADMIN') or hasRole('MANAGER')")
            )
            .anyRequest().authenticated());

    // formLogin()를 정의해야만 해도 자동생성된 로그인페이지로 이동가능.
    httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
      @Override
      public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {
        httpSecurityFormLoginConfigurer
//            .loginPage("/sample/login")
//            .loginProcessingUrl("/sample/login")
//            .defaultSuccessUrl("/")
            .successHandler(getAuthenticationSuccessHandler());
      }
    });
    // logout() 정의 안해도 로그아웃 페이지 사용 가능. 사용자 로그아웃 페이지 지정할 때사용
    httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
      @Override
      public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
        httpSecurityLogoutConfigurer
            // logoutUrl() 설정할 경우 html action 주소 또한 같이 적용해야 함.
            // logoutUrl()으로 인해 기존 logout 주소 이동은 가능하나 기능은 사용 안됨.
            .logoutUrl("/logout")
            .logoutSuccessUrl("/") // 로그아웃 후에 돌아갈 페이지 설정
            //.logoutSuccessHandler((request, response, authentication) -> {
            // logout 후에 개별적으로 여러가지 상황에 대하여 적용 가능한 설정
            //})
            .invalidateHttpSession(true); // 서버 세션을 무효화, false도 클라이언트측 무효화
      }
    });
    httpSecurity.oauth2Login(new Customizer<OAuth2LoginConfigurer<HttpSecurity>>() {
      @Override
      public void customize(OAuth2LoginConfigurer<HttpSecurity> httpSecurityOAuth2LoginConfigurer) {
        httpSecurityOAuth2LoginConfigurer.successHandler(getAuthenticationSuccessHandler());
      }
    });
    return httpSecurity.build();
  }

AuthenticationSuccessHandler 는 로그인 되었을 때, 처리하는 객체

 @Bean
  public AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
    return new AuthenticationSuccessHandler() {
      @Override
      public void onAuthenticationSuccess(HttpServletRequest request,
                                          HttpServletResponse response,
                                          Authentication authentication)
          throws IOException, ServletException {
        UserDetails principal = (UserDetails) authentication.getPrincipal();
        Collection<GrantedAuthority> authors =
            (Collection<GrantedAuthority>) principal.getAuthorities();
        List<String> result = authors.stream().map(new Function<GrantedAuthority, String>() {
          @Override
          public String apply(GrantedAuthority grantedAuthority) {
            return grantedAuthority.getAuthority();
          }
        }).collect(Collectors.toList());
        System.out.println(">>" + result.toString());
        for (int i = 0; i < result.size(); i++) {
          if (result.get(i).equals("ROLE_ADMIN")) {
            response.sendRedirect(request.getContextPath() + "/sample/admin");
          } else if (result.get(i).equals("ROLE_MANAGER")) {
            response.sendRedirect(request.getContextPath() + "/sample/manager");
          } else {
            response.sendRedirect(request.getContextPath() + "/sample/all");
          }
          break;
        }
      }
    };
  }

로그인 실패시 , 띄워주는 페이지 생성하기

public class CustomAccessDeniedHandler implements AccessDeniedHandler {
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    response.sendRedirect(request.getContextPath()+"/auth/accessDenied");
  }
}
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    response.sendRedirect(request.getContextPath()+"/auth/authenticationFailure");
  }
}
@Bean
  protected SecurityFilterChain config(HttpSecurity httpSecurity)
      throws Exception {
    httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
           httpSecurityCsrfConfigurer.disable();
    });
    return httpSecurity.build();
  }
  @Bean
  public AuthenticationSuccessHandler getAuthenticationSuccessHandler()
  {
    return new CustomLoginSuccessHandler();
  };
  @Bean
  public AccessDeniedHandler getAccessDeniedHandler(){
    return  new CustomAccessDeniedHandler();
  }
  @Bean
  public AuthenticationFailureHandler getAuthenticationFailureHandler(){
    return  new CustomAuthenticationFailuerHandler();
  }
  @Bean LogoutSuccessHandler getLogoutSuccessHandler(){
    return new CustomLogoutSuccessHandler();
  }
}

404Error 처리

 

//@RestControllerAdvice
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
  // 404 exception
  @ExceptionHandler(NoResourceFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public String handl404(NoResourceFoundException e, Model model) {
    model.addAttribute("errorMessage", e.getMessage());
    return "/error/notFound";
  }

  // Database error
  @ExceptionHandler(DataAccessException.class)
  public String handleDataAccessException(DataAccessException e, Model model) {
    model.addAttribute("errorMessage", e.getMessage());
    e.printStackTrace();
    return "error/databaseError";
  }

  // internal server error
  @ExceptionHandler(HttpServerErrorException.InternalServerError.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public String handleException(HttpServerErrorException.InternalServerError e, Model model) {
    model.addAttribute("errorMessage", e.getMessage());
    e.printStackTrace();
    return "error/serverError";
  }
}

.requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll() 해당하는 페이지가 들어왔을때 어떻게 해줄것인가에 대한 보안에 대한 설정

// authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
    // 모든 페이지가 인증을 받도록 되어 있는 상태
    // httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
    httpSecurity.authorizeHttpRequests(
        auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
            .requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll()
            .requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll()
            .requestMatchers("/sample/admin/**").hasRole("ADMIN")
            .requestMatchers("/sample/manager/**").access(
                new WebExpressionAuthorizationManager(
                    "hasRole('ADMIN') or hasRole('MANAGER')")
            )
            .anyRequest().authenticated());

database 아이디를 통하여 로그인하는 경우 사용, 소셜로그인은 사용불가

 httpSecurity.rememberMe(new Customizer<RememberMeConfigurer<HttpSecurity>>() {
      @Override
      public void customize(RememberMeConfigurer<HttpSecurity> httpSecurityRememberMeConfigurer) {
        httpSecurityRememberMeConfigurer.tokenValiditySeconds(60*60*24*7);
      }
    });

 

로그인하고 새창을 열었을 때, 로그인 하지 않아도 로그인 됨

 

728x90
반응형

댓글