-
[프로젝트 리뷰] Spring Security 로그인 후 이전 페이지로 이동 - Referer, addFlashAttributeFrameWork/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
해결 순서
- 로그인 시, 이전 페이지에 대한 url을 Session에 저장해준다.
- 로그인 성공 시, Session에 저장한 url을 사용하여 이전 페이지로 돌아간다.
- 로컬에서는 위 방법을 사용하여 문제가 없었으나 배포해보니 권한이 필요한 페이지에서 로그인 시 오류가 났다.
- 배포 서버에서는 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로 전달해 주었다.
처음에 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
'FrameWork > Spring' 카테고리의 다른 글
[프로젝트 리뷰] Spring Security OAuth2 소셜 로그인(구글, 네이버, 카카오) 구현 (0) 2023.02.06