dromara/Sa-Token · error · SaOAuth2Exception
30161
30161
Error message
登录失败
What it means
Thrown by PasswordGrantTypeHandler when password-mode login produces no loginId. The handler reads username/password, delegates to the configured password login function, and if the returned PasswordAuthResult has a null loginId the grant is aborted. Error code 30161.
Source
Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/granttype/handler/PasswordGrantTypeHandler.java:56
public class PasswordGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface {
@Override
public String getHandlerGrantType() {
return GrantType.password;
}
@Override
public AccessTokenModel getAccessToken(SaRequest req, String clientId, List<String> scopes) {
// 1、获取请求参数
String username = req.getParamNotNull(SaOAuth2Consts.Param.username);
String password = req.getParamNotNull(SaOAuth2Consts.Param.password);
// 2、调用API 开始登录,如果没能成功登录,则直接退出
PasswordAuthResult passwordAuthResult = loginByUsernamePassword(username, password);
Object loginId = passwordAuthResult.getLoginId();
if(loginId == null) {
throw new SaOAuth2Exception("登录失败").setCode(SaOAuth2ErrorCode.CODE_30161);
}
// 3、构建 ra 对象
RequestAuthModel ra = new RequestAuthModel();
ra.clientId = clientId;
ra.loginId = loginId;
ra.scopes = scopes;
// 4、生成 Access-Token
AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true, atm -> atm.grantType = GrantType.password);
return at;
}
/**
* 根据 username、password 进行登录,如果登录失败请直接抛出异常或返回 loginId = null
* @param username /
* @param password /
*/View on GitHub (pinned to ac2c7f6e94)
Solutions
- Verify the username/password against your user store independently of OAuth2 (e.g. direct login endpoint)
- If you customized the password login function (SaOAuth2ServerConfig / strategy), make it return a PasswordAuthResult with a real loginId on success and a clear exception on failure
- Enable sa-token logging to see whether your login function was invoked and what it returned
Example fix
// before
config.setPasswordLoginFunction((username, password) -> {
return null; // bad credentials silently become 30161
});
// after
config.setPasswordLoginFunction((username, password) -> {
Object loginId = userService.checkLogin(username, password);
if(loginId == null) { throw new SaOAuth2Exception("Invalid username or password"); }
return new PasswordAuthResult(loginId);
}); Defensive patterns
Strategy: try-catch
Validate before calling
// validate credentials against your store before requesting the token
if(userService.findIdByUsernamePassword(username, password) == null) {
return ResponseEntity.status(401).body("invalid credentials");
} Try / catch
try {
token = oauth2Client.passwordToken(username, password);
} catch(SaOAuth2Exception e) {
if("30161".equals(e.getCode())) return 401 with 'login failed' message;
throw e;
} Prevention
- Make the custom password login function throw a specific exception on bad credentials instead of returning null
- Integration-test the password login function independently of the OAuth2 layer
When it happens
Trigger: POST /oauth2/token with grant_type=password where the username or password is wrong, the custom doLogin function returns null/throws silently, or password auth (SaOAuth2Handle) was wired to a function that never sets loginId.
Common situations: Developer overrode SaOAuth2Strategy or config passwordLoginFunction but returned null on bad credentials; user account disabled or password hash mismatch in the underlying user store; the integration test uses a non-existent user.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/5180d3ee791cccd1.
Report an issue: GitHub.