我使用spring安全性来实现一个编程的、手动的用户登录。我有一个场景,我已经积极地建立了用户的身份,并希望登录他们。我不知道他们的密码,所以不能使用常规的登录代码路径,您可以将表单提交给url,该url通过servlet Filter拦截,执行所有的auth+session魔术。
我已经搜索过了,似乎大多数人都创建了自己的Authentication对象,然后告诉spring:
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);事实上,这是可行的。Spring甚至为我将其放入会话中,使后续的http请求保持其auth状态。
然而,我觉得这是一次肮脏的攻击。我将给出一些细节,希望给出与在控制器中使用setAuthentication()实现手动登录相关的问题的具体示例:
为了给出一个想法,我的配置是:
httpSecurity
    .authorizeRequests()
    .antMatchers("/test/**").permitAll()
    .antMatchers("/admin/**", "/api/admin/**").hasRole("USER_SUPER_ADMIN")
    .and()
    .formLogin()
    .loginPage("/sign-in?sp")
    .loginProcessingUrl("/api/auth/sign-in")
    .successHandler(createLoginSuccessHandler())
    .failureHandler(createLoginFailureHandler())
    .permitAll()
    .and()
    .logout()
    .logoutUrl("/api/auth/sign-out")
    .logoutSuccessHandler(createLogoutSuccessHandler())
    .and()
    .sessionManagement()
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)
    .sessionRegistry(sessionRegistry)
;以上配置中的关键点:
我遍历了代码,看看spring是如何处理表单登录的。正如预期的那样,Spring完成了我的HttpSecurity配置让它在使用表单登录时所做的所有会话/登录功能。但是,当我通过SecurityContextHolder.getContext().setAuthentication()进行我自己的自定义/手动登录时,它没有做任何这些功能。这是因为spring完成了servlet Filter中的所有会话/登录功能,而我的编程代码实际上不能调用过滤器。现在,我可以尝试自己添加缺少的功能,复制它们的代码:我看到spring过滤器使用:ConcurrentSessionControlAuthenticationStrategy、ChangeSessionIdAuthenticationStrategy和RegisterSessionAuthenticationStrategy。我可以自己创建这些对象,配置它们,并在自定义登录后调用它们。但是,复制所有的春季代码真是太差劲了。此外,我还缺少其他一些行为--我注意到在使用表单登录代码路径时,spring会触发一些登录事件,这些事件在我进行自定义登录时不会触发。可能还有其他我遗漏或不明白的东西。整个过程非常复杂,如果不正确的话,我觉得很有可能引入bug,更不用说,如果我开始复制spring代码,库更新将是一件痛苦的事。
所以,我觉得我是从错误的角度来的。我应该使用一种不同的策略,这样我就不会绕过spring为我做的那么多事情了?,也许我应该尝试自己的AuthenticationProvider来完成这个定制的登录?
*澄清一下,我的代码多少起作用了。但是,我觉得我是用糟糕的策略完成的,因为我不得不编写代码来复制spring为我做的许多事情。此外,我的代码并没有完美地复制spring所做的事情,这让我想知道会产生什么负面影响。必须有一个更好的方式来编程实现登录。
发布于 2017-11-14 12:10:27
对于自定义web身份验证,您应该实现自定义身份验证筛选器(例如AbstractAuthenticationProcessingFilter或仅仅是GenericFilterBean)、自定义身份验证提供程序(AuthenticationProvider)或/和自定义身份验证令牌(AbstractAuthenticationToken)的组合。
例如,请参见Spring安全Kerberos的源代码。
另请参阅:
发布于 2017-11-28 03:43:08
我想详细说明如何实现持续时间的建议。在我的场景中,我只使用了一个定制的AuthenticationProvider。我选择使用以下策略,而不是创建自定义servlet Filter,比如扩展AbstractAuthenticationProcessingFilter,这似乎是一项很大的工作:
loginProcessingUrl发送一个http帖子(与我配置的用于基于表单的登录的spring安全性相同),告诉他们发送标准的username和password表单参数,尽管它们不需要发送真实值--像foo这样的虚拟值是可以的。/login)时,spring将调用我的自定义AuthenticationProvider,它将在用户的会话中检查标志,并收集用户名。然后,它将创建并返回一个Authentication对象,例如PreAuthenticatedAuthenticationToken,它标识用户。通过这样做,您可以保持“正常”的登录方式,因此spring仍然会自动地:
server.session.timeout ),spring将使用它。可能还有其他的会话配置属性也在此时完成。SessionRegistry中。我认为这些事件也被弹簧的其他部分所使用,比如执行器和审计。当我第一次尝试做通常推荐的SecurityContextHolder.getContext().setAuthentication(authentication)登录我的用户,而不是自定义的AuthenticationProvider,上面的子弹都没有为我做,这会彻底破坏你的应用程序.或者引起微妙的安全漏洞--两者都不是好的。
这里有一些代码可以帮助巩固我说的话:
自定义AuthenticationProvider
@Component
public class AccountVerificationAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private AppAuthenticatedUserService appAuthenticatedUserService;
    @Autowired
    private AuthService authService;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // This will look in the user's session to get their username, and to make sure the flag is set to allow login without password on this request.
        UserAccount userAccount = authService.getUserAccountFromRecentAccountVerificationProcess();
        if (userAccount == null) {
            // Tell spring we can't process this AuthenticationProvider obj.
            // Spring will continue, and try another AuthenticationProvider, if it can.
            return null;
        }
        // A service to create a custom UserDetails object for this user.
        UserDetails appAuthenticatedUser = appAuthenticatedUserService.create(userAccount.getEmail(), "", true);
        PreAuthenticatedAuthenticationToken authenticationToken = new PreAuthenticatedAuthenticationToken(appAuthenticatedUser, "", appAuthenticatedUser.getAuthorities());
        authenticationToken.setAuthenticated(true);
    
        return authenticationToken;
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}配置spring安全性以使用提供程序
// In your WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
public class AppLoginConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AccountVerificationAuthenticationProvider accountVerificationAuthenticationProvider;
    @Autowired
    private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider;
    
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        // Spring will try these auth providers in the order we register them.
        // We do the accountVerificationAuthenticationProvider provider first, since it doesn't need to do any slow IO to check,
        // so it's very fast. Only if this AuthenticationProvider  rejects (which means this http request is not for programmatic login), will spring then try the next AuthenticationProvider in the list.
        authenticationManagerBuilder
            .authenticationProvider(accountVerificationAuthenticationProvider)
            // I'm using ActiveDirectory / LDAP for when a user logs in via entering a user + password via the html form, but whatever you want to use here should work.
            .authenticationProvider(activeDirectoryLdapAuthenticationProvider);
    }
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ...
    }
}https://stackoverflow.com/questions/47233187
复制相似问题