-
[Spring Boot] 시큐리티 회원가입시 추가정보 미기입 상태 분기처리SpringBoot Meteor 2021. 12. 23. 14:56
개발을 하면서 기존 시큐리티 OAuth2.0을 이용하여 카카오 로그인을 하고 추가 정보를 입력하는거 까지
회원 가입 완료 스텝으로 보는 상황이었다.
그런데 문제점이 로그인 상태가 먼저 되기때문에 추가 정보를 기입하지않고 다른 페이지로 이동하면
로그인 처리가 완료되어 인가 상태가 되어버렸기 때문에 추가적인 로직이 필요했다.
처음에는 AOP를 이용하여 처리를 하려했으나 모든 회원(추가 정보 입력도 완료)이 모든 요청시마다 해당 로직을 타는게 맞는지에 대한
얘기가 나왔고 다른 해결방법으로 처음에 가입할 때 주던 ROLE_USER권한을 ROLE_TEMP라는 권한타입을 하나 추가하고
추가 가입까지 완료 되어야 ROLE_USER권한을 주었다.
기존 시큐리티 설정
@RequiredArgsConstructor @EnableWebSecurity @Order(1) // 생략해도 상관없음 login을 2가지 운영하려고 걸어놓은 어노테이션 //@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final PrincipalOauth2UserService principalOauth2UserService; private final MemberRepository memberRepository; private final CustomLogoutHandler customLogoutHandler; @Bean public BCryptPasswordEncoder encoderPwd() { return new BCryptPasswordEncoder(); } @Override public void configure(WebSecurity web) { web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); } @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("유저"); http.csrf().disable(); http.authorizeRequests() .antMatchers("/v1/member/survey/**", "/v1/api/member/**", "/v1/question/write", "/v1/question/update/**", "/v1/mypage/**" ).authenticated() .antMatchers("/v1/api/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MASTER')") .antMatchers("/v1/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MASTER')") .anyRequest().permitAll() .and() .exceptionHandling()// 권한 거부 핸들링 .accessDeniedPage("/v1/techno/denied") .and() .logout() .logoutUrl("/v1/member/logout") .logoutSuccessHandler(customLogoutHandler) // custom logouthandler .and() .oauth2Login() .loginPage("/v1/member/login") .successHandler(successHandler()) // custom successhandler .userInfoEndpoint() .userService(principalOauth2UserService); } @Bean public AuthenticationSuccessHandler successHandler() { return new CustomLoginSuccessHandler(memberRepository); } }
기존에는 인증만 되면 모든 경로에 접근이 가능 수정후에는 ROLE_USER 권한이 있어야지만 접근 가능
수정 후 시큐리티 설정
@RequiredArgsConstructor @EnableWebSecurity @Order(1) //@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final PrincipalOauth2UserService principalOauth2UserService; private final MemberRepository memberRepository; private final CustomLogoutHandler customLogoutHandler; @Bean public BCryptPasswordEncoder encoderPwd() { return new BCryptPasswordEncoder(); } @Override public void configure(WebSecurity web) { web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); } @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("유저"); http.csrf().disable(); http.authorizeRequests() .antMatchers("/v1/member/survey/**", "/v1/api/member/**" ).authenticated() .antMatchers("/v1/question/write", "/v1/question/update/**", "/v1/mypage/**", "/v1/member/measure", "/v1/member/measureNameEnrollment") .access("hasRole('ROLE_USER')") .antMatchers("/v1/api/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MASTER')") .antMatchers("/v1/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MASTER')") .anyRequest().permitAll() .and() .exceptionHandling()// 권한 거부 핸들링 .accessDeniedPage("/v1/techno/denied") .and() .logout() .logoutUrl("/v1/member/logout") .logoutSuccessHandler(customLogoutHandler) .and() .oauth2Login() .loginPage("/v1/member/login") .successHandler(successHandler()) .userInfoEndpoint() .userService(principalOauth2UserService); } @Bean public AuthenticationSuccessHandler successHandler() { return new CustomLoginSuccessHandler(memberRepository); } }
.antMatchers("/v1/member/survey/**", "/v1/api/member/**" ).authenticated() .antMatchers("/v1/question/write", "/v1/question/update/**", "/v1/mypage/**", "/v1/member/measure", "/v1/member/measureNameEnrollment") .access("hasRole('ROLE_USER')") // 추가 회원정보 경로까지는 인증만되면 접근이 가능하게해주었고 기존에 인증만 요구하던 설정을 // 권한설정으로 바꾸고 ROLE_USER 권한을 가져야 접근이 가능하게 수정
수정 전 OAuth 로그인 처리 서비스 PrincipalOauth2UserService.java
@Service @RequiredArgsConstructor public class PrincipalOauth2UserService extends DefaultOAuth2UserService { private final MemberRepository memberRepository; /** * OAuth2MemberInfo oAuth2MemberInfo property Structure * {id=**********, 카카오톡 고유 ID 번호 * connected_at=2021-11-29T08:31:25Z, * properties={ * nickname=Moon 닉네임 * }, * kakao_account={ 카카오 어카운트 * profile_nickname_needs_agreement=false, * profile_image_needs_agreement=true, * profile={nickname=Moon}, 프로필 * has_email=true, 이메일 소유 여부 * email_needs_agreement=false, * is_email_valid=true, * is_email_verified=true, * email=tjdans345@naver.com 회원 이메일 * } * } * * @param userRequest OAuth 카카오에서 던져주는 파라미터 * @return * @throws OAuth2AuthenticationException */ @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); OAuth2MemberInfo oAuth2MemberInfo = null; if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")) { oAuth2MemberInfo = new KakaoMemberInfo(oAuth2User.getAttributes()); } if (oAuth2MemberInfo != null) { String kakaoInherenceid = oAuth2MemberInfo.getInherenceid(); String nickname = oAuth2MemberInfo.getNickname(); String email = oAuth2MemberInfo.getEmail(); Optional<MemberEntity> optional = memberRepository.findByInherenceId(kakaoInherenceid); MemberEntity memberEntity; if(optional.isEmpty()) { memberEntity = memberRepository.save( MemberEntity.builder() .inherenceId(kakaoInherenceid) .email(email) .state(UserStateType.NORMAL) .role(UserRoleType.ROLE_TEMP) // 기존 ROLE_USER -> ROLE_TEMP로 변경 .joinCompleteYn("N") .phoneNumber("-") .build()); } else { memberEntity = optional.get(); } memberEntity.setNickname(nickname); // 본인 닉네임 중복 방지 코드 return new PrincipalDetails(memberEntity, oAuth2User.getAttributes(), userRequest.getAccessToken().getTokenValue()); } return null; } }
CustomSuccessHandler.java
/** * 시큐리티 OAuth 로그인 완료 처리 커스텀 핸들러 */ @Slf4j @Component public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private final MemberRepository memberRepository; @Autowired public CustomLoginSuccessHandler(MemberRepository memberRepository) { this.memberRepository = memberRepository; } /** * 로그인 완료 상태 후 분기처리 핸들러 * @param request request * @param response response * @param authentication 시큐리티 세션 회원 정보 객체 * @throws IOException IOException */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String redirectUrl; OAuth2User oAuth2User = (OAuth2User)authentication.getPrincipal(); String id = String.valueOf(oAuth2User.getAttributes().get("id")); Optional<MemberEntity> optional = memberRepository.findByInherenceId(id); if(optional.isEmpty()) { response.sendRedirect("/v1/member/logout"); } MemberEntity member = optional.get(); if(member.getState().equals(UserStateType.SANCTION)) { String message = URLEncoder.encode("이 아이디는 현재 이용정지 상태입니다.", StandardCharsets.UTF_8); redirectUrl = "/v1/member/sanctionRedirect?message="+message; } else { if (optional.get().getJoinCompleteYn().equals("N")) { redirectUrl = "/v1/member/survey"; } else { redirectUrl = "/v1/techno/index"; } } response.sendRedirect(redirectUrl); } }
이 후 추가정보기입을 건너뛰고 권한이 필요한 페이지에 접근 시 권한 거부를 하는데 그 때 권한 거부처리경로를 설정 해줌
.exceptionHandling()// 권한 거부 핸들링 .accessDeniedPage("/v1/techno/denied")
권한 거부 핸들링 로직
@GetMapping("/denied") public String deniedPage(RedirectAttributes redirectAttributes, @AuthenticationPrincipal PrincipalDetails principalDetails) { // 권한이 임시 회원일 시 추가정보 입력 페이지로 redirect한다. if(UserRoleType.ROLE_TEMP.equals(principalDetails.getMember().getRole())) { redirectAttributes.addFlashAttribute("message", "추가 정보 기입을 완료해주세요."); return "redirect:/v1/member/survey"; } return "index/index"; }
'SpringBoot Meteor' 카테고리의 다른 글
[Spring Boot] @RestControllerAdvice를 이용해서 예외 처리 핸들링하기 (0) 2021.12.29 [Spring Boot] JWT (JSON WEB TOKEN) 간단 정리 (0) 2021.12.23 [Spring Boot] Spring Security 시큐리티 권한 업데이트 시 동적 적용 (0) 2021.12.21 [Spring Boot] 쿼리 DSL을 이용한 서브쿼리 (0) 2021.12.17 [SpringBoot] 쿼리 DSL 동적 조건문 생성 (0) 2021.12.17