{"record":{"id":"39bdc1aa3e717da8","repo":"macrozheng/mall","slug":"error-39bdc1","errorCode":null,"errorMessage":"密码不正确","messagePattern":"密码不正确","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberServiceImpl.java","lineNumber":171,"sourceCode":"    }\n\n    @Override\n    public UserDetails loadUserByUsername(String username) {\n        UmsMember member = getByUsername(username);\n        if(member!=null){\n            return new MemberDetails(member);\n        }\n        throw new UsernameNotFoundException(\"用户名或密码错误\");\n    }\n\n    @Override\n    public String login(String username, String password) {\n        String token = null;\n        //密码需要客户端加密后传递\n        try {\n            UserDetails userDetails = loadUserByUsername(username);\n            if(!passwordEncoder.matches(password,userDetails.getPassword())){\n                throw new BadCredentialsException(\"密码不正确\");\n            }\n            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());\n            SecurityContextHolder.getContext().setAuthentication(authentication);\n            token = jwtTokenUtil.generateToken(userDetails);\n        } catch (AuthenticationException e) {\n            LOGGER.warn(\"登录异常:{}\", e.getMessage());\n        }\n        return token;\n    }\n\n    @Override\n    public String refreshToken(String token) {\n        return jwtTokenUtil.refreshHeadToken(token);\n    }\n\n    //对输入的验证码进行校验\n    private boolean verifyAuthCode(String authCode, String telephone){\n        if(StrUtil.isEmpty(authCode)){","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/macrozheng/mall/blob/0504e86b1f1b6f1b8aa6a734d37a90fb67346be7/mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberServiceImpl.java#L153-L189","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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.","If the account is corrupt, trigger a password reset so a fresh hash is generated from the correct client-side input.","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."],"exampleFix":"// before (silent swallow, returns null on any auth failure):\ntry {\n    UserDetails userDetails = loadUserByUsername(username);\n    if (!passwordEncoder.matches(password, userDetails.getPassword())) {\n        throw new BadCredentialsException(\"密码不正确\");\n    }\n    ...\n} catch (AuthenticationException e) {\n    LOGGER.warn(\"登录异常:{}\", e.getMessage());\n}\nreturn token; // null on failure — caller cannot tell why\n\n// after (fail explicitly, keep the token null contract but log the cause):\nif (!passwordEncoder.matches(password, userDetails.getPassword())) {\n    LOGGER.warn(\"密码校验失败, username={}\", username);\n    throw new BadCredentialsException(\"用户名或密码错误\");\n}\n// client side (mirror of registration):\n// const pwd = md5(rawPassword);  axios.post('/sso/login', { username, password: pwd })","handlingStrategy":"validation","validationCode":"// Validate the password format the portal expects (client-side MD5) before\n// even calling login(), and guard against a null stored hash.\nif (storedHash == null || storedHash.isEmpty()) {\n    throw new BadCredentialsException(\"用户名或密码错误\");\n}\nboolean ok = passwordEncoder.matches(clientHashedPassword, storedHash);\n// clientHashedPassword must equal md5(rawPassword) sent by the front-end","typeGuard":null,"tryCatchPattern":"// In UmsMemberServiceImpl.login, distinguish 'wrong password' from systemic\n// errors instead of blanket-catching AuthenticationException and returning null.\ntry {\n    if (!passwordEncoder.matches(password, userDetails.getPassword())) {\n        throw new BadCredentialsException(\"用户名或密码错误\");\n    }\n    // ... generate token ...\n} catch (BadCredentialsException e) {\n    LOGGER.warn(\"密码校验失败 username={}\", username);\n    throw e; // let the controller map it to a 401 with a clear message\n} catch (AuthenticationException e) {\n    LOGGER.error(\"登录系统异常 username={}\", username, e);\n    throw e;\n}","preventionTips":["Lock the client-side pre-hash algorithm (MD5) and the server encoder (BCryptPasswordEncoder) in one shared constant so registration and login cannot drift.","Write a unit test that registers a member with a known password and immediately logs in, to catch encoder/format regressions before deploy.","Reject null/empty password hashes at registration with a clear error so a member can never be saved unloggable.","Do not return null to signal failure — throw or return a result object so callers cannot mistake failure for success."],"tags":["spring-security","authentication","bad-credentials","mall-portal","password-encoder","bcrypt","member"],"backgroundTag":null,"analyzedSha":"0504e86b1f1b6f1b8aa6a734d37a90fb67346be7","analyzedAt":"2026-08-13T22:19:36.553Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}