{"record":{"id":"bad536c1356aa0f6","repo":"macrozheng/mall","slug":"error","errorCode":null,"errorMessage":"用户名或密码错误","messagePattern":"用户名或密码错误","errorType":"exception","errorClass":"UsernameNotFoundException","httpStatus":null,"severity":"error","filePath":"mall-admin/src/main/java/com/macro/mall/service/impl/UmsAdminServiceImpl.java","lineNumber":272,"sourceCode":"        UmsAdmin umsAdmin = adminList.get(0);\n        if(!passwordEncoder.matches(param.getOldPassword(),umsAdmin.getPassword())){\n            return -3;\n        }\n        umsAdmin.setPassword(passwordEncoder.encode(param.getNewPassword()));\n        adminMapper.updateByPrimaryKey(umsAdmin);\n        getCacheService().delAdmin(umsAdmin.getId());\n        return 1;\n    }\n\n    @Override\n    public UserDetails loadUserByUsername(String username){\n        //获取用户信息\n        UmsAdmin admin = getAdminByUsername(username);\n        if (admin != null) {\n            List<UmsResource> resourceList = getResourceList(admin.getId());\n            return new AdminUserDetails(admin,resourceList);\n        }\n        throw new UsernameNotFoundException(\"用户名或密码错误\");\n    }\n\n    @Override\n    public UmsAdminCacheService getCacheService() {\n        return SpringUtil.getBean(UmsAdminCacheService.class);\n    }\n\n    @Override\n    public void logout(String username) {\n        //清空缓存中的用户相关数据\n        UmsAdmin admin = getCacheService().getAdmin(username);\n        getCacheService().delAdmin(admin.getId());\n        getCacheService().delResourceList(admin.getId());\n    }\n}\n","sourceCodeStart":254,"sourceCodeEnd":288,"githubUrl":"https://github.com/macrozheng/mall/blob/0504e86b1f1b6f1b8aa6a734d37a90fb67346be7/mall-admin/src/main/java/com/macro/mall/service/impl/UmsAdminServiceImpl.java#L254-L288","documentation":"This is the Spring Security UserDetailsService.loadUserByUsername contract implemented for the mall-admin (backend) module. Spring's DaoAuthenticationProvider calls loadUserByUsername(username) during login; the method looks up UmsAdmin via getAdminByUsername, builds an AdminUserDetails (admin + its resourceList), and only throws UsernameNotFoundException when the lookup returns null — i.e. the submitted username does not exist in the ums_admin table. Note: by default DaoAuthenticationProvider.hideUserNotFoundExceptions=true swallows this and rethrows it as BadCredentialsException, so callers usually see a generic bad-credentials error rather than this exact message.","triggerScenarios":"POST to the admin login endpoint with a username absent from ums_admin; a JWT (subject = admin username) presented for a since-deleted or renamed admin account; the Redis admin cache (UmsAdminCacheService) holding a stale null/empty entry for that username.","commonSituations":"Typo or wrong-case username at the admin login screen; admin record deleted by another operator while the browser still holds a valid token; a fresh database/migration where ums_admin was not seeded with the default admin/admin account; a misconfigured MyBatis mapper or DB connection causing getAdminByUsername to silently return null.","solutions":["Verify the username exists: SELECT id,username FROM ums_admin WHERE username = ? — confirm spelling, case, and absence of leading/trailing whitespace.","If logging in via JWT, the token's sub claim must match a current admin row; clear the client token and perform a fresh interactive login.","Confirm getAdminByUsername and the underlying UmsAdminMapper/SQL run against the correct database and that the Redis cache key is not poisoned (flush the ums_admin cache keys and retry).","If the default account is missing, re-seed ums_admin from the project's SQL (macro/mall schema) so the admin/admin seed row exists.","At the controller/login layer, catch BadCredentialsException and UsernameNotFoundException together and return one generic 'username or password incorrect' response to prevent username enumeration."],"exampleFix":"// before (in the admin login controller):\ntry {\n    authenticationManager.authenticate(\n        new UsernamePasswordAuthenticationToken(username, password));\n} catch (Exception e) {\n    throw e; // leaks which credential was wrong\n}\n\n// after:\nUmsAdmin admin;\ntry {\n    admin = adminService.login(username, password);\n} catch (BadCredentialsException | UsernameNotFoundException e) {\n    return CommonResult.validateFailed(\"用户名或密码错误\");\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate before invoking authentication, so a missing admin is a clean 404/401,\n// not a UsernameNotFoundException leaking from the security stack.\nUmsAdmin admin = adminService.getAdminByUsername(username);\nif (admin == null) {\n    return CommonResult.validateFailed(\"用户名或密码错误\");\n}","typeGuard":null,"tryCatchPattern":"// Treat UsernameNotFoundException and BadCredentialsException identically at the\n// boundary to prevent username enumeration. DaoAuthenticationProvider already\n// converts the former into the latter when hideUserNotFoundExceptions=true.\ntry {\n    Authentication auth = authenticationManager.authenticate(\n        new UsernamePasswordAuthenticationToken(username, password));\n    // ... issue JWT ...\n} catch (BadCredentialsException | UsernameNotFoundException e) {\n    return CommonResult.validateFailed(\"用户名或密码错误\");\n} catch (LockedException | DisabledException e) {\n    return CommonResult.forbidden(\"账号已被禁用\");\n}","preventionTips":["Keep hideUserNotFoundExceptions=true (Spring default) so missing users are not distinguishable from wrong passwords.","Never echo which credential was wrong — return one generic message for both cases.","Cache the negative lookup result with a short TTL only if getAdminByUsername is DB-backed and hot, and always invalidate on admin create/rename/delete.","Log authentication failures with the username but never the password, and rate-limit by username/IP to slow brute force."],"tags":["spring-security","authentication","user-details-service","mall-admin","username-not-found"],"backgroundTag":null,"analyzedSha":"0504e86b1f1b6f1b8aa6a734d37a90fb67346be7","analyzedAt":"2026-08-13T22:19:36.553Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}