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

  1. Verify the username/password against your user store independently of OAuth2 (e.g. direct login endpoint)
  2. 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
  3. 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

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.