Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面
Posted
技术标签:
【中文标题】Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面【英文标题】:Spring Security - User auth sometimes fails and user is redirected to login page 【发布时间】:2017-06-28 19:38:35 【问题描述】:我的环境:
Java 6 Servlet 2.5 Weblogic 12.1 Spring MVC 4.3.4.RELEASE Spring Security 4.2.0.RELEASE我已经实现了一个 CustomAuthenticationProvider 来针对 Oracle db 验证用户:用户实际上是 db 用户,所以我尝试连接到 db 以检查用户/密码,如果结果比我加载权限是积极的。
配置运行良好,除非出现登录错误。如果用户输入错误的用户名或密码,应用程序会在同一页面显示错误消息,但进一步正确尝试不会让用户登录。而是重定向到登录页面。如果用户再次尝试,则问题仍然存在。一分钟后,同样的尝试,无需重新加载页面,就成功了。
正如我所说,只有在登录错误之后才会出现问题。如果用户在第一次尝试或注销后正确键入了他的凭据,则不会出现问题。如果他在登录错误后这样做,那么问题就会出现。此外,我的开发环境没有任何问题(本地应用程序服务器和各种浏览器)但问题每次都出现在暂存环境中(相同的应用程序服务器但集中式和 IE9 -IE10-边缘)。我真的不明白有什么区别。
我在我的 CustomAuthenticationProvider 上放了很多日志,我可以看到 在两种情况下(正登录和负登录)用户名和密码都已成功接受,并且 UsernamePasswordAuthenticationToken 有被创建。然后,应该将用户重定向到 default-target-url,即我的应用程序根 /(我设置 always-use-default-target = true)。由于我不明白的原因,当问题发生时,重定向失败,因为 Spring Security 认为用户尚未获得访问安全路径的授权,并将他再次重定向到登录页面。
我已经检查了这两种情况的登录表单提交的请求,它们实际上是相同的,除了传递的 JSESSIONID。但是登录成功我可以从响应头中看到已经设置了JSESSIONID cookie,在否定的情况下没有“设置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);
这是我的 Spring Security 配置文件:
<?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>
每一个建议都值得赞赏。 谢谢你们, 多尔菲兹
编辑:我使 spring 安全日志工作,似乎由于某种原因会话在身份验证后已被清除,因此转发到登录页面。 这些是日志:
[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: CustomUserusername='MAROTAN1', password='null', email='antonio.marotta@novartis.com', firstName='Antonio', lastName='Marotta', graceTime='null', authorities=[Rolename='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: Rolename='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
为什么这种情况只会在某些时候发生,而且只发生在某些环境中?
【问题讨论】:
为什么是-1?根据某人的说法,我的问题没有显示任何研究工作,或者可能不清楚和/或没有用:/ 【参考方案1】:阅读不同但相似的问题here和here的解决方案,我猜我的也是并发问题,与会话管理有关。
出于这个原因,我尝试在我的securityConfig.xml
中为会话管理设置显式配置,将每个用户允许的身份验证会话数限制为 1:
<session-management session-fixation-protection="newSession">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
并将所需的侦听器放在我的web.xml
:
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
这些更改完全解决了问题。
【讨论】:
以上是关于Spring Security - 用户身份验证有时会失败并且用户被重定向到登录页面的主要内容,如果未能解决你的问题,请参考以下文章
Spring security openId 支持和用户取消身份验证
Spring-Security:优化获取当前经过身份验证的用户...
Spring Security:如何向经过身份验证的用户添加额外的角色
Spring Security + JPA - 使用许多业务模型(实体)进行用户身份验证
如何在单元测试中针对 Spring Security 对用户进行身份验证
防止 Spring Security 通过下一个身份验证提供程序对具有 BadCredentialException 的用户进行身份验证