728x90
반응형
[스프링 시큐리티] 6. 기본 API 및 Filter 이해(인증/인가 예외 처리 -ExceptionTranslationFilter, RequestCacheAwareFilter)
해당 포스팅은 인프런에서 스프링 시큐리티 정수원님의 강의를 참고하여 작성했습니다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의
초급에서 중.고급에 이르기까지 스프링 시큐리티의 기본 개념부터 API 사용법과 내부 아키텍처를 학습하게 되고 이를 바탕으로 실전 프로젝트를 완성해 나감으로써 스프링 시큐리티의 인증과
www.inflearn.com
인증과 인가

사용자가 서버에 접근하기 위해서는 먼저 인증을 받습니다.
여기서 인증된 사용자가 아닐 경우 예외를 처리해야합니다.
예를 들어 “아이디 혹은 비밀번호를 확인해 주세요”
와 같은 예외 처리 처럼요.
인증이 완료된 사용자는 권한이 존재하게 됩니다.
권한에 따라서 인가된 자원만이 접근 가능합니다.
만약 해당 자원에 접근 권한이 없는 사용자는 “회원님은 해당 메뉴를 선택할 수 없습니다.”
같은 예외처리를 해야합니다.
스프링 시큐리티에서 인증과 인가에 대한 예외처리를 어떻게 할 수 있는지 알아봅시다.
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 |