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

Cod*_*Med 24 java authentication spring spring-mvc spring-security

Spring MVC中使用的应用程序春天的安全,我想用一个自定义的AuthenticationProvider检查正数超出了预设的附加字段usernamepassword.我正在尝试使用Java配置.我该如何设置?

Apo*_*psa 7

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

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

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

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

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

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

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

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

至于你应该选择哪种方式,如果你能找到一种让你的SecurityConfig课程可以在其他项目中重复使用的方法,那么依赖注入方式可能会更好.在这种情况下,你可以只导入它(通过使用@Importannotaion)并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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@Bean
AuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider();
}
Run Code Online (Sandbox Code Playgroud)


MS *_*him 6

我们需要做的第一件事是扩展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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

一旦我们拥有用户名和域名,我们就可以使用我们的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>
Run Code Online (Sandbox Code Playgroud)

在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因素身份验证.如果任何问题仍然存在,请尝试切换到我的代码.


Cod*_*Med 0

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

首先,定义分层角色,每个因素对应一个角色。如果您只有双因素身份验证,请在数据库中保留现有的一个角色,然后创建仅在运行时分配的具有完全访问权限的第二个角色。因此,当用户登录时,他们将登录到存储在数据库中的最小角色,并且该最小角色只能访问一个视图,这是一种允许他们输入控制器刚刚发送给他们的 pin 码的表单通过短信或电子邮件或其他方式。这些分层角色在 中定义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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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";  
Run Code Online (Sandbox Code Playgroud)

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