问题场景:
登录成功后,在执行某个功能操作(例如:系统管理模块的删除功能时),会去执行UserDetailsService.loadUserByUsername 再次进行用户认证。
出现问题版本 Spring security 4.04 、 4.10
通过源码分析发现BasicAuthenticationFilter.authenticationIsRequired(username) 一直返回true (true表示需要认证)
org.springframework.security.web.authentication.www.BasicAuthenticationFilter.class
关键代码分析
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
...
try {
...
if (<span style="color:#ff0000;"><strong>authenticationIsRequired(username)</strong></span>) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
...
}
chain.doFilter(request, response);
}
进一步分析authenticationIsRequired(String username),发现问题在existingAuth.getName().equals(username) 每次返回false,导致每次认证不通过。
private boolean authenticationIsRequired(String username) {
// Only reauthenticate if username doesn't match SecurityContextHolder and user
// isn't authenticated
// (see SEC-53)
Authentication existingAuth = SecurityContextHolder.getContext()
.getAuthentication();
if (existingAuth == null || !existingAuth.isAuthenticated()) {
return true;
}
// Limit username comparison to providers which use usernames (ie
// UsernamePasswordAuthenticationToken)
// (see SEC-348)
if (existingAuth instanceof UsernamePasswordAuthenticationToken
&& !<strong><span style="color:#ff0000;">existingAuth.getName().equals(username)</span></strong>) {
return true;
}
// Handle unusual condition where an AnonymousAuthenticationToken is already
// present
// This shouldn't happen very often, as BasicProcessingFitler is meant to be
// earlier in the filter
// chain than AnonymousAuthenticationFilter. Nevertheless, presence of both an
// AnonymousAuthenticationToken
// together with a BASIC authentication request header should indicate
// reauthentication using the
// BASIC protocol is desirable. This behaviour is also consistent with that
// provided by form and digest,
// both of which force re-authentication if the respective header is detected (and
// in doing so replace
// any existing AnonymousAuthenticationToken). See SEC-610.
if (existingAuth instanceof AnonymousAuthenticationToken) {
return true;
}
return false;
}
通过调试后发现,我原先登录账号是大写和数据库一致,第一次登录时账号是大写没有改变,但到了第二次认证后却自动转为小写,所以导致验证不通过。