简体   繁体   中英

How implement Spring security when login page having more field apart from user name and password?

I have a login page where the user need put the below information VIN number,email, zip code and accessCode which they will get from different application.

So to validate a user I need all the information in my custom UserDetailsService class and then will called a procedure to authenticate the user.

But I saw that when I implement the UserDetailsService like below

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

}

The loginDetails Object is like below

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

In the above situation how to use spring security. Here the user need to give all information to validate him self.

It is not the responisibility of UserDetailsService to validate the Authentication token. This is what an AuthenticationProvider does.

So first leave your implementation of UserDetailsService the single responsibility of loading all the data of the user from the database by 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);
    }
}

Than to intercept additional parameters from a login form you need to implement AuthenticationDetailsSource . It may be a good idea to extend WebAuthenticationDetails , but you can have just any object returned by 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);
    }
}

And to do the validation implement your own AuthenticationProvider by either implementing the interface itself or extending AbstractUserDetailsAuthenticationProvider or 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)
    }
}

Than you just need to pass your implementations to AuthenticationManager and 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>

Hope this helps!

PS Consider constructor injection over field injection! It's more testable and states the contract of the class better. See this discussion .

First of all, I would solve your problem differently. I would do a multi step authentication. The first would be a traditional user name / password login, using spring security's default model. The second step would be to show another form which would have to be filled up by the user to provide additional details for authentication, which your application wants to enforce.

Regardless, if you want to continue customizing the spring security model to ask more details on login in in a single step. Follow the steps reference in the previous answer from @Petr. And then to access session attributes in your UserDetailsService class, use the http://static.springsource.org/spring/docs/2.0.8/api/org/springframework/web/context/request/RequestContextHolder.html class provided by Spring.

You can get access to currentRequestAttributes() , which returns a RequestAttributes object. You can query the RequestAttributes object to get the desired attribute from the desired scope.

Note: This is a static method, which means its not going to be friendly to unit test.

You can also downcast RequestAttributes to ServletRequestAttributes if you want to get access to the underlying HttpServletRequest

Hope this helps.

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

Thanks. I created a custom filter class for authenticating the user based on three parameters - username, password, and account id. I autowired it as a bean in SecurityConfig class:

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

So, instead of just the traditional username and password fields, I was able to perform authentication using three fields (username, password, and account id) by calling appropriate service methods required for authentication and setting authorities for the logged in user:

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

}

I overrode following methods of UsernamePasswordAuthenticationFilter class for appropriate redirection after authentication & authorization:

@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");

}

I also modified the configure method in SecurityConfig class to execute the custom filter:

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

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

For custom authentication in Spring Security, the method

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

in this filter class (AccountCredentialsAuthenticationFilter) makes the following method in controller class redundant:

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

   public String loginPage(@Valid @ModelAttribute("user") User user, BindingResult result, ModelMap model, HttpServletRequest request){---- call ervice methods here ----}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM