안녕하세요.
지난 시간에는 401(UnAuthorized)과 403(Forbidden) ExceptionHandler를 Spring Security에서 어떻게 처리하는지에 대해서 알아보았습니다.
이때 /auth 라는 Path에 대하여 인증과 권한(ROLE_AUTH)을 부여하여 성공적으로 스프링 시큐리티에서 인증하는 것을 볼 수 있었습니다.
https://sas-study.tistory.com/362
이번 포스팅에서는 인증된 객체를 사용할 경우가 많은데요. 예를 들어서, 게시판 글을 등록해야할 때, 로그인 객체를 사용하게 될 수 있습니다.
이러한 경우 유용하게 쓸 수 있는 어노테이션을 소개하고자 합니다.
@AuthenticationPrincipal 어노테이션
먼저 이 어노테이션을 활용하기 위해서 스프링 시큐리티 설정 클래스를 수정해야합니다.
@Configuration
//이부분 추가해주세요.
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
..이후 코드 동일
@EnableWebSecurity 어노테이션이 바로 @AuthenticationPrincipal 어노테이션을 통해 Authentication 객체 속에 들어있는 principal 필드를 가져올 수 있게 합니다. 이 부분에 대한 처리는 아래에서 설명드리겠습니다.
@EnableGlobalMethodSecurity 어노테이션은 부가적인 내용이지만 Controller 메소드에 직접적으로 Role을 부여할 수 있습니다.
지난 시간 SecurityConfig에
antMatchers("/api/v1/test/auth").hasRole("AUTH")
다음과 같이 Role을 설정하였는데, 이렇게 되면 설정파일이 굉장히 길어지게 됩니다.
따라서 이부분을 아래의 TestController 클래스의 getTest2() 메소드처럼 @Secured("ROLE_AUTH")를 이용하여 처리할 수 있게 됩니다.
TestController.java
@Secured("ROLE_AUTH")
@GetMapping("/auth")
public Object getTest2(@AuthenticationPrincipal SecurityUser securityUser)
throws Exception {
return testService.getTest2(securityUser);
}
즉 다음과 같이 Controller의 메소드 Parameter 영역에 @AuthenticationPrincipal을 선언하고 SecurityUser 타입 변수를 선언하게되면 스프링 시큐리티 내부 XXXProvider라는 객체에서 Authentication에 저장된 Principal 객체를 꺼내주게 됩니다.
헌데 이전시간에 제가 다르게 처리한 부분이 있어서 이를 수정하고 진행해야 합니다.!
SecurityAuthenticationFilter.java
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String username = "test";
UserDetails authentication = customUserDetailsService.loadUserByUsername(username);
//이 부분을 수정해주세요. -> authentication.getUsername() -> authentication
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(authentication, "test", authentication.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
filterChain.doFilter(request, response);
}
UsernamePasswordAuthenticationToken 객체는 principal과 credentials, authorities로 구분되어 있습니다.
이전에는 principal 부분에 UserDetails 객체의 username을 넣어놨었는데, 이 부분을 UserDetails 객체로 넣어놔야 @AuthenticationPrincipal 어노테이션이 가져오는 getPrincipal() 메소드를 호출할 때 SecurityUser로 가져오게 됩니다.
이제 결과를 확인해보겠습니다.
디버깅을 걸어 확인한 결과 @AuthenticationPrincipal 어노테이션을 설정한 SecurityUser 객체에 loadUserByUsername메소드에서 반환한 객체가 들어있음을 확인할 수 있습니다.
감사합니다.!!