我使用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
复制相似问题