사용자가 로그인을 하지않아 Exception 발생 경우 처리 방법
by 볼빵빵오춘기페이지를 만들다보면 사용자가 로그인한 경우에만 들어갈 수 있는 페이지가 있다.
예를 들면, 게시판에 글을 쓰는데 로그인한 사용자만 글을 쓸 수 있는 경우
이런 경우 처리를 해줘야할 것 같은데 단순하게 생각해서 아래와 같이 코드를 작성하였다.
@GetMapping("/communityW")
public String writeForm(Model model,@AuthenticationPrincipal PrincipalDetails principalDetails){
if(principalDetails.getUsername()==null || principalDetails==null){
model.addAttribute("message", "로그인한 회원만 이용가능합니다.");
model.addAttribute("searchUrl", "/loginForm");
}
return "community/write";
}
이렇게 적다보니 이런 경우의 페이지가 한 두 개면 저것도 나쁘지않은 것 같긴한데 라고 생각했는데
생각보다 많다. 나쁘지않다가 아니라 나쁘다.
토이 프로젝트를 해보면서 페이지도 몇 개없고 저런 페이지가 얼마나 되겠어 했는데
게시판 하나에 글 쓰기 페이지, 글 수정 페이지, 글 삭제 페이지에 들어간다 생각하면
내가 하는 토이 프로젝트에 게시판이 3개니깐 그러면 저런 코드를 9번 반복하게 된다.
어후 이 부분이 한 번에 완벽하게 작동하고 프로젝트에서 클라이언트가 컨펌해주고 끝나면 좋겠지만
그럴 경우가 얼마나 된다고..
자 그러면 사용자가 로그인해야만 사용할 수 있는 페이지를 어떻게 처리하면 좋을까?
사용자가 로그인을 하지않아 Exception 발생 경우 처리 방법
사전에 검사하기
- 컨트롤러 메서드 실행 전에 principalDetails.getUsername()이 null인지 확인하고, null일 경우에 대응하는 로직을 추가한다.
- 컨트롤러에서 사용자 정보를 검사하고 필요한 경우 예외를 방지할 수 있다.
- 이 로직은 컨트롤러마다 중복될 수 있으므로, 중복을 줄이고 유지보수성을 높이기 위해 AOP(Aspect-Oriented Programming)와 같은 기술을 활용하여 중복 코드를 추상화하는 방법도 고려할 수 있다.
@GetMapping("/updateForm")
public String updateForm(int id, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
if (principalDetails == null || principalDetails.getUsername() == null) {
// 사용자 정보가 없는 경우 처리할 로직 (예: 로그인 페이지로 리다이렉트)
return "redirect:/loginForm";
}
// 사용자 정보가 있는 경우 정상적인 처리
// ...
}
Service 계층에서 처리하기
Controller는 단순히 요청을 받고 반환하는 역할을 하므로, principalDetails.getUsername()이 필요한 비즈니스 로직이나 데이터 접근은 Service 계층으로 이동하여 처리한다.
// Controller
@GetMapping("/updateForm")
public String updateForm(int id, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
userService.updateForm(id, model, principalDetails);
return "updateForm";
}
// Service
public void updateForm(int id, Model model, PrincipalDetails principalDetails) {
if (principalDetails == null || principalDetails.getUsername() == null) {
// 사용자 정보가 없는 경우 처리할 로직
throw new IllegalArgumentException("사용자 정보 없음!!.");
}
// 사용자 정보가 있는 경우 정상적인 처리
// ...
}
Spring Security의 Access Control 설정
- Spring Security를 이용하여 접근 제어를 설정하고, 접근 권한에 따라 자동으로 처리되도록 설정할 수도 있다.
- 이 경우, 컨트롤러에서 사용자 정보가 없는 경우가 발생하지 않도록 할 수 있다.
Spring Security의 Access Control 설정하기
로그인하지 않은 상태로 보호된 페이지에 접근하거나, 권한이 없는 상태로 특정 페이지에 접근했을 때 처리하는 방법을 설정할 수 있다.
이를 위해 HttpSecurity의 exceptionHandling 메서드를 사용하여 커스텀 접근 거부 페이지를 설정한다.
SecurityConfig 설정 코드 추가
1. exceptionHandling
- authenticationEntryPoint는 인증되지 않은 사용자가 보호된 페이지에 접근할 때 처리하는 메서드이다.
/loginForm?error=true로 리다이렉트하도록 설정했다. - accessDeniedHandler는 권한이 없는 사용자가 특정 페이지에 접근할 때 처리하는 메서드이다.
/accessDenied로 리다이렉트하도록 설정했다.
2. authenticationEntryPoint 메서드
- AuthenticationEntryPoint를 구현하여 인증되지 않은 사용자가 접근할 때의 동작을 정의한다.
- 로그인 페이지로 리다이렉트하며, URL에 error=true를 추가하여 오류 메시지를 표시할 수 있다.
3. accessDeniedHandler 메서드
- AccessDeniedHandler를 구현하여 권한이 없는 사용자가 접근할 때의 동작을 정의한다.
- 접근 거부 페이지로 리다이렉트한다.
✅ 이렇게 설정하면 보호된 페이지에 접근했을 때, 로그인 페이지로 리다이렉트하거나 권한이 없을 때 접근 거부 페이지로 리다이렉트하도록 할 수 있습니다. 이를 통해 사용자의 접근 권한을 보다 세밀하게 관리할 수 있다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ..
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin(formLogin -> formLogin
.loginPage("/loginForm")
.loginProcessingUrl("/login")
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler()))
.authorizeRequests(authorize -> authorize
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll())
.oauth2Login(oauth2 -> oauth2
.loginPage("/loginForm")
.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
.userService(principalOauth2UserService))
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler()))
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.accessDeniedHandler(new CustomAccessDeniedHandler()))
.csrf().disable()
.cors().and();
}
@Bean
public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
return new CustomAuthenticationEntryPoint();
}
@Bean
public CustomAccessDeniedHandler customAccessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
// ..
}
CustomAuthenticationEntryPoint 구현 (401 처리)
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendRedirect("/accessDenied");
}
}
CustomAccessDeniedHandler 구현 (403 처리)
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect("/accessDenied2");
}
}
accessDenied.html & accessDenied2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Access Denied</title>
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<div class="container">
<h1>접근 거부 (401)</h1>
<p>죄송합니다. 해당 페이지에 접근할 권한이 없습니다.</p>
<p>다시 로그인하려면 <a th:href="@{/loginForm}">여기</a>를 클릭하세요.</p>
<p>홈페이지로 돌아가려면 <a th:href="@{/}">여기</a>를 클릭하세요.</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Access Denied</title>
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<div class="container">
<h1>접근 거부 (403)</h1>
<p>죄송합니다. 해당 페이지에 접근할 권한이 없습니다.</p>
<p>다시 로그인하려면 <a th:href="@{/loginForm}">여기</a>를 클릭하세요.</p>
<p>홈페이지로 돌아가려면 <a th:href="@{/}">여기</a>를 클릭하세요.</p>
</div>
</body>
</html>
참고링크
https://yoo-dev.tistory.com/28
https://mangkyu.tistory.com/146
'👩🏻💻 About 프로그래밍 > spring' 카테고리의 다른 글
Spring Data Jpa 쿼리 메소드, 네이밍 규칙, 네이티브 쿼리 (0) | 2024.07.15 |
---|---|
JPA란? (feat. ORM) (0) | 2024.07.15 |
권한 설정(feat. @PreAuthorize, @PostAuthorize) (0) | 2024.07.11 |
스프링 시큐리티 SecurityConfig 설정(스프링부트 2.x.x vs 3.x.x) (0) | 2024.07.10 |
스프링 시큐리티(Spring Security) vs 전통적인 방식 (0) | 2024.07.10 |
블로그의 정보
Hello 춘기's world
볼빵빵오춘기