首页 > 解决方案 > 获取异常 spring security 用户帐户被锁定\

问题描述

我们已经在我们的 angular spring boot 项目中实现了 Spring Security。在这里我们得到异常 spring security 用户帐户被锁定

请查看以下代码。

安全配置.java

package com.jwt.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;

import com.jwt.security.filter.AuthenticationTokenFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration<jwtAuthenticationEntryPoint>  extends WebSecurityConfigurerAdapter{

    @Autowired private UserDetailsService userDetailsService;

    @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint ; 

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder( PasswordEncoder());

    }

    @Bean
    public PasswordEncoder PasswordEncoder() {  
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean 
    public AuthenticationTokenFilter authenticationTokenFilterBean( ) {
        return new AuthenticationTokenFilter(); 
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception    {
        httpSecurity.csrf().disable()
        .exceptionHandling()
        .authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests()
        .antMatchers("/**").permitAll()
        .antMatchers("/registration").permitAll()
        .antMatchers("/login").permitAll()
        .antMatchers(HttpMethod.OPTIONS ,"/**").permitAll()
        .anyRequest().authenticated();
        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
        .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
        httpSecurity.headers().cacheControl();
        httpSecurity.headers().httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(31536000);       
    }
}

身份验证令牌过滤器 AuthenticationTokenFilter.hjava

package com.jwt.security.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.jwt.security.JwtTokenUtil;

public class AuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.header}")
    private String tokenHeader;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        String authToken = request.getHeader(this.tokenHeader);
        if (authToken != null && authToken.length() > 7) {
            authToken = authToken.substring(7);
        }
        String username = jwtTokenUtil.getUsernameFromToken(authToken);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            boolean isValid = jwtTokenUtil.validateToken(authToken, userDetails);
            if (isValid) {
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

在这里,当我从邮递员运行时,authToken 为空

JwtUtil 的代码如下

package com.jwt.security;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class JwtTokenUtil implements Serializable {

    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_AUDIENCE = "audience";
    static final String CLAIM_KEY_CREATED = "created";

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String getUsernameFromToken(String authToken) {
        String username = null;
        try {
            final Claims claims = getClaimsFromToken(authToken);
            username = claims.getSubject();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            username = null;
        }
        return username;
    }

    private Claims getClaimsFromToken(String authToken) {
        // TODO Auto-generated method stub
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken).getBody();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            claims = null;
        }

        return claims;
    }

    public boolean validateToken(String authToken, UserDetails userDetails) {
        // TODO Auto-generated method stub
        JwtUser user = (JwtUser) userDetails;
        final String username = getUsernameFromToken(authToken);
        return (username.equals(user.getUsername()) && !isTokenExpired(authToken));

    }

    private boolean isTokenExpired(String authToken) {
        final Date expiration = getExpirationDateFromToken(authToken);
        return expiration.before(new Date());
    }

    private Date getExpirationDateFromToken(String authToken) {
        // TODO Auto-generated method stub
        Date expiration = null;
        final Claims claims = getClaimsFromToken(authToken);
        if (claims != null) {
            expiration = claims.getExpiration();
        } else {
            expiration = null;
        }
        return expiration;
    }
    public String generateToken(JwtUser userDetails) {
        Map<String,Object> claims = new HashMap<String,Object>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);

    }

    public String generateToken(Map<String , Object> claims ) {
        return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }
}

CsrfHeaderFilter 的代码如下

package com.jwt.security;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;

public class CsrfHeaderFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        System.out.println("...CsrfToken.class.getName() :::" + CsrfToken.class.getName()); 
//      CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    //  CsrfToken csrfToken = new HttpSessionCsrfTokenRepository().loadToken(request);
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
        String token = null;
        Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
        if(csrfToken != null) {
        token = csrfToken.getToken();
        }
        if (cookie == null || token != null && !token.equals(cookie.getValue())) {
            cookie = new Cookie("XSRF-TOKEN", token);
            cookie.setPath("/");
            response.addCookie(cookie);

        }
        filterChain.doFilter(request, response);
    }

}

使用的控制器是 AuthenticationController 代码如下

package com.jwt.security.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.jwt.security.JwtTokenUtil;
import com.jwt.security.JwtUser;
import com.jwt.security.domain.User;
import com.jwt.security.domain.UserDTO;
import com.jwt.security.exception.UnauthorizedException;

@RestController
public class AuthenticationController {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Autowired private AuthenticationManager authenticationManager; 
    @Autowired private JwtTokenUtil jwtTokenUtil;

    @PostMapping(value="/login")
    public ResponseEntity<UserDTO> login(@RequestBody User user, HttpServletRequest request , HttpServletResponse response) {
    try {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        System.out.println("matches ::" + encoder.matches("123", user.getPassword()));
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
        final JwtUser userDetails = (JwtUser)authentication.getPrincipal();
        SecurityContextHolder.getContext().setAuthentication(authentication);
        final String token = jwtTokenUtil.generateToken(userDetails);
        response.setHeader("Token", token);
        return new ResponseEntity<UserDTO>(new UserDTO(userDetails.getUser(), token) , HttpStatus.OK);
    }catch(UnauthorizedException ex) {
        ex.printStackTrace();
        throw new UnauthorizedException(ex.getMessage());
    }
    }


}

在从邮递员调用http://localhost:8080/login并传递正确的电子邮件和密码时,我们得到以下异常

org.springframework.security.authentication.LockedException: User account is locked

请指教

标签: spring-security

解决方案


消息显示“用户帐户已锁定”。这发生在许多失败的身份验证事件之后。该帐户最终会根据实施情况解锁。

Spring Security 中只有两个地方发生:

  1. AccountStatusUserDetailsChecker.check(UserDetails user)
public void check(UserDetails user) {
    if (!user.isAccountNonLocked()) {
        throw new LockedException(messages.getMessage(
                "AccountStatusUserDetailsChecker.locked", "User account is locked"));
    }

    if (!user.isEnabled()) {
        throw new DisabledException(messages.getMessage(
                "AccountStatusUserDetailsChecker.disabled", "User is disabled"));
    }

    if (!user.isAccountNonExpired()) {
        throw new AccountExpiredException(
                messages.getMessage("AccountStatusUserDetailsChecker.expired",
                        "User account has expired"));
    }

    if (!user.isCredentialsNonExpired()) {
        throw new CredentialsExpiredException(messages.getMessage(
                "AccountStatusUserDetailsChecker.credentialsExpired",
                "User credentials have expired"));
    }
}
  1. AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks.check(UserDetails user)

所以如果你想设置一个断点,那就是你开始的地方。

所有这些都发生在您UserDetailsService的配置中。

    @Autowired private UserDetailsService userDetailsService;

该服务返回一个实现UserDetails接口的对象

    public interface UserDetails {
        boolean isAccountNonLocked();
    }

如果此方法返回 false,则帐户被锁定。这个名字有点混乱。

由于我们不知道您UserDetailsService是什么,因此我们无法告诉您这是如何填充的。所以建议只在抛出错误时设置一个断点。

如果您不希望启用帐户锁定功能,则可以使用不同的方法来实现。如果您覆盖UserDetailsServicebean,您总是可以返回从未锁定的用户。

另一种方法是注入你自己的检查器

   DaoAuthenticationProvider daoProvider = .... 
   daoProvider.setPreAuthenticationChecks(toCheck -> {});

还有一个PostAuthenticationChecks对象可以查看您的密码是否已过期。


推荐阅读