Spring mvc Spring安全性-用户身份验证有时会失败,用户被重定向到登录页面

Spring mvc Spring安全性-用户身份验证有时会失败,用户被重定向到登录页面,spring-mvc,session,spring-security,Spring Mvc,Session,Spring Security,我的环境: 爪哇6 Servlet 2.5 Weblogic 12.1 Spring MVC 4.3.4.1版本 Spring Security 4.2.0.0版本 我已经实现了一个CustomAuthenticationProvider,以便针对Oracle db对用户进行身份验证:用户实际上是db用户,因此我尝试连接到db以检查用户/密码,如果结果是肯定的,则加载权限 除了登录错误之外,配置工作正常。如果用户键入错误的用户名或密码,则应用程序会在同一页面中显示错误消息,但如果用户再次正确

我的环境:

  • 爪哇6
  • Servlet 2.5
  • Weblogic 12.1
  • Spring MVC 4.3.4.1版本
  • Spring Security 4.2.0.0版本
我已经实现了一个CustomAuthenticationProvider,以便针对Oracle db对用户进行身份验证:用户实际上是db用户,因此我尝试连接到db以检查用户/密码,如果结果是肯定的,则加载权限

除了登录错误之外,配置工作正常。如果用户键入错误的用户名或密码,则应用程序会在同一页面中显示错误消息,但如果用户再次正确尝试,则不允许用户登录。而是重定向到登录页面。如果用户重试,则问题仍然存在。一分钟后,同样的尝试,没有重新加载页面,是成功的

正如我所说,只有在登录错误后才会出现问题。如果用户在第一次尝试或注销后正确键入其凭据,则不会出现问题。如果他在登录错误后这样做,那么问题就会出现。另外,我的开发环境(本地应用程序服务器和各种浏览器)上没有发生任何错误,但问题每次都会出现在登台环境上(相同的应用程序服务器,但集中化和IE9-IE10-Edge)。我真的不明白有什么不同

我在CustomAuthenticationProvider上放置了许多日志,我可以看到在这两种情况下(正面登录和负面登录)用户名和密码都已被成功接受,并且已创建了一个用户名密码身份验证令牌。然后,应该将用户重定向到作为我的应用程序根目录的默认目标url/(我设置始终使用默认目标=true)。出于我不理解的原因,当问题发生时,重定向失败,因为Spring Security认为用户尚未获得访问安全路径的授权,它会将用户重定向到再次登录页面

我已经用登录表单提交检查了这两种情况下的请求,它们实际上是相同的,除了传递的JSESSIONID。但是登录成功了,我可以从响应头中看到已经设置了JSESSIONID cookie,在否定的情况下没有“set cookie”

尽管提交的用户名和密码相同,但为什么会有这种行为差异?!什么能起作用我的猜测是,错误的登录尝试会留下一些脏东西。影响下一次尝试的东西。那会是什么?为什么这个问题只发生在我当地的环境中?我错过了什么?

这是CustomAuthenticationProvider的实现:

@Component
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

private Logger log = LogManager.getLogger(CustomAuthenticationProvider.class);

@Autowired
private UserService userService;

@Autowired
private SecurityService securityService;

@Autowired
private Messages messages;

@Value("${login.test.mode}")
private String testMode;

@Value("${login.test.mode.userid}")
private String testModeUserid;

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String password = (String) authentication.getCredentials();

    log.debug("##### SECURITY ##### Test mode status: " + testMode);

    // test mode uses its own configured user, ignoring login credentials, if username is empty
    if (Constants.FLAG_YES.equals(testMode) && StringUtils.isEmpty(username)) {
        username = testModeUserid;
    }

    GddbUserDetails gddbUserDetails = userService.findGddbUserDetailsByUsername(username);
    UserRole userRole = userService.findUserRolesByUsername(username);

    if (gddbUserDetails == null) {
        log.debug("##### SECURITY ##### Utente non trovato in anagrafica GDDB: " + username);
        throw new BadCredentialsException(messages.get("user.not.found.gddb"));
    } else {
        log.debug("##### SECURITY ##### OK Utente trovato in anagrafica GDDB: " + username);
    }

    // perform checks only if test mode is disabled
    if (!Constants.FLAG_YES.equals(testMode)) {
        // GDDB state check
        if (!Constants.USER_STATO_ACTIVE.equals(gddbUserDetails.getStato())) {
            log.debug("##### SECURITY ##### Utente presente in anagrafica GDDB ma disabilitato: " + username);
            throw new BadCredentialsException(messages.get("user.not.enabled.gddb"));
        } else {
            log.debug("##### SECURITY ##### Utente presente in anagrafica GDDB e abilitato: " + username);
        }
        // dbetichette user existence check
        if (userRole == null) {
            log.debug("##### SECURITY ##### Utente non presente in anagrafica DBEtichette: " + username);
            throw new BadCredentialsException(messages.get("user.not.enabled.locally"));
        } else {
            log.debug("##### SECURITY ##### Utente presente in anagrafica DBEtichette: " + username);
        }
        // dbetichette user activation check
        if (!Constants.FLAG_YES.equals(userRole.getActive())) {
            log.debug("##### SECURITY ##### Utente disabilitato in anagrafica DBEtichette: " + username);
            throw new BadCredentialsException(messages.get("user.not.enabled.locally"));
        } else {
            log.debug("##### SECURITY ##### Utente abilitato in anagrafica DBEtichette: " + username);
        }

        // oracle user password check
        String usernamePasswordCheckResult = securityService.checkUserPassword(username, password);
        log.debug("##### SECURITY ##### usernamePasswordCheckResult: " + usernamePasswordCheckResult);

        if (Constants.SECURITY_ACCOUNT_LOCKED.equals(usernamePasswordCheckResult)) {
            log.debug("##### SECURITY ##### Utente presente su DB ma bloccato: " + username);
            throw new BadCredentialsException(messages.get("user.blocked"));
        } else if (Constants.SECURITY_PASSWORD_EXPIRED.equals(usernamePasswordCheckResult)) {
            log.debug("##### SECURITY ##### Password dell'utente scaduta: " + username);
            throw new BadCredentialsException(messages.get("user.password.expired"));
        } else if (Constants.SECURITY_INVALID_USERNAME_PASSWORD.equals(usernamePasswordCheckResult)) {
            log.debug("##### SECURITY ##### Tentativo di accesso fallito per errata password: " + username);
            throw new BadCredentialsException(messages.get("user.password.wrong"));
        } else if (!Constants.SECURITY_VALID_USERNAME_PASSWORD.equals(usernamePasswordCheckResult)) {
            log.debug("##### SECURITY ##### Tentativo di accesso fallito per motivo sconosciuto: " + username
                    + " ( usernamePasswordCheckResult = " + usernamePasswordCheckResult + " )");
            throw new BadCredentialsException(messages.get("user.login.error.other"));
        } else {
            log.debug("##### SECURITY ##### Tentativo di accesso eseguito con successo: " + usernamePasswordCheckResult + " - " + username);
        }

    }

    CustomUser user = userService.createCustomUser(gddbUserDetails, userRole);
    log.debug("##### SECURITY ##### Creazione custom user: " + user);

    Collection<? extends GrantedAuthority> authorities = user.getAuthorities();

    UsernamePasswordAuthenticationToken userToken = new UsernamePasswordAuthenticationToken(user, password, authorities);
    log.debug("##### SECURITY ##### Creazione userToken: " + userToken);

    return userToken;

}

@Override
protected UserDetails retrieveUser(String s, UsernamePasswordAuthenticationToken token) throws AuthenticationException {

    UserDetails user = (UserDetails) token.getPrincipal();
    log.debug("##### SECURITY ##### retrieveUser: " + user);
    return user;
}

@Override
public boolean supports(Class<?> aClass) {
    return true;
}

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken token) throws AuthenticationException {
    log.debug("##### SECURITY ##### additionalAuthenticationChecks - userDetails " + userDetails);
    log.debug("##### SECURITY ##### additionalAuthenticationChecks - token " + token);
}

}
我想重要的一点是:

[DEBUG] 2017-02-17 17:01:41.317 org.springframework.security.web.context.HttpSessionSecurityContextRepository - HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session

为什么这种情况只在某些环境中偶尔发生?

阅读不同但相似问题的解决方案,我猜我的问题也是一个并发问题,与会话管理有关

因此,我尝试在我的
securityConfig.xml
中为会话管理设置显式配置,将每个用户允许的身份验证会话数限制为1:

<session-management session-fixation-protection="newSession">
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>

这些更改完全解决了问题。

为什么-1?有人说,我的问题没有显示任何研究成果,或者可能不清楚和/或没有用处:/
[DEBUG] 2017-02-17 17:01:41.317 org.springframework.security.web.context.HttpSessionSecurityContextRepository - HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session
<session-management session-fixation-protection="newSession">
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
<listener>
    <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>