8

我有一个登录页面,用户需要输入以下信息 VIN 号码、电子邮件、邮政编码和 accessCode,他们将从不同的应用程序中获得这些信息。

因此,要验证用户,我需要自定义UserDetailsService类中的所有信息,然后调用一个过程来验证用户。

但是我看到当我实现UserDetailsService下面的类似

@Component
 public class LoginService implements UserDetailsService {
@Autowired
LoginStoredProcedureDao loginStoredProcedureDao;

public Map<String, Object> verifyLogin(LoginDetails details) {
    return loginStoredProcedureDao.verifyLogin(details);

}
@Override
public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException {
    // TODO Auto-generated method stub
      //verifyLogin();
    return null;
}

}

loginDetails 对象如下所示

public class LoginDetails {
String vin;
String email;
String zipcode;
String accessCode;
}

在上述情况下如何使用spring security。在这里,用户需要提供所有信息来验证他自己。

4

4 回答 4

9

UserDetailsService验证Authentication令牌不是责任。这就是 anAuthenticationProvider所做的。

所以首先让你的实现UserDetailsService从数据库中加载用户的所有数据的单一责任login

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = null;
        try {
            user = userRepository.findByUsername(username);
        } catch (NotFoundException e) {
            throw new UsernameNotFoundException(String.format("No user found for username %s!", username);
        }
        retrun new UserDetailsImpl(user);
    }
}

而不是从您需要实现的登录表单中截取其他参数AuthenticationDetailsSource。扩展可能是个好主意WebAuthenticationDetails,但您可以只使用AuthenticationDetailsSource.

@Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, MyWebAuthenticationDetails> {

    @Override
    public MyWebAuthenticationDetails buildDetails(HttpServletRequest context) {
        // the constructor of MyWebAuthenticationDetails can retrieve
        // all extra parameters given on a login form from the request
        // MyWebAuthenticationDetails is your LoginDetails class
        return new MyWebAuthenticationDetails(context);
    }
}

AuthenticationProvider并通过实现接口本身或扩展AbstractUserDetailsAuthenticationProvider或来实现您自己的验证DaoAuthenticationProvider

@Component
public class UserDetailsAuthenticationProviderImpl extends AbstractUserDetailsAuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyWebAuthenticationDetails detais = (MyWebAuthenticationDetails) authentication.getDetails();
        // verify the authentication details here !!!
        // and return proper authentication token (see DaoAuthenticationProvider for example)
    }
}

比你只需要将你的实现传递给AuthenticationManagerand UsernamePasswordAuthenticationFilter

<util:list id="authenticationProviders">
    <ref bean="userDetailsAuthenticationProviderImpl" />
</util:list>

<!-- 
    This bean MUST have this exact ID to be the default authenticationManager!
    This is required prior Spring 3.1, as authentication-manager-ref is not
    present in sec:http element before!
 -->
<bean id="org.springframework.security.authenticationManager"
    name="authenticationManager"
    class="org.springframework.security.authentication.ProviderManager"
    c:providers-ref="authenticationProviders" />

<bean id="usernamePasswordAuthenticationFilter"
    class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
    p:authenticationManager-ref="authenticationManager"
    p:authenticationDetailsSource-ref="webAuthenticationDetailsSourceImpl" />

<sec:http authentication-manager-ref="authenticationManager">
    <sec:custom-filter position="FORM_LOGIN_FILTER" ref="usernamePasswordAuthenticationFilter" />
</sec:http>

希望这可以帮助!

PS考虑构造函数注入而不是字段注入!它更具可测试性,并且更好地说明了类的合同。请参阅此讨论

于 2013-04-22T20:02:44.907 回答
8

首先,我会以不同的方式解决您的问题。我会做一个多步骤的身份验证。第一种是传统的用户名/密码登录,使用 Spring Security 的默认模型。第二步是显示另一个必须由用户填写的表单,以提供您的应用程序想要强制执行的身份验证的其他详细信息。

无论如何,如果您想继续自定义 Spring 安全模型以在一个步骤中询问有关登录的更多详细信息。按照@Petr 上一个答案中的步骤参考。然后要访问 UserDetailsS​​ervice 类中的会话属性,请使用Spring 提供的http://static.springsource.org/spring/docs/2.0.8/api/org/springframework/web/context/request/RequestContextHolder.html类.

您可以访问currentRequestAttributes(),它返回一个RequestAttributes对象。您可以查询 RequestAttributes 对象以从所需范围获取所需属性。

注意:这是一个静态方法,这意味着它对单元测试不友好。

ServletRequestAttributes如果您想访问底层,您还可以将 RequestAttributes 向下转换为HttpServletRequest

希望这可以帮助。

于 2013-04-22T09:26:17.177 回答
2

是您的答案,您需要实现自己的过滤器并覆盖默认过滤器,以便将参数添加到登录表单。

于 2013-04-19T12:10:38.507 回答
0

谢谢。我创建了一个自定义过滤器类,用于基于三个参数(用户名、密码和帐户 ID)对用户进行身份验证。我将它自动装配为 SecurityConfig 类中的 bean:

@Bean
public AccountCredentialsAuthenticationFilter accountCredentialsAuthenticationFilter()
        throws Exception {
    AccountCredentialsAuthenticationFilter accountCredentialsAuthenticationFilter = new AccountCredentialsAuthenticationFilter();
    accountCredentialsAuthenticationFilter
            .setAuthenticationManager(authenticationManagerBean());
    return accountCredentialsAuthenticationFilter;
}

因此,通过调用身份验证所需的适当服务方法并为登录用户设置权限,我能够使用三个字段(用户名、密码和帐户 ID)执行身份验证,而不仅仅是传统的用户名和密码字段:

public class AccountCredentialsAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

@Autowired
private UserService userService;

@Qualifier("authenticationManager")
protected AuthenticationManager authenticationManager;

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {

    String account = request.getParameter("account");
    final String userName = request.getParameter("userName");
    final String password = request.getParameter("password");

    boolean isFound = userService.checkLogin(userName, password, account);

    if (isFound == true) {
        boolean selectedAccount = false;
        UserDetails userDetails = userService.loadUserByUsername(userName);

        User user = (User) userDetails;
        Set<Account> accounts = user.getAccounts();
        String acctSelect = null;
        // user has multiple accounts
        for (Account acct : accounts) {
            acctSelect = acct.getAccountId().toString();
            if (acctSelect.equals(account)) {
                // confirm which account user has logged in with
                selectedAccount = true;

                account = acctSelect;
                request.getSession().setAttribute("account", account);

                break;
            }
        }

        if (selectedAccount) {

            Set<? extends GrantedAuthority> authorities = (HashSet<? extends GrantedAuthority>) userDetails
                    .getAuthorities();

            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password,
                    authorities);

            token.setDetails(new WebAuthenticationDetails(request));

            super.setDetails(request, token);

            Authentication auth = this.getAuthenticationManager().authenticate(token);
            SecurityContext securityContext = SecurityContextHolder.getContext();
            securityContext.setAuthentication(auth);
            // Create a new session and add the security context.
            HttpSession session = request.getSession(true);
            session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);

            return auth;

        } else {

            SecurityContextHolder.getContext().setAuthentication(null);
            request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", null);

            throw new UsernameNotFoundException("Please input correct credentials");
        }

    } else {

        SecurityContextHolder.getContext().setAuthentication(null);
        request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", null);

        throw new UsernameNotFoundException("Please input correct credentials");
    }

}

我覆盖了 UsernamePasswordAuthenticationFilter 类的以下方法,以便在身份验证和授权后进行适当的重定向:

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
        Authentication authResult) throws IOException, ServletException {
    RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    redirectStrategy.sendRedirect(request, response, "/home");

}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException failed) throws IOException, ServletException {
    RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    redirectStrategy.sendRedirect(request, response, "/login?error=true");

}

我还修改了 SecurityConfig 类中的 configure 方法来执行自定义过滤器:

   @Override
protected void configure(HttpSecurity http) throws Exception {  

    http.addFilterBefore(accountCredentialsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)  
    .authorizeRequests()....rest of the code....}

对于 Spring Security 中的自定义身份验证,方法

 @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){---- call service methods here ----}

在这个过滤器类 (AccountCredentialsAuthenticationFilter) 中,控制器类中的以下方法是多余的:

 @RequestMapping(value = { "/login" }, method = RequestMethod.POST)

   public String loginPage(@Valid @ModelAttribute("user") User user, BindingResult result, ModelMap model, HttpServletRequest request){---- call ervice methods here ----}
于 2017-08-08T09:09:30.233 回答