Spring Security:在 servlet 过滤器中访问当前经过身份验证的用户

Posted

技术标签:

【中文标题】Spring Security:在 servlet 过滤器中访问当前经过身份验证的用户【英文标题】:Spring Security: Access the current authenticated User inside a servlet Filter 【发布时间】:2014-11-25 10:06:36 【问题描述】:

我最近开始学习 Spring Security,今天我遇到了这个基本(我相信)问题:为什么我不能访问 Servlet 过滤器中的当前 Principal,如下面的课程所示:

package com.acme.test;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
public class TestFilter implements Filter 

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
        // TODO Auto-generated method stub

    

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     * javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException 

        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication auth = securityContext.getAuthentication();

        // auth is null here

        chain.doFilter(request, response);
    

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() 
        // TODO Auto-generated method stub

    


使用 Authentication auth = securityContext.getAuthentication(); 检索到的 Authentication 对象为空。在 MVC @Controller 中使用上面的 sn-p 可以正常工作(如预期的那样)。

为什么会这样?

【问题讨论】:

我会查看 spring 安全过滤器链,以覆盖默认的 spring 配置并将过滤器插入到正确的位置。身份验证可能仅在特定位置可用docs.spring.io/spring-security/site/docs/3.1.x/reference/… @jpprade 谢谢。我对通用过滤器最感兴趣,所以我不想覆盖安全过滤器。似乎(从下面的答案)配置过滤器的顺序是有效的。 【参考方案1】:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null) 
  if (authentication.getPrincipal() instanceof UserDetails) 
    UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
    return springSecurityUser.getUsername();
   else if (authentication.getPrincipal() instanceof String) 
    return (String) authentication.getPrincipal();
  

return null;

【讨论】:

【参考方案2】:

doFilter内:

HttpServletRequest request = (HttpServletRequest) request;
HttpSession session = request.getSession(false);

SecurityContextImpl sci = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");

if (sci != null) 
        UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();
        // do whatever you need here with the UserDetails

希望对你有帮助

【讨论】:

确实有效...@mordechai-tamam 的解决方案也有效。您能否解释一下这里发生了什么...我最感兴趣的是了解发生了什么。 HttpSessionSecurityContextRepository 将安全上下文存储在请求之间的 HttpSession 中。对于每个请求,它都放在一个 ThreadLocal 中,您可以从中访问它。 @Ralph 写的很好:***.com/questions/6408007/… 感谢 Vipul 提供的信息。有趣的东西。尽管如此,这并不能完全解释为什么 SecurityContextHolder.getContext();没有获得对包含当前用户的 SecurityContext 的引用。这是因为我的过滤器在 SecurityContextPersistenceFilter 之前运行吗? 我想我现在明白了...跟踪所有过滤器调用,并在链中的所有过滤器运行后看到 SecurityContextPersistenceFilter 对 SecurityContextHolder#clearContext() 的调用。我将接受这个答案,因为这就是我要使用的。虽然下面的答案有助于理解正在发生的事情。【参考方案3】:

以下 sn-p 工作并提供Principal 实例:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException 
    HttpServletRequest req = (HttpServletRequest) request;

    Principal principal = req.getUserPrincipal();

    if (principal != null) 
        // do something with the Principal
    

    chain.doFilter(request,  response);

【讨论】:

【参考方案4】:

旧的 web.xml 部署描述符是一种确定过滤器顺序的简单方法。来自 Servlet 3.0 规范:容器在构建过滤器链时使用的顺序,以应用于 具体请求URI如下:

    首先,匹配过滤器映射的顺序与这些 元素出现在部署描述符中。 接下来,匹配过滤器映射的顺序与这些 元素出现在部署描述符中。

简而言之,您必须将 <filter-mapping> for your filter 放在 Spring 安全性之后。

同样来自同一个规范文档:如果调用监听器、servlet、过滤器的顺序对 应用程序,则必须使用部署描述符。

【讨论】:

谢谢;我没有使用 XML 配置,但您的回答有助于理解过滤器排序的重要性。【参考方案5】:

您可以看到here,为了访问SecurityContext,必须先使用安全过滤器。

如果您询问如何操作,这取决于您配置 Web 应用程序的方式。 就我而言,我使用的是 Spring-Boot ,基于 Servlet-3 配置风格,以及 Java 中的 Spring 上下文配置(无 XML) 所以,我的配置是这样的:

@Configuration
@EnableWebMvc
@EnableWebMvcSecurity
public class WebCtxConfig extends WebMvcConfigurerAdapter 

    @Autowired
    ApplicationContext ctx;


    @Bean
    FilterRegistrationBean springSecurityFilter() 
        FilterChainProxy o = (FilterChainProxy) ctx
                .getBean(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
        FilterRegistrationBean trVal = new FilterRegistrationBean();
        trVal.setFilter(o);
        trVal.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
        return trVal;
    

    @Bean
    public FilterRegistrationBean applicationContextIdFilter(final IThreadLifecycleManager threadLifecycleManager) 
        FilterRegistrationBean retVal = new FilterRegistrationBean();
        YourFilter filter = new YourFilter();
        retVal.setFilter(filter);
        retVal.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 2);
        return retVal;
    

请注意,通过设置顺序,您可以控制过滤器的顺序。

【讨论】:

这行得通,看起来不错。想知道为什么@vipul-paralikar 的 sn-p 也有效。哪种方法是正确的? 我的回答展示了您可以订购过滤器的方式,以便您能够在 FilterChainProxy 之前/之后放置其他过滤器。关键是在调用 FilterChainProxy 之前(其中一个链实际上正在执行它),安全上下文是空的。因此,我的解决方案适用于您的过滤器与任何与身份验证相关的操作无关的情况,并且仍然依赖于用户已经通过身份验证的事实。

以上是关于Spring Security:在 servlet 过滤器中访问当前经过身份验证的用户的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security:在 servlet 过滤器中访问当前经过身份验证的用户

Grails spring security、servlet 过滤器和响应

Spring Security:Servlet 过滤器

spring-security

在没有 servlet api 的 webflux 项目中使用 OAuth2 和 Spring Security OAuth2 和 reactor netty

发生超时后调用 Servlet 方法(控制器)时,Spring Security 3.1 重定向到登录不起作用