首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >spring安全手册登录最佳实践

spring安全手册登录最佳实践
EN

Stack Overflow用户
提问于 2017-11-11 00:44:35
回答 2查看 5K关注 0票数 11

我使用spring安全性来实现一个编程的、手动的用户登录。我有一个场景,我已经积极地建立了用户的身份,并希望登录他们。我不知道他们的密码,所以不能使用常规的登录代码路径,您可以将表单提交给url,该url通过servlet Filter拦截,执行所有的auth+session魔术。

我已经搜索过了,似乎大多数人都创建了自己的Authentication对象,然后告诉spring:

代码语言:javascript
运行
复制
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);

事实上,这是可行的。Spring甚至为我将其放入会话中,使后续的http请求保持其auth状态。

然而,我觉得这是一次肮脏的攻击。我将给出一些细节,希望给出与在控制器中使用setAuthentication()实现手动登录相关的问题的具体示例:

为了给出一个想法,我的配置是:

代码语言:javascript
运行
复制
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的默认会话固定保护(在登录时更改会话id )。
  • 我想使用会话注册表
  • ..。如果我选择配置这些会话/登录功能的更多功能。

我遍历了代码,看看spring是如何处理表单登录的。正如预期的那样,Spring完成了我的HttpSecurity配置让它在使用表单登录时所做的所有会话/登录功能。但是,当我通过SecurityContextHolder.getContext().setAuthentication()进行我自己的自定义/手动登录时,它没有做任何这些功能。这是因为spring完成了servlet Filter中的所有会话/登录功能,而我的编程代码实际上不能调用过滤器。现在,我可以尝试自己添加缺少的功能,复制它们的代码:我看到spring过滤器使用:ConcurrentSessionControlAuthenticationStrategyChangeSessionIdAuthenticationStrategyRegisterSessionAuthenticationStrategy。我可以自己创建这些对象,配置它们,并在自定义登录后调用它们。但是,复制所有的春季代码真是太差劲了。此外,我还缺少其他一些行为--我注意到在使用表单登录代码路径时,spring会触发一些登录事件,这些事件在我进行自定义登录时不会触发。可能还有其他我遗漏或不明白的东西。整个过程非常复杂,如果不正确的话,我觉得很有可能引入bug,更不用说,如果我开始复制spring代码,库更新将是一件痛苦的事。

所以,我觉得我是从错误的角度来的。我应该使用一种不同的策略,这样我就不会绕过spring为我做的那么多事情了?,也许我应该尝试自己的AuthenticationProvider来完成这个定制的登录?

*澄清一下,我的代码多少起作用了。但是,我觉得我是用糟糕的策略完成的,因为我不得不编写代码来复制spring为我做的许多事情。此外,我的代码并没有完美地复制spring所做的事情,这让我想知道会产生什么负面影响。必须有一个更好的方式来编程实现登录。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-11-14 12:10:27

对于自定义web身份验证,您应该实现自定义身份验证筛选器(例如AbstractAuthenticationProcessingFilter或仅仅是GenericFilterBean)、自定义身份验证提供程序(AuthenticationProvider)或/和自定义身份验证令牌(AbstractAuthenticationToken)的组合。

例如,请参见Spring安全Kerberos的源代码。

另请参阅:

票数 4
EN

Stack Overflow用户

发布于 2017-11-28 03:43:08

我想详细说明如何实现持续时间的建议。在我的场景中,我只使用了一个定制的AuthenticationProvider。我选择使用以下策略,而不是创建自定义servlet Filter,比如扩展AbstractAuthenticationProcessingFilter,这似乎是一项很大的工作:

  • 在我的代码中,当我确信我已经识别了用户,并希望他们“登录”时,我在用户会话中添加了一个标志,标记他们应该在下一个请求中登录,以及我需要的任何其他身份/簿记信息,比如他们的用户名。
  • 然后,我告诉浏览器客户端向loginProcessingUrl发送一个http帖子(与我配置的用于基于表单的登录的spring安全性相同),告诉他们发送标准的usernamepassword表单参数,尽管它们不需要发送真实值--像foo这样的虚拟值是可以的。
  • 当用户发出这个post请求(例如/login)时,spring将调用我的自定义AuthenticationProvider,它将在用户的会话中检查标志,并收集用户名。然后,它将创建并返回一个Authentication对象,例如PreAuthenticatedAuthenticationToken,它标识用户。
  • 其余的都由春天来处理。用户现在已登录。

通过这样做,您可以保持“正常”的登录方式,因此spring仍然会自动地:

  • 调用您为表单登录配置的任何自定义成功和失败处理程序,如果您使用该位置在登录时执行某些事情,比如查询或更新db,这是很好的。
  • 它将尊重您可能使用的每个用户设置的任何最大并发会话。
  • 您可以保留spring的默认会话固定攻击保护(登录时更改会话id )。
  • 如果您设置了自定义会话超时(如通过属性文件中的server.session.timeout ),spring将使用它。可能还有其他的会话配置属性也在此时完成。
  • 如果您启用了spring的“记住我”的功能,它就会工作。
  • 它将触发一个登录事件,该事件用于其他spring组件,例如将用户的会话存储在SessionRegistry中。我认为这些事件也被弹簧的其他部分所使用,比如执行器和审计。

当我第一次尝试做通常推荐的SecurityContextHolder.getContext().setAuthentication(authentication)登录我的用户,而不是自定义的AuthenticationProvider,上面的子弹都没有为我做,这会彻底破坏你的应用程序.或者引起微妙的安全漏洞--两者都不是好的。

这里有一些代码可以帮助巩固我说的话:

自定义AuthenticationProvider

代码语言:javascript
运行
复制
@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安全性以使用提供程序

代码语言:javascript
运行
复制
// 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 {
        ...
    }
}
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47233187

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档