使用java配置进行n因子身份validation

在使用spring securityspring mvc app中,我想使用自定义AuthenticationProvider来检查除默认usernamepassword之外的n个其他字段。 我正在尝试使用Java配置。 我该如何设置?

首先,对您正在使用的接口以及它们在身份validation过程中扮演的角色进行一些说明:

  • Authentication – 表示对用户进行身份validation的结果。 保留授予该用户的权限以及可能需要的有关用户的任何其他详细信息。 由于框架无法知道,需要哪些细节,validation对象有一个可以返回任何对象的getDetails方法

  • AuthenticationProvider – 可以某种方式创建Authentication对象的对象。 为了使它们更具可重用性,一些(或大多数) AuthenticationProvider不会在Authentication对象上设置用户详细信息,因为每个应用程序可能需要特定的用户详细信息。 相反,他们将解析用户详细信息的过程委托给可设置的UserDetailsService

  • UserDetailsService – 用于检索应用程序中所需的用户详细信息的策略 。

因此,如果您要创建自定义AuthenticationProvider您甚至可能不需要以需要UserDetailsService的方式实现它。 决定权取决于您,取决于您是否计划在其他项目中重复使用您的实施。

至于代码中的编译问题,您混合了两种提供UserDetailsService 。 在CustomAuthenticationProvider您已使用@Inject批注对userService字段进行了批注。这意味着,容器(在您的情况下为Spring应用程序上下文)是要找到一个合适的实现,并在运行时使用reflection将其注入该字段。 通过上下文设置此字段的过程称为dependency injection。 在SecurityConfig类中,您尝试通过setUserDetailsService方法设置字段来自己提供实现,该方法在您的类中不存在。

要解决此问题,您需要决定使用其中一种方法来提供UserDetails服务,并且:

  • 删除@Inject批注并创建setUserDetailsService方法,或
  • 在调用不存在的方法时删除该行,并将UserDetailsService的实现声明为bean

至于您应该选择哪种方式,如果您能找到一种方法使您的SecurityConfig类可以在其他项目中重用,那么dependency injection方式可能会更好。 在这种情况下,您可以导入它(通过使用@Import注释)并在您的下一个应用程序中将另一个UserDetailsSerice实现声明为bean并使其正常工作。

通常,像SecurityConfig这样的类并不是真正可重用的,因此创建setter并删除dependency injection可能是我的第一选择。

编辑

虽然简单的实现(主要基于此博客条目 )是一个有效的工作,但是:

 public class CustomAuthenticationProvider implements AuthenticationProvider{ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String name = authentication.getName(); String password = authentication.getCredentials().toString(); List grantedAuths = new ArrayList<>(); if (name.equals("admin") && password.equals("system")) { grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } if(pincodeEntered(name)){ grantedAuths.add(new SimpleGrantedAuthority("ROLE_PINCODE_USER")); } Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths); } @Override public boolean supports(Class authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } private boolean pincodeEntered(String userName){ // do your check here return true; } } 

然后在您的config类中更改以下方法:

 @Bean AuthenticationProvider customAuthenticationProvider() { return new CustomAuthenticationProvider(); } 

我们需要做的第一件事是扩展UsernamePasswordAuthenticationFilter类,以便它可以处理第二个输入字段。

 public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private String extraParameter = "extra"; private String delimiter = ":"; //getters and setters @Override protected String obtainUsername(HttpServletRequest request) { String username = request.getParameter(getUsernameParameter()); String extraInput = request.getParameter(getExtraParameter()); String combinedUsername = username + getDelimiter() + extraInput; return combinedUsername; } } 

obtainUsername()此方法用于从传入的HttpServletRequest对象中检索用户名和“额外”输入字段。

然后它将这两个值连接成一个字符串,用分隔符字符串(默认情况下为冒号)分隔它们。

然后它返回这个组合字符串。 默认情况下,读取“额外”输入字段的参数是额外的。

UserDetailsS​​ervice应如下所示:

 @Override public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException, DataAccessException { String[] split = input.split(":"); if(split.length < 2) { throw new UsernameNotFoundException("Must specify both username and corporate domain"); } String username = split[0]; String domain = split[1]; User user = userDao.findByUsernameAndDomain(username, domain); if(user == null) { throw new UsernameNotFoundException("Invalid username or corporate domain"); } return user; } 

将给定的用户名拆分为两个组件:用户名和额外字段。 在此示例中,额外字段是用户的公司域。

一旦我们拥有用户名和域名,我们就可以使用我们的DAO来查找匹配的用户。

最后的难题:

TwoFactorAuthenticationFilter:

                                   

在twoFactorAuthenticationFilter bean定义中,我们将extraParameter属性设置为“domain”,这是我们的登录表单中使用的输入字段的名称。

编辑:

看看User类的构造函数。

如果您不知道授权的权限是什么,请查看以下链接:

http://docs.spring.io/autorepo/docs/spring-security/3.2.1.RELEASE/apidocs/org/springframework/security/core/GrantedAuthority.html

您的编码提供了另一种模式,仅适用于普通用户名和密码。 我的代码适用于n因素身份validation。 如果任何问题仍然存在,请尝试切换到我的代码。

我很有意思,这篇文章经历了28次编辑,所以我可能错过了一些背景。 我也很有意思,你已经将其他答案中的一些代码合并到了你的问题中,并且问题已经从“为什么有效的用户不会进行身份validation?”中“转过头来”。 “为什么每个用户都进行身份validation?”。

目前的问题。

但是,正如所写,您的CustomAuthenticationProvider.authenticate()方法将始终返回一个返回auth.isAuthenticated() == trueAuthentication对象,因为您使用此方法进行实例化, 该方法会警告您这一点。 即使你作为第三个参数传入的collection是空的,也是如此。 事实上,该集合始终包含“已注册”的GrantedAuthority ,因为pincodeEntered(name)始终返回true 。 因此,您需要在这些方法中纠正您的逻辑。 如果身份validation不成功, authenticate()应返回null

下一步

您在评论中指出,您想要的是多因素身份validation的参考实现。 这是有问题的 – 关于什么构成这样的事情并不一定是一致的。 例如,有些人认为多因素应该包括占有因素,而不是单个登录页面上的n个知识因素。 它也不太适合SO答案,因为它需要一个博客文章(或一系列) – 无论多么慷慨。

例如,在网络上, 在这里和这里 ,存在多因素认证的工作示例。 后者我认为你必须发现,因为你似乎正在使用那里的一些代码。

使CustomAuthenticationProvider工作可能需要数小时。 调试可能需要更长时间,因为您的示例中有多种方法 – 它并不是最小的。 特别是, TwoFactorAuthenticationFilter类应该用于拦截来自登录页面的请求的输入并连接用户名和引脚。 在博客的示例中,这是以XML格式设置的 – 您可以将security命名空间添加到business-config.xml并在那里添加那些bean。

但是, SecurityConfig类和CustomAuthenticationProvider再次是一种不同的方法。

接下来,您的项目代码引用了j_security_checkurl,但该url未由任何内容处理。 我不确定这背后的意图,或者它来自哪里。 最后,URL路由的MVC配置增加了另一个元素 – 我不熟悉的一个元素。

我已经玩了一段时间了。 我有太多的混合方法和太多的复杂性来快速修复 – 也许其他人可以。

强烈建议您从博客中的示例开始,然后添加您想要的mvc配置。

NB设置其他人试图让示例工作

在设置项目时有一些问题 – 它对javax.mail有一个不需要和不满意的依赖,你需要将maven依赖项发布到服务器(在project-> properties-> deployment assembly中)你需要下载并安装tomcat服务器的适配器(如果您还没有)。

您还需要在数据库中创建表和列。

使用java配置进行n因子身份validation的最简单方法是从使用java配置的单因素身份validation(用户名和密码)的工作示例开始。 然后你只需做一些非常小的改动:假设你有一个使用java配置的工作单因素认证应用程序,步骤很简单:

首先,定义分层角色,每个因素都有一个角色。 如果您只有两个因子身份validation,请将现有的一个角色保留在数据库中,但随后创建第二个具有完全访问权限的角色,您只能在运行时分配该角色。 因此,当用户登录时,他们将登录到存储在数据库中的最小角色,并且该最小角色只能访问一个视图,这是一个允许他们输入控制器刚刚发送给他们的密码的表单通过短信或电子邮件或其他方法。 这些分层角色在SecurityConfig.java定义,如下所示:

 @Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .formLogin() .loginPage("/login") .defaultSuccessUrl("/getpin") .usernameParameter("j_username") .passwordParameter("j_password") .loginProcessingUrl("/j_spring_security_check") .failureUrl("/login") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .and() .authorizeRequests() .antMatchers("/getpin").hasAuthority("get_pin") .antMatchers("/securemain/**").hasAuthority("full_access") .antMatchers("/j_spring_security_check").permitAll() .and() .userDetailsService(userDetailsService); } } 

其次,在成功将正确的密码输入到处理PIN码输入表格的控制器代码后,添加将用户角色升级为完全访问权限的代码。 在控制器中手动分配完全访问权限的代码是:

 Role rl2 = new Role();rl2.setRole("full-access");//Don't save this one because we will manually assign it on login. Set rls = new HashSet(); rls.add(rl2); CustomUserDetailsService user = new CustomUserDetailsService(appService); Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities(rls)); SecurityContextHolder.getContext().setAuthentication(authentication); return "redirect:/securemain"; 

您可以在/getpin之后添加/getpin 。 您还可以支持多个授权角色,并使其尽可能复杂。 但是这个答案给出了使用java配置运行它的最简单方法。