且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

预控制器过滤器的ExceptionHandler(Spring Security)

更新时间:2023-12-03 20:52:40

首先,应在 ExceptionTranslationFilter ,此过滤器将处理 AccessDeniedException AuthenticationException 捕获到它们。

  http.addFilterAfter(siteMinderFilter(),ExceptionTranslationFilter.class); 

接下来,您需要插入 AuthenticationEntryPoint 到处理 AuthenticationException ,已经有一些已经存在的 AuthenticationEntryPoint 您可以使用它,例如 HttpStatusEntryPoint LoginUrlAuthenticationEntryPoint ,...;



如果要返回JSON响应,则需要实现自己的 AuthenticationEntryPoint ,例如:

 公共类ExampleAuthenticationEntryPoint实现AuthenticationEntryPoint {

private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);

@Override
public void start(HttpServletRequest请求,HttpServletResponse响应,AuthenticationException authException)引发IOException,ServletException {
logger.error(authException.getMessage(),authException);
if(!response.isCommitted()){
Map< String,Object> responseMsg = new HashMap< String,Object>();
responseMsg.put( message,authException.getMessage());
ObjectMapper映射器= new ObjectMapper();
try {
response.setContentType( application / json; charset = utf-8);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.write(mapper.writeValueAsString(responseMsg));
writer.flush();
writer.close();
} catch(IOException e){
//忽略
logger.error(e.getMessage(),e);
}
}
}
}

I认为***返回http雕像代码 401 而不是 403 403 用于 AccessDeniedException ;



然后注入自己的 AuthenticationEntryPoint ExceptionTranslationFilter ;

  http.exceptionHandling() .authenticationEntryPoint(myEntryPoint()); 

@Bean
AuthenticationEntryPoint myEntryPoint(){
返回新的ExampleAuthenticationEntryPoint();
}

/ *
或仅返回401
AuthenticationEntryPoint myEntryPoint(){
返回新的HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
* /

BTW:



如果在 RequestHeaderAuthenticationFilter $中将 continueFilterChainOnUnsuccessfulAuthentication 设置为 true c $ c>,该异常不会抛出超级过滤器,并且其默认值为 true ,也许您需要将其设置为false,否则需要注入 DelegateRequestMatchingFilter 中的 AuthenticationFailureHandler 在进行身份验证时处理异常。



**更新**

 否则,如果(AccessDeniedException的instanceInstance除外){
身份验证= SecurityContextHolder.getContext( ).getAuthentication();
if(authenticationTrustResolver.isAnonymous(authentication)|| authenticationTrustResolver.isRememberMe(authentication)){
logger.debug(
访问被拒绝(用户为+(authenticationTrustResolver.isAnonymous(authentication)) ?匿名:未完全认证)+);重定向到认证入口点,
异常);

sendStartAuthentication(
请求,
响应,
链,
新InsufficientAuthenticationException(
需要完全认证才能访问此资源) );
}
else {
logger.debug(
访问被拒绝(用户不是匿名用户);委托给AccessDeniedHandler,
例外);

accessDeniedHandler.handle(请求,响应,
(AccessDeniedException)异常);
}
}

您可以从代码中读取,现在它是 AccessDeniedException ,我认为您可以禁用匿名用户,或者您需要检查为什么得到 AccessDeniedException



http.anonymous()。disable();



UPDATE 2

 身份验证currentUser = SecurityContextHolder.getContext()
.getAuthentication();

if(currentUser == null){
返回true;
}

if(!checkForPrincipalChanges){
返回false;
}

if(!principalChanged(request,currentUser)){
返回false;
}

checkForPrincipalChanges的默认值 false ,因此现在如果启用 anon 过滤器,它将返回false并跳过 RequestHeaderAuthenticationFilter ,因此请尝试禁用 anon 过滤器。并且您需要学习spring安全代码并通过调试对其进行研究。


I have a question about the exceptions on the Spring security level.

I user the SM_USER header for authorization and for the validation the request goes through the DelegateRequestMatchingFilter (it helps to understand whether the SM_USER is needed).

The problem is that if there is no SM_USER-header at all the line super.doFilter(...) throws a PreAuthenticatedCredentialsNotFoundException which cannot be processed with the standard ExceptionResolver for controllers and that's why looks strange and differs from all the other exception thrown by the application.

I tried to write an own method marked with @ExceptionResolver annotation for the filter, but it is ignored.

How can I insert an ExceptionsResolver for this Filter?

DelegateRequestMatchingFilter snippet

public class DelegateRequestMatchingFilter extends RequestHeaderAuthenticationFilter {

private RequestMatcher ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher matcher) {
    super();
    super.setPrincipalRequestHeader("SM_USER");
    this.ignoredRequests = matcher;
}   

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

     HttpServletRequest httpReq = (HttpServletRequest) req;      
     if(ignoredRequests.matches(httpReq)) {
         chain.doFilter(req,resp);
     } else {
         super.doFilter(req,resp,chain);//<-- throws exception
     }       
}   
}

Fragment from ExceptionResolver which does not solve the problem because it works only for controllers

 @ControllerAdvice
public class ExceptionResolver extends AbstractHandlerExceptionResolver{


@Override
protected ModelAndView doResolveException(HttpServletRequest request,
        HttpServletResponse responce, Object handler, Exception exception) {

    ModelAndView toReturn = new ModelAndView();
    toReturn.setView(new MappingJackson2JsonView());
    toReturn.addObject("message", exception.getMessage());
    toReturn.addObject("exceptionClass", exception.getClass().getCanonicalName());
    return toReturn;
}

@ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)       
public ResponseEntity<Result> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {

    Result result = new Result("the identification header is missing");
    result.setException(PreAuthenticatedCredentialsNotFoundException.class);
    ResponseEntity<Result> response = new ResponseEntity<Result>(result, HttpStatus.FORBIDDEN);
    return response;
}
}

SecurityConfiguration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

private UserDetailsService userDetailsService;  
private PreAuthenticatedAuthenticationProvider preAuthenticatedProvider;



@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

    Map<String, List<GrantedAuthority>> rolesAuthorities = getRolesWithAutorities();

    userDetailsService = new CustomUserDetailsService(...);
    UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = 
            new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService);
    preAuthenticatedProvider = new PreAuthenticatedAuthenticationProvider();
    preAuthenticatedProvider.setPreAuthenticatedUserDetailsService(wrapper);
    auth.authenticationProvider(preAuthenticatedProvider);
}   

@Bean
public SmUserFailureHandler smUserFailureHandler(){
    return new SmUserFailureHandler();
}

@Bean
public AccessDeniedHandler accessDeniedHandler(){
    AccessDeniedHandlerImpl handler = new AccessDeniedHandlerImpl();
    handler.setErrorPage("/errorPage/");        
    return handler;     
}

public RequestHeaderAuthenticationFilter siteMinderFilter() throws Exception
{

    RequestHeaderAuthenticationFilter siteMinderFilter = new DelegateRequestMatchingFilter(
            new OrRequestMatcher(
            new AntPathRequestMatcher("/uriToSkip/")));

    siteMinderFilter.setPrincipalRequestHeader("SM_USER");
    siteMinderFilter.setAuthenticationManager(authenticationManager());   
    return siteMinderFilter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {  

 //...   
 http.addFilter(siteMinderFilter());
 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
  //adding methods... 
  http = registry.and();

  http.csrf().disable();
  http.headers().cacheControl().disable();
  http
  .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.NEVER);    
}
}

If I change my code according to the answer, I get this exception even SM_USEr header is present and is correct

org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:168)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)

At first, you should add your filter after ExceptionTranslationFilter, this filter will handle the AccessDeniedException and AuthenticationException when it catches them.

http.addFilterAfter(siteMinderFilter(), ExceptionTranslationFilter.class);

and next you need inject a AuthenticationEntryPoint to handle AuthenticationException, there are some already exist AuthenticationEntryPoint you can use it, like HttpStatusEntryPoint, LoginUrlAuthenticationEntryPoint, ...;

If you want to return a JSON response you need to implement your own AuthenticationEntryPoint, for example:

public class ExampleAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        logger.error(authException.getMessage(), authException);
        if (!response.isCommitted()) {
            Map<String, Object> responseMsg = new HashMap<String, Object>();
            responseMsg.put("message", authException.getMessage());
            ObjectMapper mapper = new ObjectMapper();
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter writer = response.getWriter();
                writer.write(mapper.writeValueAsString(responseMsg));
                writer.flush();
                writer.close();
            } catch (IOException e) {
                // ignore
                logger.error(e.getMessage(), e);
            }
        }
    }
}

I think it's better to return http statue code 401 instead of 403, 403 is used for AccessDeniedException;

and then inject your own AuthenticationEntryPoint to ExceptionTranslationFilter;

http.exceptionHandling().authenticationEntryPoint(myEntryPoint());

@Bean
AuthenticationEntryPoint myEntryPoint() {
    return new ExampleAuthenticationEntryPoint();
}

/*
Or just return 401
AuthenticationEntryPoint myEntryPoint() {
    return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
} 
*/

BTW:

If you set continueFilterChainOnUnsuccessfulAuthentication to true in RequestHeaderAuthenticationFilter, the exception will not throw in super filter, and its default value is true, maybe you need to set it to false, or you will need to inject a AuthenticationFailureHandler in DelegateRequestMatchingFilter to handle the exception when do authentication.

** UPDATE **

else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);

                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                                "Full authentication is required to access this resource"));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);

                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }

As you can read from the code, now it's AccessDeniedException, I think you can disable the Anonymous user or you need to check why you get AccessDeniedException.

http.anonymous().disable();

UPDATE 2

Authentication currentUser = SecurityContextHolder.getContext()
            .getAuthentication();

    if (currentUser == null) {
        return true;
    }

    if (!checkForPrincipalChanges) {
        return false;
    }

    if(!principalChanged(request, currentUser)) {
        return false;
    }

default value of checkForPrincipalChanges is false, so now if you enable the anon filter, this will return false and skip do authentication in RequestHeaderAuthenticationFilter, so just try do disable anon filter. and you need to learn spring security code and research it by debugging.