728x90
반응형
[스프링 시큐리티] 6. 기본 API 및 Filter 이해(인증/인가 예외 처리 -ExceptionTranslationFilter, RequestCacheAwareFilter)
해당 포스팅은 인프런에서 스프링 시큐리티 정수원님의 강의를 참고하여 작성했습니다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의
인증과 인가
사용자가 서버에 접근하기 위해서는 먼저 인증을 받습니다.
여기서 인증된 사용자가 아닐 경우 예외를 처리해야합니다.
예를 들어 “아이디 혹은 비밀번호를 확인해 주세요”
와 같은 예외 처리 처럼요.
인증이 완료된 사용자는 권한이 존재하게 됩니다.
권한에 따라서 인가된 자원만이 접근 가능합니다.
만약 해당 자원에 접근 권한이 없는 사용자는 “회원님은 해당 메뉴를 선택할 수 없습니다.”
같은 예외처리를 해야합니다.
스프링 시큐리티에서 인증과 인가에 대한 예외처리를 어떻게 할 수 있는지 알아봅시다.
ExceptionTranslationFilter
AuthenticationException
,AccessDeniedException
를 호출하는 필터 입니다.
AuthenticationException
인증 예외 처리 클래스 입니다.
AuthenticationEntryPoint
(인터페이스)를 호출- 로그인 페이지 이동 또는 401 전달
- 인증 예외가 발생하기 전의 요청 정보를 저장합니다.
RequestCache
- 사용자 이전 요청 정보를 세션에 저장하고 이를 꺼내오는 캐시 메커니즘 입니다.
SavedRequest
- 사용자가 요청했던
request
파라미터 값들, 그 당시의 헤더값 등이 저장됩니다.
- 사용자가 요청했던
AccessDeniedException
- 인가 예외 처리
AccessDeniedHandler
에서 예외 처리
인증, 인가 처리 과정
- 부가설명
- fully authenticated 정책을 설정한 상황에서
remember-me 인증 객체가 생성 되었다면 AccessDeniedHandler로 이동하지 않고 AuthenticationException으로 이동 합니다. - remember-me 인증은
fully authenticated를 의미하지는 않기 때문입니다.- fully authenticated는 로그인 행위에 있어
id, password와 같은 신원증명을 할 수 있는것을 의미합니다.
- fully authenticated는 로그인 행위에 있어
- remember-me의 토큰이
만료 되었을 경우에도 인증 예외가 발생합니다.
- fully authenticated 정책을 설정한 상황에서
실습
MyAccessDeniedHandler
인증 예외 처리
/**
* 인증 예외 처리
*/
@Slf4j
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
String encode = URLEncoder.encode(authException.getMessage(), StandardCharsets.UTF_8);
response.sendRedirect("/login?exception="+encode);
}
}
MyAuthenticationEntryPoint
인가 예외 처리
/**
* 인가 예외 처리
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
String encode = URLEncoder.encode(accessDeniedException.getMessage(), StandardCharsets.UTF_8);
response.sendRedirect("/denied?exception="+encode);
}
}
LoginSuccessHandler
/**
* 로그인 성공 후 처리
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
// 사용자 이전 요청 정보를 세션에 저장하고 이를 꺼내오는 캐시
private final RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 사용자 이전 요청 정보 조회
SavedRequest savedRequest = requestCache.getRequest(request, response);
if(savedRequest != null) {
// 사용자 이전 요청 url로 redirect
response.sendRedirect(savedRequest.getRedirectUrl());
} else {
response.sendRedirect("/"); // root page로 이동
}
}
}
MySecurityConfig
@EnableWebSecurity
@RequiredArgsConstructor
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
private final MyAuthenticationEntryPoint myAuthenticationEntryPoint;
private final MyAccessDeniedHandler myAccessDeniedHandler;
private final LoginSuccessHandler loginSuccessHandler;
/**
* 권한 설정
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 메모리 방식
auth.inMemoryAuthentication().withUser("user").password("{noop}123").roles("USER");
auth.inMemoryAuthentication().withUser("manager").password("{noop}123").roles("MANAGER");
auth.inMemoryAuthentication().withUser("admin").password("{noop}123").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/user").hasRole("USER")
.antMatchers("/manager").hasRole("MANAGER")
.antMatchers("/admin-manager").access("hasRole('ADMIN') or hasRole('MANAGER')")
.anyRequest().authenticated();
http
.formLogin()
.successHandler(loginSuccessHandler); // 로그인 성공 후 로직
http
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint) // 인증 예외
.accessDeniedHandler(myAccessDeniedHandler); // 인가 예외
http
.csrf().disable();
}
}
MyController
@Controller
public class MyController {
@ResponseBody
@GetMapping("/user")
public String user() {
return "user";
}
@ResponseBody
@GetMapping("/manager")
public String manager() {
return "manager";
}
@ResponseBody
@GetMapping("/admin-manager")
public String adminManager() {
return "admin-manager";
}
@ResponseBody
@GetMapping("/denied")
public String denied(@RequestParam(value = "exception", required = false) String exception) {
return exception;
}
@GetMapping("/login")
public String login(@RequestParam(value = "exception", required = false) String exception,
Model model) {
model.addAttribute("exception", exception);
return "login";
}
@ResponseBody
@GetMapping("/hello")
public String hello() {
return "hello! 사용자 이전 요청 페이지";
}
@ResponseBody
@GetMapping("/")
public String root() {
return "root page";
}
}
login.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please sign in</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css"
rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" action="/login">
<h2 class="form-signin-heading">Please sign in</h2>
<h2 th:if="${exception != null}" th:text="${exception}" style="color: red"></h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<!-- <input name="_csrf" type="hidden" value="825947ce-e260-42a1-8395-bd08a01dcc72" />-->
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body></html>
결과
GET localhost:8080/hello
RequestCache
에 해당 정보 저장
- 인증 예외 발생
localhost:8080/login?exception=Full+authentication+is+required+to+access+this+resource
user
계정으로 로그인 후LoginSuccessHandler
에서RequestCache
를 이용해서localhost:8080/hello
로redirect
GET localhost:8080/manager
- 인가 예외 발생
GET localhost:8080/denied?exception=Access+is+denied
user
는manager
권한이 아님
728x90
반응형
'0+ 스프링 > 0+ 스프링 Security' 카테고리의 다른 글
[스프링 시큐리티] 7. 기본 API 및 Filter 이해(CSRF(사이트간 요청 위조)) (0) | 2023.01.15 |
---|---|
[스프링 시큐리티] 5. 기본 API 및 Filter 이해(권한(인가) 설정) (0) | 2023.01.09 |
[스프링 시큐리티] 4. 기본 API 및 Filter 이해(동시 세션 제어/세션 고정 보호/세션 정책) (0) | 2023.01.02 |
[스프링 시큐리티] 3. 기본 API 및 Filter 이해(익명사용자 인증 처리 - AnonymousAuthenticationFilter) (0) | 2023.01.01 |
[스프링 시큐리티] 2. 기본 API 및 Filter 이해(Remember-me + JSESSIONID) (0) | 2022.12.25 |