java 除了用户名和密码外,登录页面有更多字段时如何实现Spring安全?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16104228/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 21:55:57  来源:igfitidea点击:

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

javaspringspring-mvcspring-security

提问by Krushna

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.

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

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

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

But I saw that when I implement the UserDetailsServicelike below

但是当我实现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;
}

}

The loginDetails Object is like below

loginDetails 对象如下所示

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.

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

采纳答案by Akshay

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.

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

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.htmlclass provided by Spring.

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

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

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

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

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

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

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

Hope this helps.

希望这可以帮助。

回答by Roadrunner

It is not the responisibility of UserDetailsServiceto validate the Authenticationtoken. This is what an AuthenticationProviderdoes.

UserDetailsService验证Authentication令牌不是责任。这就是 an 的AuthenticationProvider作用。

So first leave your implementation of UserDetailsServicethe single responsibility of loading all the data of the user from the database by login:

因此,首先让您执行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);
    }
}

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.

比从您需要实现的登录表单中拦截额外的参数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);
    }
}

And to do the validation implement your own AuthenticationProviderby either implementing the interface itself or extending AbstractUserDetailsAuthenticationProvideror DaoAuthenticationProvider:

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

Than you just need to pass your implementations to AuthenticationManagerand UsernamePasswordAuthenticationFilter.

比您只需要将您的实现传递给AuthenticationManagerUsernamePasswordAuthenticationFilter

<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!

希望这可以帮助!

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

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

回答by Petr Mensik

Hereis your answer, you need to implement your own filter and override the default one in order to add parameters to the login form.

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

回答by vishal

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:

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

@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:

因此,除了传统的用户名和密码字段之外,我还能够通过调用身份验证所需的适当服务方法并为登录用户设置权限,使用三个字段(用户名、密码和帐户 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");
    }

}

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

我重写了 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");

}

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

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

   @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

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

 @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:

在此过滤器类 (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 ----}