使用java配置进行n因素身份验证

使用java配置进行n因素身份验证,java,spring,spring-mvc,authentication,spring-security,Java,Spring,Spring Mvc,Authentication,Spring Security,在使用spring security的spring mvc应用程序中,我想使用自定义AuthenticationProvider检查默认用户名和密码之外的n-number附加字段。我正在尝试使用Java配置。我应该如何设置它?首先,对您正在使用的接口及其在身份验证过程中所起的作用进行一些解释: -表示对用户进行身份验证的结果。保存授予该用户的权限以及该用户可能需要的任何其他详细信息。由于框架无法知道需要哪些详细信息,身份验证对象有一个可以返回任何对象的方法 -可以以某种方式创建身份验证对象的对

在使用spring securityspring mvc应用程序中,我想使用自定义
AuthenticationProvider
检查默认
用户名和
密码之外的n-number附加字段。我正在尝试使用Java配置。我应该如何设置它?

首先,对您正在使用的接口及其在身份验证过程中所起的作用进行一些解释:

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

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

  • -用于检索应用程序中所需的用户详细信息的
因此,如果您正在创建自定义的
AuthenticationProvider
,您甚至可能不需要以需要
UserDetailsService
的方式来实现它。决策取决于您,取决于您是否计划在其他项目中重用您的实现

至于代码中的编译问题,您混合了提供
UserDetailsService
的两种方法。在
CustomAuthenticationProvider
中,您已经用
@Inject
注释了
userService
字段。这意味着容器(在您的情况下是Spring应用程序上下文)将找到合适的实现,并在运行时使用反射将其注入该字段。通过上下文设置此字段的过程称为依赖项注入。在
SecurityConfig
类中,您试图通过类中不存在的
setUserDetailsService
方法设置字段,从而自己提供实现

要解决此问题,您需要决定使用以下方法之一提供UserDetails服务:

  • 删除
    @Inject
    注释并创建
    setUserDetailsService
    方法,或
  • 调用不存在的方法时删除该行,并将
    UserDetailsService
    的实现声明为bean
至于您应该选择哪种方式,如果您能找到一种方法使您的
SecurityConfig
类在其他项目中可重用,那么依赖注入方式可能会更好。在这种情况下,您可以导入它(通过使用
@import
注释),并在下一个应用程序中将另一个
UserDetailsSerice
实现声明为bean,然后让它工作

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

编辑

一个可行的、尽管过于简单的实现(主要基于此)将是:

public class CustomAuthenticationProvider implements AuthenticationProvider{

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        List<GrantedAuthority> 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;
    }
}

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

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;
    }

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

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

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

UserDetailsService应如下所示:

@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;
}
@覆盖
public UserDetails loadUserByUsername(字符串输入)抛出UsernameNotFoundException、DataAccessException
{
String[]split=input.split(“:”);
如果(拆分长度<2)
{
抛出新的UsernameNotFoundException(“必须同时指定用户名和公司域”);
}
字符串用户名=拆分[0];
字符串域=拆分[1];
User User=userDao.findByUsernameAndDomain(用户名,域);
if(user==null)
{
抛出新用户名NotFoundException(“无效用户名或公司域”);
}
返回用户;
}
将给定用户名拆分为两个组件:用户名和额外字段。在本例中,额外字段是用户的公司域

一旦我们有了用户名和域,我们就可以使用DAO找到匹配的用户

最后一个谜题:

TwoFactorAuthenticationFilter:

    <http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <intercept-url pattern="/secured" access="isAuthenticated()" />
        <intercept-url pattern="/**" access="permitAll" />
        <custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
        <logout logout-url="/logout" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="authenticationProvider" />
    </authentication-manager>

    <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="passwordEncoder">
            <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
        </beans:property>
        <beans:property name="userDetailsService" ref="userService" />
    </beans:bean>

    <beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />

    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login" />
    </beans:bean>

    <beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationFailureHandler" ref="failureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="successHandler" />
        <beans:property name="filterProcessesUrl" value="/processLogin" />
        <beans:property name="postOnly" value="true" />
        <beans:property name="extraParameter" value="domain" />
    </beans:bean>

    <beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/login" />
    </beans:bean>

    <beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login?login_error=true" />
    </beans:bean>

班级

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


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

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

首先,定义分层角色,每个因素对应一个角色。如果只有双因素身份验证,请在数据库中保留现有的一个角色,然后创建只有在运行时分配的具有完全访问权限的第二个角色。因此,当用户登录时,他们将登录到存储在数据库中的最小角色,并且该最小角色仅为g
    <http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <intercept-url pattern="/secured" access="isAuthenticated()" />
        <intercept-url pattern="/**" access="permitAll" />
        <custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
        <logout logout-url="/logout" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="authenticationProvider" />
    </authentication-manager>

    <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="passwordEncoder">
            <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
        </beans:property>
        <beans:property name="userDetailsService" ref="userService" />
    </beans:bean>

    <beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />

    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login" />
    </beans:bean>

    <beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationFailureHandler" ref="failureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="successHandler" />
        <beans:property name="filterProcessesUrl" value="/processLogin" />
        <beans:property name="postOnly" value="true" />
        <beans:property name="extraParameter" value="domain" />
    </beans:bean>

    <beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/login" />
    </beans:bean>

    <beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login?login_error=true" />
    </beans:bean>
@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);
    }
}
Role rl2 = new Role();rl2.setRole("full-access");//Don't save this one because we will manually assign it on login.
Set<Role> rls = new HashSet<Role>();
rls.add(rl2);
CustomUserDetailsService user = new CustomUserDetailsService(appService);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities(rls));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/securemain";