macrozheng/mall · error · BadCredentialsException

密码不正确

Error message

密码不正确

What it means

This is Spring Security's BadCredentialsException thrown inside UmsMemberServiceImpl.login when the submitted password does not match the stored hash: passwordEncoder.matches(rawPassword, userDetails.getPassword()) returns false. The method's own comment — '密码需要客户端加密后传递' (password must be encrypted by the client before transmission) — is the critical context: the portal expects the client to send a pre-hashed (typically MD5) password, and the stored hash is the BCrypt of that pre-hash, so a plaintext or differently-hashed input will fail matches. The exception is an AuthenticationException subclass and is caught by the same try block (catch AuthenticationException), logged as '登录异常:{}', and login() returns a null token — so the caller sees login failure, not the thrown exception, unless it inspects the return value.

Source

Thrown at mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberServiceImpl.java:171

    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        UmsMember member = getByUsername(username);
        if(member!=null){
            return new MemberDetails(member);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }

    @Override
    public String login(String username, String password) {
        String token = null;
        //密码需要客户端加密后传递
        try {
            UserDetails userDetails = loadUserByUsername(username);
            if(!passwordEncoder.matches(password,userDetails.getPassword())){
                throw new BadCredentialsException("密码不正确");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }

    @Override
    public String refreshToken(String token) {
        return jwtTokenUtil.refreshHeadToken(token);
    }

    //对输入的验证码进行校验
    private boolean verifyAuthCode(String authCode, String telephone){
        if(StrUtil.isEmpty(authCode)){

View on GitHub (pinned to 0504e86b1f)

Solutions

  1. Ensure the client applies the same pre-hash as at registration (typically MD5 of the plaintext) before sending — verify in the browser/app network request that the password field is the hash, not plaintext.
  2. Inspect the stored hash: SELECT username, password FROM ums_member WHERE username = ? — confirm it is a BCrypt hash (starts with $2a$/$2b$) and is not null/empty.
  3. Reproduce matches locally: passwordEncoder.matches(expectedClientHash, storedHash) to confirm the encoder bean and hash format agree; if not, align the encoder (BCryptPasswordEncoder) registration-to-login.
  4. If the account is corrupt, trigger a password reset so a fresh hash is generated from the correct client-side input.
  5. Do not swallow the exception silently — surface a non-null token check or a thrown exception so the caller can distinguish 'wrong password' from 'system error' instead of receiving null.

Example fix

// before (silent swallow, returns null on any auth failure):
try {
    UserDetails userDetails = loadUserByUsername(username);
    if (!passwordEncoder.matches(password, userDetails.getPassword())) {
        throw new BadCredentialsException("密码不正确");
    }
    ...
} catch (AuthenticationException e) {
    LOGGER.warn("登录异常:{}", e.getMessage());
}
return token; // null on failure — caller cannot tell why

// after (fail explicitly, keep the token null contract but log the cause):
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
    LOGGER.warn("密码校验失败, username={}", username);
    throw new BadCredentialsException("用户名或密码错误");
}
// client side (mirror of registration):
// const pwd = md5(rawPassword);  axios.post('/sso/login', { username, password: pwd })
Defensive patterns

Strategy: validation

Validate before calling

// Validate the password format the portal expects (client-side MD5) before
// even calling login(), and guard against a null stored hash.
if (storedHash == null || storedHash.isEmpty()) {
    throw new BadCredentialsException("用户名或密码错误");
}
boolean ok = passwordEncoder.matches(clientHashedPassword, storedHash);
// clientHashedPassword must equal md5(rawPassword) sent by the front-end

Try / catch

// In UmsMemberServiceImpl.login, distinguish 'wrong password' from systemic
// errors instead of blanket-catching AuthenticationException and returning null.
try {
    if (!passwordEncoder.matches(password, userDetails.getPassword())) {
        throw new BadCredentialsException("用户名或密码错误");
    }
    // ... generate token ...
} catch (BadCredentialsException e) {
    LOGGER.warn("密码校验失败 username={}", username);
    throw e; // let the controller map it to a 401 with a clear message
} catch (AuthenticationException e) {
    LOGGER.error("登录系统异常 username={}", username, e);
    throw e;
}

Prevention

When it happens

Trigger: Client sends a plaintext password instead of the client-side hash the portal expects; client uses a different hash algorithm (e.g. SHA-256) than the MD5 the registration flow used; the stored password hash was created from a raw password while login compares a pre-hashed one (or vice-versa); member record's password field is null/empty so matches returns false.

Common situations: Front-end forgot to MD5 the password before POSTing to /sso/login; a member registered on a legacy flow (plaintext stored) now hitting the BCrypt matches path; password column migrated/corrupted so the hash no longer matches; copy-paste introducing trailing whitespace in the password; passwordEncoder bean changed (e.g. from BCrypt to noop) between registration and login.

Related errors


AI-assisted analysis of macrozheng/mall@0504e86b1f (2026-08-13). Data as JSON: /api/errors/39bdc1aa3e717da8. Report an issue: GitHub.