복붙노트

[SPRING] Spring MVC AccessDeniedException @PreAuthorized unauth 요청에 대한 사용자 지정 401 오류 대신 500 오류가 수신되었습니다.

SPRING

Spring MVC AccessDeniedException @PreAuthorized unauth 요청에 대한 사용자 지정 401 오류 대신 500 오류가 수신되었습니다.

프론트 엔드 장치 (웹 사이트, 모바일 앱 등)와 데이터베이스 사이에 앉아있을 Java Spring MVC 4 REST 앱을 작성 중입니다. 각 요청 (REST는 상태 비 저장이므로)마다 새 세션을 만들고 아래의 코드를 사용하여 요청의 Authorization 헤더를 살펴보고 토큰이 유효하고 요청이 인증되었는지 확인합니다.

사용자가 유효한 토큰을 사용하지 않고 보안 방법을 요청하면 500 액세스 거부 메시지의 권한이없는 요청을 401 Unauthorized 메시지로 리디렉션하려고합니다.

이것은 내가 지금까지 가지고있는 것이다.

AccessDeniedHandler :

public class Unauthorized401AccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException)
            throws IOException, ServletException {

        response.setStatus(401);
    }
}

WebSecurityConfigurerAdapter :

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new Unauthorized401AccessDeniedHandler());

    }
}

필터:

public class SecurityFilter implements Filter {
    final static Logger logger = Logger.getLogger(SecurityFilter.class);

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        HttpSession session = request.getSession();

        String requestUri = request.getRequestURI();

        session.invalidate();
        SecurityContextHolder.clearContext();

        session = request.getSession(true); // create a new session
        SecurityContext ctx = SecurityContextHolder.createEmptyContext();


        boolean isLoggedIn = false;

        String token = null;
        String authorizationHeader = request.getHeader("authorization");
        if(authorizationHeader != null && authorizationHeader.startsWith("bearer")) {
            String encryptedToken = authorizationHeader.split(" ")[1];
            token = StringUtils.newStringUtf8(Base64.decodeBase64(encryptedToken));

            // confirm user is logged in and authorized here TBD

            isLoggedIn = true;
        }

        PreAuthenticatedAuthenticationToken authentication = null;
        if(isLoggedIn) {
            SessionCredentialsModel authRequestModel = new SessionCredentialsModel();
            authRequestModel.employeeId = 323;
            authRequestModel.firstName = "Danny";
            authRequestModel.lastName = "Boy";
            authRequestModel.token = "this_is_a_test_token";

            authentication = new PreAuthenticatedAuthenticationToken(authRequestModel, token);
        } else {
            authentication = new PreAuthenticatedAuthenticationToken(new SessionCredentialsModel(), null);
        }

        authentication.setAuthenticated(true);
        ctx.setAuthentication(authentication);

        SecurityContextHolder.setContext(ctx);
        chain.doFilter(req, res);
    }

보안 모델 (보안 컨텍스트 주체라고도 함) :

public class SessionCredentialsModel {
    public int employeeId;
    public String firstName;
    public String lastName;
    public String token;

    public boolean isAuthenticated() {
        if(employeeId > 0 && token != null) {
            return true;
        }

        return false;
    }
}

그리고 마지막으로 컨트롤러 :

  @RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize("principal.isAuthenticated()")
    public ResponseEntity<LoginResponseModel> create() {
        LoginResponseModel responseModel = new LoginResponseModel();
        responseModel.statusCode = 55;
        responseModel.token = "authorized model worked!";

        return new ResponseEntity<LoginResponseModel>(responseModel, HttpStatus.OK);
    }

Authorization 헤더없이이 메소드를 실행할 때 (얻으려고하는 오류 대신)이 오류가 발생합니다.

HTTP Status 500 - Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied

type Exception report

message Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    net.pacificentertainment.middletier.app.security.SecurityFilter.doFilter(SecurityFilter.java:73)
root cause

org.springframework.security.access.AccessDeniedException: Access is denied
    org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
    org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
    org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
    net.pacificentertainment.middletier.app.controllers.EmployeeController$$EnhancerBySpringCGLIB$$b6765b64.create(<generated>)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    java.lang.reflect.Method.invoke(Method.java:498)
    org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    net.pacificentertainment.middletier.app.security.SecurityFilter.doFilter(SecurityFilter.java:73)
note The full stack trace of the root cause is available in the Apache Tomcat/8.0.43 logs.

Apache Tomcat/8.0.43

나는 왜 내가 401이 아닌 다른 상태 코드 또는 500이 아닌 다른 상태 코드를 반환하는 권한이없는 요청을 얻을 수 없는지를 알아 내는데 실패했습니다.

어떻게 생각해?

해결법

  1. ==============================

    1.OK 사람들은 커뮤니티에서 도움을 얻을 수 없었지만 해결책을 찾았습니다. 직접적인 해결책은 아니지만.

    OK 사람들은 커뮤니티에서 도움을 얻을 수 없었지만 해결책을 찾았습니다. 직접적인 해결책은 아니지만.

    @ControllerAdvice
    public class SecurityExceptionHandler extends ResponseEntityExceptionHandler {
    
        @ExceptionHandler({AccessDeniedException.class})
        public ResponseEntity<Object> handleAccessDeniedException(Exception ex, WebRequest request) {
            if(ex.getMessage().toLowerCase().indexOf("access is denied") > -1) {
                return new ResponseEntity<Object>("Unauthorized Access", new HttpHeaders(), HttpStatus.UNAUTHORIZED);
            }
    
            return new ResponseEntity<Object>(ex.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    

    내 응용 프로그램에있는이 새 파일을 통해 예외 상황을 제어 할 수 있습니다. 이제는 문제를 수동으로 검사하여 "액세스가 거부되었습니다"라는 메시지를 확인한 다음 401이 작동하는지 확인합니다. 위의 문제는 401로 리디렉션되는 코드가 적중되지 않았 음을 의미합니다. 이 코드는 실행됩니다.

    다시 말하지만, 스프링 MVC의 다른 부분을 조작하고 기본 동작을 해킹하는 것과 같은 방식으로 작동시키기 때문에 이것은 직접적인 해결책이 아닙니다.

    누구든지 좀 더 우아한 해결책을 가지고 있다면 게시하십시오.

  2. ==============================

    2.스프링 부트를 사용하지 않는다고 가정합니다. springSecurityFilterChain을 컨테이너에 등록하지 않았습니까?

    스프링 부트를 사용하지 않는다고 가정합니다. springSecurityFilterChain을 컨테이너에 등록하지 않았습니까?

    스택 트레이스는 호출 된 스프링 보안 필터를 보여주지 않습니다. 사용자 지정 AccessDeniedHandler는 springSecurityFilterChain에있는 ExceptionTranslationFilter에서 호출됩니다. 하지만 귀하의 메소드 보안 요격기가 어디서나 잡히지 않는 AccessDeniedException을 던지고있는 것처럼 보입니다.

    참조 가이드를 확인하십시오.

  3. from https://stackoverflow.com/questions/43554489/spring-mvc-accessdeniedexception-500-error-received-instead-of-custom-401-error by cc-by-sa and MIT license