본문 바로가기

SpringSecurity

SpringSecurity(AccessDenied) 인증거부처리

반응형

인증은 성공했으나 서버자원에 접근하려 했을때 사용자의 권한이 서버자원에 접근가능한 권한이랑 일치하지 않을때 인가예외가 발생하게되는데 인가예외는 인증필터가 처리 하는게 아니고 FilterSecurityInterceptor 에서 인가 예외를 처리한다.

 

AbstractSecurityInterceptor.java

인가예외가 발생하게 되면 이필터가 AccessDeniedException에서 예외를 받아서 다시 예외를 throw 하게 되는데 이 예외를 어디서 받냐면

 

ExceptionTranslationFilter.java method= handleSpringSecurityException

AccessDeniedException을 받아서 accessDeniedHandler를 호출한이후에 예외를 처리 하게되어 있다 중요한건 인증시도중에 발생하는

인증예외는 인증예외처리하는 필터가 받아서 처리를 하고 인가예외는 인가예외를 처리하는 필터에서 받아서 처리한다 생각해보면 그래서 인증에 성공하고나서 인증객체를 새로 만드는데 아마 인증,인가예외가 따로 처리 되어서 그렇지 않나 싶다 처음에 인증 시도할때 SpringSecurity가 인증객체를 만들긴 하지만 그안에는 인가정보는 없다 이후에 인증이 성공하면 객체를 만들어서 다시 저장하는데 그때보면 사용자의 권한정보가 담겨 있는걸 확인할수 있다 아마 따로따로 처리 하다보니 객체를 두번 만드는거 같다 이건내생각

 

accessDeniedHandler 구현체를 만들어서 설정클래스에다가 설정하게되면 SpringSecurity가 만들어준 accessDeniedHandler를 호출해서

 관련된 작업들을 처리 할수 있다.

 

 

 

 

AccessDeniedHandler 인터페이스를 구현해보겠돠~ 

메소드의 인자값을 보면 accessDeniedException(인가예외) 가 파라메터로 전달되고 있다. 그럼 여기에데가 무엇을 설정하냐!

사용자가 인증성공 이후에 접근할수 없는 페이지에 접근을 하고자 한다면 에러페이지를 전달해줄것이다!

 

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
#에러 페이지 exception="인가예외메세지"
String deniedUrl = errorPage + "?exception=" + accessDeniedException.getMessage();
response.sendRedirect(deniedUrl);
}


private String errorPage;

    public void setErrorPage(String errorPage) {
        this.errorPage = errorPage;
    }

 

경로를 받는 컨트롤러 url 매핑을 해줘야 한다

 

  @GetMapping("/denied")
  #위에설정에 쿼리 스트링으로 "exception" 을 전달하고 있다.RequestParam으로 받고 필수값이 아니도록 설정해준다
    public String accessDenied(@RequestParam(value = "exception",required = false)String exception,
                                Model model){
       
       #현재 사용자의 이름과 예외메세지를 보여주기 위해서 우선 사용자의 정보를 가져와야 한다
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
       
       #이전에 Account트로 인증객체를 저장했다 
       Account account =(Account) authentication.getPrincipal();
       
       #모델에 담아서 사용자의 이름과 예외 메세지를 화면에 전달해줄것이다.
        model.addAttribute("username",account.getUsername());
        model.addAttribute("exception",exception);
	
        return"user/login/denied";

 

 

설정페이지에서 설정해주면 되는데 혹시나 나처럼 API가 확인이 안되는 경우가 있다 그리고 아마 SecurityBuilder 가 확인될텐데 

일단 API를 한번 보면

FormLoginConfigurer<HttpSecurity>가 리턴되는걸 확인할수 있다 여기서 문제는 authenticationDetailsSource는 이놈인데 

이놈은 FormLoginConfigurer가 리턴되는걸 확인할수 있다.

이둘의 차이점은 api가 실행되고 나서 리터되는 객체가 HttpSecurity타입으로 되어야 하는데 authenticationDetailsSource은 제네릭이 적용되지 않아서 최상위 인터페이스인 SecurityBulider 타입의 객체가 리턴되어서 발생하는 문제이다. 

 

그래서 해결방법은 이렇다 처음에 @Autowired되어 있던 

 

밑에 사진 처럼 변경해주면된다.  

그러면 정사적으로 HttpSecurity타입의 객체가 반환되는걸 확인할수 있다.

 

일반적으로 동일한 인터페이스 타입으로 동일한 이름을 가진 빈을 두개 이상 선언하지 않으면 오류가 발생하지 않기 때문에 AuthenticationDetailsSource로 선언해도 상관없고 실제로 확인해보면 FormAuthenticationDetailsSource타입의 객체로 DI되고 있다. 

 

이거 때문에 엄청 해맷다 혼자서 해결하고 싶었지만 결국엔 나와같은 문제를 가진 사람의 글을 보고 해결하긴 했다 ㅜ 근데 왜 AuthenticationDetailsSource 햇을경우에 <HttpSecurity> 가 아닌 FormLoginConfigurer 로 적용되어서 최종 리턴되는지는 잘 모르겠다

 

이렇게 되면 코드작성은 끝낫고 한번 와스를 구동해보자 에러메시지가 잘가는지 디버깅을 통해서 확인 해보자 

오잉? 이게 무슨에러인가.. ㅋㅋ 에러 내용을 보면 "Bean" 설정된 부분의 메소드가 private , final 이면 안된다 라는 것을 알려준다.

요놈이다 구현했던 CustomAccessDeniedHandler를 이용해서 에러페이지로 이동할수 있게 @Bean으로 설정 설정해줬지만 보면 private이다. public 으로 바꿔주자! 단순히 접근제한 때문인가? 라고 생각이 들어서 스프링 문서를 찾아봤다 

 

스프링 API문서

일반적으로 @Bean메서드는 @Configuration 클래스 내에서 선언됩니다 . 이 경우 빈 메소드는 직접@Bean 호출하여 동일한 클래스의 다른 메소드를 참조 할 수 있습니다 . 이렇게하면 빈 간의 참조가 강력하게 입력되고 탐색 가능합니다. 이러한 소위 '빈 간 참조'  getBean()조회 와 마찬가지로 범위 지정 및 AOP 의미 체계를 존중 합니다. 이들은 런타임시 이러한 각 구성 클래스의 CGLIB 서브 클래 싱이 필요한 원래 'Spring JavaConfig'프로젝트에서 알려진 의미 체계입니다. 결과적으로 @Configuration클래스와 해당 팩토리 메서드는이 모드에서 최종 또는 비공개로 표시되지 않아야합니다.

 

 @Configuration도 한번 보자 

스프링의 @Configuration 어노테이션은 어노테이션기반 환경구성을 돕는다. 이 어노테이션을 구현함으로써 클래스가 하나 이상의 @Bean 메소드를 제공하고 스프링 컨테이가 Bean정의를 생성하고 런타임시 그 Bean들이 요청들을 처리할 것을 선언하게 된다.

 

@Bean
    public AccessDeniedHandler accessDeniedHandler() {
        CustomAccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler();
        accessDeniedHandler.setErrorPage("/denied");
        return accessDeniedHandler;
    }

 

 

현재 나는 로그인에 성공했고 ROLE_USER 의 권한을 가진 아이디가 Admin 권한을 가저야 접근이 가능한 자원으로 접근을 요청하고 있다 

확인호변 deniedUrl에서 예외메세지를 전달해주는게 확인된다 

요청하는 사요자의 권한도 확인이 가능하다

 

 

 

반응형