Spring Security:为啥 Authentication 正在扩展 Principal?

Posted

技术标签:

【中文标题】Spring Security:为啥 Authentication 正在扩展 Principal?【英文标题】:Spring Security: Why Authentication is extending Principal?Spring Security:为什么 Authentication 正在扩展 Principal? 【发布时间】:2013-07-18 15:09:55 【问题描述】:

Spring Security 假设 Authentication 是一个 Principal。

public interface Authentication extends Principal, Serializable 

HttpServletRequest 有getUserPrincipal 方法,负责访问主体对象。

让我们考虑一下这种情况:

public interface RealPrincipal extends Principal 
   public Integer getId();

Common Module A 具有 Real Principal 接口和实现。

Module A 使用 Common Module A,Servlet Api,不依赖 Spring Security:

Module B 使用 Common Module A、Servlet Api 并配置 Spring Security。该模块负责安全性和 UserDetails 的实现。

Web A 使用模块 A 和模块 B。

为了使用请求方法,我最终得到了这样一个实现:

public ModelAndView someRequestHandler(Principal principal) 
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...

这迫使我对模块 A 和其他模块依赖 Spring Security。我相信一个适当的 servlet api 抽象不应该依赖于 spring 安全性。 request.getUserPrincipal 应该返回真正的委托人。

请解释为什么 org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper 返回

身份验证而不是Real Principal

编辑:我已将通用模块 A 添加到我的场景中,并更新了模块 B 负责安全性。

【问题讨论】:

【参考方案1】:

正如 Luke 所说,Spring Security 对 Principal 使用 Authentication,因为它实现了 Principal。它不使用 Authentication#getPrincipal() 因为它不能保证是一个主体(它是一个对象)。事实上,在大多数情况下,Spring Security 的 Authentication#getPrincipal() 会返回一个 User(不实现 Principal)、框架用户提供的自定义 UserDetails 或 String。

如果您希望 Spring Security 处理此问题,您可能需要按照 Luke 的建议使用 HttpServletRequestWrapper 来实现此逻辑。例如,您可以执行以下操作:

public RealPrincipalFilter extends OncePerRequestFilter 

    public void doFiter(HttpServletRequest request, HttpServletResponse response, FilterChain) 
        chain.doFilter(new RealPrincipalRequestWrapper(request), response);
    

    private static final class RealPrincipalRequestWrapper 
          extends HttpServletRequestWrapper 
        public Principal getUserPrincipal() 
            Authentication auth = (Authentication) super.getPrincipal();
            return auth == null ? null : (RealPrincipal) auth.getPrincipal()
        
    


@Configuration
@EnableWebSecurity
public WebSecurityConfig extends WebSecurityConfigurerAdapter 
    public configure(HttpSecurity http) 
        http
            // ... other config ...
            .addFilterAfter(new RealPrincipalFilter(), SecurityContextHolderAwareRequestFilter.class);
    
    ...

或者,请查看我对您的其他问题的回答,了解与 Spring MVC 集成的选项 - Injecting Custom Principal to Controllers by Spring Security

【讨论】:

感谢 Rob 的精彩回答。现在我明白了 Spring Security 不仅与 Web 应用程序有关,而且与独立应用程序有关。这意味着用户不需要是委托人。并且为了符合 Http Api,Authentication 实现了 Principal 接口。到目前为止一切顺利:) 这是我的问题:如果 Spring Security 自定义用户已经实现了 Principal 接口怎么办?这导致了这样一个我认为完美的实现:gist.github.com/cemo/6041868 你觉得呢? 您建议的更改将是非被动的,因此充其量可以包含在 4.x 版本中。我不认为这是大多数人都需要的用例,所以我认为它也不会包含在 4.x 中。这并不是说你没有充分的理由去做你正在做的事情,而是说作为一个框架,我们需要尝试瞄准 90% 的用例,并为其他用例提供挂钩。【参考方案2】:

简短的回答是AuthenticationPrincipal,因此它可以在需要一个的API(例如您提到的servlet API 方法)中使用。

这在实践中意味着什么?不是很多。 Java 的Principal 接口只有一个方法getName,所以如果你想做的不仅仅是渲染用户名,你需要了解更多的实现。

当您使用短语“real principal”和“proper servlet api abstraction”时,您可能应该考虑一下您的意思。例如,如果主体是“真实的”主体,您希望如何实现您的 someRequestHandler 方法?

【讨论】:

将 RealPrincipal 视为主体的子类。它具有所有必要的方法,例如用户名、电子邮件、密码等……我可以依赖它。没问题。但是依赖 Spring Security 并不是一个可行的解决方案。还有另一个问题。 Spring MVC 正在使用 request.getUserPrincipal 注入 Principal。如果我将 RealPrincipal 声明为控制器方法参数,则它不能注入。因为 Authentication 不是 RealPrincipal 而是 Principal。这会导致错误。 另外请看我的另一个问题***.com/questions/17741787/…。 RealPrincipal 是什么?您希望它来自哪里?即谁提供它,您如何依赖它? 我添加了 RealPrincipal 接口。它来自另一个模块,如 servlet-api。模块 B 提供了必要的实施。顺便说一句,这不是虚构的场景。我的情况和我上面写的完全一样。 如果你想从getUserPrincipal方法返回RealPrincipal,那么最好的选择可能是在spring安全过滤器链之后添加一个你自己的单独过滤器,它使用HttpServletRequestWrapper来自定义方法。您还可以禁用内置包装器。

以上是关于Spring Security:为啥 Authentication 正在扩展 Principal?的主要内容,如果未能解决你的问题,请参考以下文章

Spring security UsernamePasswordAuthenticationToken 始终返回 403:用户凭据已过期

防止 Spring Security 通过下一个身份验证提供程序对具有 BadCredentialException 的用户进行身份验证

尽管我们不提供 Spring Security 配置,为啥 Spring Security 应用程序会重定向到登录页面?

为啥spring-security-oauth oauth 2.0实现中需要scope参数

Spring Security:为啥 Authentication 正在扩展 Principal?

为啥 Spring Security 使用默认的预认证检查?