ABOUT ME

96년생 컴공

Today
-
Yesterday
-
Total
-
  • [프로젝트 리뷰] Spring Security 로그인 후 이전 페이지로 이동 - Referer, addFlashAttribute
    FrameWork/Spring 2023. 2. 7. 12:18

    개발환경

    • STS 4.16.1
    • Springboot 3.0.1 (springsecurity6)
    • Thymeleaf
    • Java 17
    • JPA
    • Gradle 7.6

     

    관련 포스팅  -  소셜 로그인 구현

     

    [프로젝트 리뷰] Spring Security OAuth2 소셜 로그인(구글, 네이버, 카카오) 구현

    개발환경 STS 4.16.1 Springboot 3.0.1 (springsecurity6) Thymeleaf Java 17 JPA Gradle 7.6 사전설정 네이버 / 구글 / 카카오 OAuth2 API 설정은 따로 기술하지 않습니다. Gradle 의존성 추가 build.gradle dependencies{ implementation

    rkgh17.tistory.com

     

    전체 Repository

     

    GitHub - rkgh17/SeoulWalk: 휴먼 스프링부트 프로젝트

    휴먼 스프링부트 프로젝트. Contribute to rkgh17/SeoulWalk development by creating an account on GitHub.

    github.com

     


    해결 순서

    1. 로그인 시, 이전 페이지에 대한 url을 Session에 저장해준다.
    2. 로그인 성공 시, Session에 저장한 url을 사용하여 이전 페이지로 돌아간다.
    3. 로컬에서는 위 방법을 사용하여 문제가 없었으나 배포해보니 권한이 필요한 페이지에서 로그인 시 오류가 났다.
    4. 배포 서버에서는 url이 계속 null값으로 나옴 -> addFlashAttribute를 사용해 url을 전달해주었다.

    권한이 필요한 페이지에서의 로그인

     

    위 화면에서 질문등록 버튼을 누를 시 아래와 같은 흐름으로 진행됨.

     


     

    QuestionController.java

    현재 사용자의 세션이 없으면 로그인 하지 않은 유저이므로, 이전 url값을 컨트롤러에 전달해 주어야 한다.

     

    import org.springframework.http.HttpStatus;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpSession;
    import jakarta.validation.Valid;
    import lombok.RequiredArgsConstructor;
    
    @RequiredArgsConstructor
    @RequestMapping("board")
    @Controller
    public class QuestionController {
    
    	/*생략*/
        
    	// 질문 등록 메서드 
    	@GetMapping("qna/create")
    	public String questionCreate(QuestionForm questionForm, HttpServletRequest request, RedirectAttributes re) {
        
    		if(userService.getSession() == null) {
            
    			// 회원이 아닐 시, 이전 uri를 flashattribute로 보내준다
    			String uri = request.getHeader("Referer");
    			re.addFlashAttribute("referer",uri);
    			return "redirect:/user/login";
    		}else {
    			return "bbs/bbsQnaForm";
    		}
        }
    
    	/*생략*/
        
    }

     

    addFlashAttribute  

    스프링에서 객체를 전달할 때 사용하는 방법 중 하나.

    전달할 때 String으로 단순 문자열로 전달 할 수도 있는데, 여기서는 Object로 전달해 주었다.

    RedirectAttributes 공식문서

     

    처음에 model.addAttribute를 사용하였는데, 로그인 화면으로 이동시에 url에 이전 페이지 url이 표시되었기 때문에 보기에 좋지 않았다....

    addFlashAttribute는 표시되지 않았기에 사용하였다.

     


     

    UserController.java

    전달받은 url을 세션에 넣어준다.

    import java.util.Map;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.support.RequestContextUtils;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    
    @Controller
    @RequestMapping("user")
    public class UserController {
    	
    	@GetMapping("login")
    	public String login(HttpServletRequest request) {
        
    		/* 로그인 성공 시 이전 페이지로 이동 */
    		String uri = request.getHeader("Referer");
    		
    		// 이전 uri가 null이다 -> 배포 서버에서 나타나는 오류?
    		if (uri==null) {
    			// null일시 이전 페이지에서 addFlashAttribute로 보내준 uri을 저장
    			Map<String, ?> paramMap = RequestContextUtils.getInputFlashMap(request);
    			uri = (String) paramMap.get("referer");
    			
    			// 이전 url 정보 담기
    			request.getSession().setAttribute("prevPage", uri);
    
    		}else {
    			// 이전 url 정보 담기
    			request.getSession().setAttribute("prevPage", uri);
    		}	
    		
    		return "login";
    	}
    	
    	@GetMapping("logout")
    	public String logoutpage() {
    		
    		return "logout";
    	}
    	
    	@GetMapping("logout/do")
    	public String logout(HttpServletRequest request, HttpServletResponse response) throws Exception{
    		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    		
    		if (auth != null) {
    			new SecurityContextLogoutHandler().logout(request, response, auth);
    		}
    			return "redirect:/main";
    	}
    
    }

    권한이 필요한 페이지에서 온 경우, 이전 컨트롤러에서 전달받은 url을 Session의 prevPage attribute로 저장해준다.

     

    권한이 필요하지 않은 페이지에서의 로그인도 마찬가지로 이전 url을 전달해준다.

     

    다시한번 강조하지만, 권한이 필요한 페이지에서의 로그인 요청 시 request.getHeader("Referer")값이 null로 나왔기 때문에 이러한 방식을 사용한 거지, 더 좋은 방법이 분명 있을것이다!!
    더 근본적인 해결책을 찾아 포스팅하는 그날까지..

     


     

    CustomAuthSuccessHandler.java

    로그인 성공 시 동작하는 SimpleUrlAuthenticationSuccessHandler를 상속받아 구현

     

    SimpleUrlAuthenticationSuccessHandler 공식문서

     

    import java.io.IOException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.DefaultRedirectStrategy;
    import org.springframework.security.web.RedirectStrategy;
    import org.springframework.security.web.WebAttributes;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
    import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
    import org.springframework.security.web.savedrequest.RequestCache;
    import org.springframework.security.web.savedrequest.SavedRequest;
    import org.springframework.stereotype.Component;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.servlet.http.HttpSession;
    
    @Component
    public class CustomAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
    
        private final RequestCache requestCache = new HttpSessionRequestCache();
        private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();	
    	
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException {
        	
        	// 이전 url로 redirect
        	String prevPage = (String) request.getSession().getAttribute("prevPage");
        	
        	redirectStrategy.sendRedirect(request, response, prevPage);
        }
    
    }

    로그인 성공 시 onAuthenticationSuccess 메서드 동작

    Session에 넣어주었던 prevPage attribute를 받아서 redirectStrategy를 통해 로그인 성공 후 결정된 url로 redirect한다.

     

    소셜 로그인으로 인가를 처리하고 있기에, 로그인 실패에 대한 상황은 고려하지 않았다.

     


     

    SecurityConfig.java

    스프링 시큐리티 설정

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import com.human.seoulroad.user.CustomOAuth2UserService;
    import com.human.seoulroad.user.CustomAuthSuccessHandler;
    import lombok.RequiredArgsConstructor;
    
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig{
    	
    	@Autowired
    	private CustomOAuth2UserService customOAuth2UserService;
    
    	@Bean
    	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    			http.csrf().disable();
    		
    			http.authorizeHttpRequests().requestMatchers(
    				new AntPathRequestMatcher("/**")).permitAll()
            
    				// h2콘솔관련
    				.and()
    					.csrf().ignoringRequestMatchers(
    						new AntPathRequestMatcher("/h2-console/**"))
    				.and()
    					.headers()
    					.addHeaderWriter(new XFrameOptionsHeaderWriter(
    							XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
                    	
    				// 로그인/로그아웃 관련
    				.and()
    					.logout()
    					.logoutUrl("/user/logout")
    					.logoutSuccessUrl("/")
    					.invalidateHttpSession(true)
    				.and()
    					.oauth2Login()
    					//.defaultSuccessUrl("/")
    					.successHandler(new CustomAuthSuccessHandler()) // 반드시 추가!!
    					.userInfoEndpoint()
    					.userService(customOAuth2UserService)
    				;
    		return http.build();
        }
    
    }

     

     


    생각해볼 점

     

     

    세션 탈취해서 로그인하기(세션 하이재킹) feat. 쿠키와 세션 실습

    HttpSession session = request.getSession(); 자바에서 클라이언트의 세션을 얻기 위해 위의 코드를 많이 사용한다. 세션이 존재하지 않으면 초기에는 WAS 컨테이너에서 Session Id를 발급해준다. 그리고 Session

    stir.tistory.com

     

    댓글

Designed by Tistory.