Java 预验证并将用户名转发给默认验证提供程序

Java 预验证并将用户名转发给默认验证提供程序,java,spring,authentication,spring-mvc,spring-security,Java,Spring,Authentication,Spring Mvc,Spring Security,我是Spring Security的新手,如果这是直截了当的话,请原谅我 我们正在重写遗留web应用程序的UI层。我们决定新的UI层将基于SpringMVC,使用SpringSecurity来处理安全性和授权 我一直在考虑在新应用程序中设置安全性,以模仿我们在以前的应用程序中所做的。在旧应用程序中,我们基本上有两个用户入口点: 内部用户通过HTTP基本身份验证进行身份验证 使用客户端LDAP服务器执行实际身份验证。 这种身份验证机制是在JBoss服务器上配置的,它也是 集装箱管理 外部用户通过

我是Spring Security的新手,如果这是直截了当的话,请原谅我

我们正在重写遗留web应用程序的UI层。我们决定新的UI层将基于SpringMVC,使用SpringSecurity来处理安全性和授权

我一直在考虑在新应用程序中设置安全性,以模仿我们在以前的应用程序中所做的。在旧应用程序中,我们基本上有两个用户入口点:

  • 内部用户通过HTTP基本身份验证进行身份验证 使用客户端LDAP服务器执行实际身份验证。 这种身份验证机制是在JBoss服务器上配置的,它也是 集装箱管理
  • 外部用户通过验证凭据的第三方身份验证服务登录。外部用户角色存储在LDAP服务器中。当第三方身份验证服务对凭据进行身份验证时,将使用用户名和硬编码密码在JBoss配置的安全域上对凭据进行身份验证,以便加载它们的角色
我想我会尝试在SpringSecurity中模拟这个功能,但到目前为止还不够。我在测试中使用内存中的身份验证提供程序代替LDAP,因为它更容易。我已经为内部用户提供了http基本身份验证

我尝试过子类化
AbstractPreAuthenticatedProcessingFilter
并通过它提供凭据,但它似乎确实将凭据正确地转发给“默认”身份验证提供程序

<http>
    ...
    <http-basic />
    <custom-filter position="PRE_AUTH_FILTER" ref="ExternalLoginFilter" />
</http>

<beans:bean id="ExternalLoginFilter" class="com.foo.ExternalLoginPreAuthenticationFilter">
    <beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>

<authentication-manager alias="authenticationManager">
    <authentication-provider>
        <user-service>
            <user name="internal-user" password="password" authorities="ROLE_USER, ROLE_INTERNAL" />
            <user name="external-user" password="password" authorities="ROLE_USER, ROLE_EXTERNAL" />
        </user-service>
    </authentication-provider>
</authentication-manager>`
}

我还试着像一些示例建议的那样设置“预验证身份验证提供者”,但这似乎期望我的
ExternalLoginPreAuthenticationFilter
也解决了用户角色问题

我如何配置SpringMVC以允许上面的场景?基本上,我需要能够告诉Spring Security以最少的侵入方式,使用特定用户名/密码对默认身份验证提供者执行登录,最好不要有太多的黑客(旧应用程序使用)

溶液说明:拉尔夫的溶液似乎有效,特别是这一部分:

我认为应该使用PreAuthenticatedAuthenticationProvider并将preAuthenticatedUserDetailsService变量设置为内存中的AuthenticationUserDetailsService

然而,CSRF保护似乎也很糟糕。当我登录并被重定向到主页时,来自此页面的任何HTTP帖子都将无法通过CSRF检查。在发布之前对主页的后续获取修复了该问题,因此Spring似乎以某种方式不正确地覆盖了当前的CSRF令牌。我发现了一个解决问题的方法。即使它声称是固定的,我也无法解决它。虽然bug报告链接到论坛中的一个变通方法,但我已经习惯了下面的变通方法,这似乎是可行的

诀窍是将
AuthenticationManager
注入
控制器
,然后自己登录:

@Controller
@RequestMapping(value = "/external-login")
public class ExternalLoginController {

    private AuthenticationManager authenticationManager;

    @Autowired
    public ExternalLoginController(AuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    // ...

    @RequestMapping(method = RequestMethod.POST)
    public String login(){
        // Do this after third-party authentication service accepts credentials
        String username = "external-user"; // or whatever username was authenticated by third-party
        UsernamePasswordAuthenticationToken credentials = new UsernamePasswordAuthenticationToken(username, "password");
        Authentication auth = authenticationManager.authenticate(credentials);
        SecurityContextHolder.getContext().setAuthentication(auth);

        return "redirect:/";
    }
}

问题是,
AuthenticationProvider
AuthenticationManager
中由提供的身份验证凭据类(通常是
AbstractAuthenticationToken
的子类)选择

您的
PreAuthenticationProcessingFilter
将创建一个
preauthenticationdauthenticationtoken
,该令牌通常由
preauthenticationdauthenticationprovider
使用

因此,要么:

  • 您注册了一个
    AuthenticationProvider
    ,该提供者“使用”了
    preauthenticationdauthenticationprovider
    令牌,并执行您想要的操作,或者
  • 更改
    PreAuthenticationProcessingFilter
    以创建其他类型的令牌(例如
    UsernamePasswordAuthenticationToken
    ,该令牌由您使用的“普通”身份验证提供程序“消费”(AbstractUserDetailsAuthenticationProvider的子类))

我认为应该使用
PreAuthenticatedAuthenticationProvider
并将
preAuthenticatedUserDetailsService
变量设置为内存中的
AuthenticationUserDetailsService

您是否在AbstractPreAuthenticatedProcessingFilter.doAuthenticate中设置断点以检查此方法是否正确调用了吗?我尝试了您的上一个建议,它似乎有效(使用UserDetailsByNameServiceWrapper作为UserDetailsService)。然而,当我使用这种方法登录时,CSRF保护似乎崩溃了。后续的表单帖子会给我一个带有CSRF失败错误消息的http 403?我尝试将AuthenticationManager注入到处理外部登录的控制器中,如果我直接调用它并随后在SecurityContextHolder上设置返回的身份验证,那么CSRF和登录将按预期工作。有什么想法吗?
@Controller
@RequestMapping(value = "/external-login")
public class ExternalLoginController {

    private AuthenticationManager authenticationManager;

    @Autowired
    public ExternalLoginController(AuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    // ...

    @RequestMapping(method = RequestMethod.POST)
    public String login(){
        // Do this after third-party authentication service accepts credentials
        String username = "external-user"; // or whatever username was authenticated by third-party
        UsernamePasswordAuthenticationToken credentials = new UsernamePasswordAuthenticationToken(username, "password");
        Authentication auth = authenticationManager.authenticate(credentials);
        SecurityContextHolder.getContext().setAuthentication(auth);

        return "redirect:/";
    }
}