Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面 [英] Spring Security - User auth sometimes fails and user is redirected to login page

查看:200
本文介绍了Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的环境:

  • Java 6
  • Servlet 2.5
  • Weblogic 12.1
  • Spring MVC 4.3.4.RELEASE
  • Spring Security 4.2.0.RELEASE

我已经实现了一个 CustomAuthenticationProvider 以便根据 Oracle 数据库对用户进行身份验证:用户实际上是数据库用户,因此我尝试连接到数据库以检查用户/密码,如果结果比我加载权限为正.

I've implemented a CustomAuthenticationProvider in order to authenticate users against an Oracle db: users are actually db users, so I try a connection to the db to check user/password, and if the result is positive than I load authorities.

该配置运行良好,除非出现登录错误.如果用户输入错误的用户名或密码,应用程序会在同一页面中显示错误消息,但进一步正确的尝试不会让用户登录.而是重定向到登录页面.如果用户再次尝试,则问题仍然存在.一分钟后,同样的尝试,没有重新加载页面,成功了.

The configuration works good except after a login error. If the user type the wrong username or password, than the application shows an error message in the same page, but a further correct try does not let the user log-in. Instead is redirected to login page. If the user tries again, than the problem persists. After a minute the same try, without page reloading, is successfull.

正如我所说,问题仅在登录错误后出现.如果用户在第一次尝试或注销后正确键入其凭据,则不会出现问题.如果他在登录错误后执行此操作,则会出现问题.另外,我的开发环境没有任何问题(本地应用服务器和各种浏览器)但问题每次都出现在登台环境(相同的应用服务器,但集中式和 IE9-IE10-Edge).我真的不明白有什么区别.

As I said, the problem occurs only after a login error. If the user types his credentials correctly at the first try or after a logout, no problem occurs. If he does it after a login error, than the problem shows up. Also, nothing wrong happens on my development environment (local app-server and various browser) but the problem shows up everytime on staging environemnt (same app-server but centralized and IE9-IE10-Edge). I really don't understand what makes the difference.

我在 CustomAuthenticationProvider 上放置了许多日志,我可以看到 在两种情况下(正面和负面登录)用户名和密码都已成功接受,并且 UsernamePasswordAuthenticationToken被创建.然后,用户应该被重定向到 default-target-url,也就是我的应用程序根目录/(我设置了 always-use-default-target = true).出于我不明白的原因,当问题发生时,重定向失败,因为 Spring Security 认为用户尚未获得访问安全路径的授权,因此将他再次重定向到登录页面.

I put many logs on my CustomAuthenticationProvider and I can see than in both cases (positive and negative login) username and password have been successfully accepted, and a UsernamePasswordAuthenticationToken has been created. Then, the user should be redirected to the default-target-url that is my application root / (I set always-use-default-target = true). For a reason that I don't understand, when the problem occurs the redirect fails, as Spring Security think user is not authorized yet to acess a secure path and it redirects him to login page again.

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

I've checked the request with the login form submission for both cases and they are actually identical, except for the JSESSIONID that is passed. But the login is successful I can see from the response header that JSESSIONID cookie has been set, in the negative case there's not "set cookie".

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

Why there is this difference of behaviour despite the fact that username and password submitted are the same?! What can make a difference? My guess is that a wrong login attempt leave something dirty behind. Something that affects the next try. Whan can it be? And why this problem occurs only on my local environment? What am I missing?

这是 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);
}

}

这是我的 Spring Security 配置文件:

This is my Spring Security configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://www.springframework.org/schema/security"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/security
                            http://www.springframework.org/schema/security/spring-security.xsd">

<http auto-config="true">
    <intercept-url pattern="/assets/**" access="permitAll()"/>
    <intercept-url pattern="/pages/**" access="permitAll()"/>
    <intercept-url pattern="/login" access="permitAll()"/>
    <intercept-url pattern="/loginApp" access="permitAll()"/>
    <intercept-url pattern="/loginFailed" access="permitAll()"/>
    <intercept-url pattern="/logout" access="permitAll()"/>
    <intercept-url pattern="/logoutSuccess" access="permitAll()"/>
    <intercept-url pattern="/changepwd" access="permitAll()"/>
    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
    <intercept-url pattern="/relabel/**" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_WAREHOUSE_OP') or hasRole('ROLE_QA')"/>
    <intercept-url pattern="/**" access="hasRole('ROLE_DATA_ENTRY') or hasRole('ROLE_APPROVER') or hasRole('VIEWER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_WAREHOUSE_OP') or hasRole('ROLE_QA')"/>
    <form-login login-page="/login"
                default-target-url="/"
                authentication-failure-url="/loginFailed"
                login-processing-url="/loginApp"
                username-parameter="username"
                password-parameter="password"
                always-use-default-target="true"
    />
    <logout logout-success-url="/logoutSuccess" logout-url="/logout"/>
    <access-denied-handler error-page="/403"/>

    <csrf disabled="true" />
</http>

<authentication-manager>
    <authentication-provider ref="customAuthenticationProvider"/>
</authentication-manager>

感谢每一个建议.谢谢你们,多尔菲兹

Every suggestion is appreciated. Thank you all, dolfiz

我使 spring 安全日志正常工作,似乎由于某种原因会话在身份验证后已被清除,因此转到登录页面.这些是日志:

I made spring security logs working and it appears that for some reason the session has been cleared after the authentication, so the forward to login page. These are the logs:

[DEBUG] 2017-02-17 17:01:41.317 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@e2fe4b0e: Principal: CustomUser{username='MAROTAN1', password='null', email='antonio.marotta@novartis.com', firstName='Antonio', lastName='Marotta', graceTime='null', authorities=[Role{name='ROLE_ADMIN'}], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true}; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 10.166.243.87; SessionId: QYWPYnpbth0y139v2gz7r6hCm0cHpsfmxq8DFqsvv3XM1kT6YcP2!2062762872!1487347291632; Granted Authorities: Role{name='ROLE_ADMIN'}
[DEBUG] 2017-02-17 17:01:41.317 org.springframework.security.web.DefaultRedirectStrategy - Redirecting to '/dbetichette/'
[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
[DEBUG] 2017-02-17 17:01:41.317 org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
[DEBUG] 2017-02-17 17:01:41.321 org.springframework.security.web.FilterChainProxy - / at position 1 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
[DEBUG] 2017-02-17 17:01:41.321 org.springframework.security.web.context.HttpSessionSecurityContextRepository - No HttpSession currently exists
[DEBUG] 2017-02-17 17:01:41.322 org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
[DEBUG] 2017-02-17 17:01:41.322 org.springframework.security.web.FilterChainProxy - / at position 2 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
[DEBUG] 2017-02-17 17:01:41.322 org.springframework.security.web.FilterChainProxy - / at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
[DEBUG] 2017-02-17 17:01:41.323 org.springframework.security.web.FilterChainProxy - / at position 4 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
[DEBUG] 2017-02-17 17:01:41.323 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/logout'
[DEBUG] 2017-02-17 17:01:41.323 org.springframework.security.web.FilterChainProxy - / at position 5 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
[DEBUG] 2017-02-17 17:01:41.323 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Request 'GET /' doesn't match 'POST /loginApp
[DEBUG] 2017-02-17 17:01:41.323 org.springframework.security.web.FilterChainProxy - / at position 6 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
[DEBUG] 2017-02-17 17:01:41.324 org.springframework.security.web.FilterChainProxy - / at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
[DEBUG] 2017-02-17 17:01:41.324 org.springframework.security.web.FilterChainProxy - / at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
[DEBUG] 2017-02-17 17:01:41.324 org.springframework.security.web.FilterChainProxy - / at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
[DEBUG] 2017-02-17 17:01:41.324 org.springframework.security.web.authentication.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@6faa3d44: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff4c9c: RemoteIpAddress: 10.166.243.87; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.FilterChainProxy - / at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.session.SessionManagementFilter - Requested session ID QYWPYnpbth0y139v2gz7r6hCm0cHpsfmxq8DFqsvv3XM1kT6YcP2!2062762872 is invalid.
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.FilterChainProxy - / at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.FilterChainProxy - / at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/assets/**'
[DEBUG] 2017-02-17 17:01:41.325 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/pages/**'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/login'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/loginApp'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/loginFailed'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/logout'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/logoutSuccess'
[DEBUG] 2017-02-17 17:01:41.326 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/changepwd'
[DEBUG] 2017-02-17 17:01:41.327 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/admin/**'
[DEBUG] 2017-02-17 17:01:41.327 org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/'; against '/relabel/**'
[DEBUG] 2017-02-17 17:01:41.327 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /; Attributes: [hasRole('ROLE_DATA_ENTRY') or hasRole('ROLE_APPROVER') or hasRole('VIEWER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_WAREHOUSE_OP') or hasRole('ROLE_QA')]
[DEBUG] 2017-02-17 17:01:41.327 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@6faa3d44: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff4c9c: RemoteIpAddress: 10.166.243.87; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
[DEBUG] 2017-02-17 17:01:41.328 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@14b4feab, returned: -1
[DEBUG] 2017-02-17 17:01:41.329 org.springframework.security.web.access.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied

我想重要的一行是:

[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

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

Why is this happening only sometimes and only on some enviroments?

推荐答案

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

Reading solutions for different but similar problem here and here, I guessed that mine is a concurrency problem too, related to session management.

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

For this reason I tried to set explicit configuration in my securityConfig.xml for session management, limiting to 1 the number of auth sessions allowed for each user:

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

并将所需的侦听器放在我的 web.xml 上:

And put the required listener on my web.xml:

<listener>
    <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

这些变化完全解决了问题.

These changes solve the problem entirely.

这篇关于Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆