dromara/Sa-Token · error · SaOAuth2Exception

30141

30141

Error message

应用未开放的 grant_type: 

What it means

Thrown by SaOAuth2Strategy.grantTypeAuth after clientSecret/scope validation: the grant type is valid and globally enabled, but this client's allowGrantTypes does not include it. Note the code assigned here is 30141 (the 'system not enabled' code) even though the message says the client has not enabled it — a code-reuse quirk in the library. Error code 30141.

Source

Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/strategy/SaOAuth2Strategy.java:210

		}

		// 针对 authorization_code 与 password 两种特殊 grant_type,需要判断全局是否开启
		SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig();
		if(grantType.equals(GrantType.authorization_code) && !config.getEnableAuthorizationCode() ) {
			throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30126);
		}
		if(grantType.equals(GrantType.password) && !config.getEnablePassword() ) {
			throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30126);
		}

		// 校验 clientSecret 和 scope
		ClientIdAndSecretModel clientIdAndSecretModel = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req);
		List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(SaOAuth2Consts.Param.scope));
		SaClientModel clientModel = SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientIdAndSecretModel.getClientId(), clientIdAndSecretModel.getClientSecret(), scopes);

		// 检测应用是否开启此 grantType
		if(!clientModel.getAllowGrantTypes().contains(grantType)) {
			throw new SaOAuth2Exception("应用未开放的 grant_type: " + grantType).setCode(SaOAuth2ErrorCode.CODE_30141);
		}

		// 调用 处理器构建 Access-Token
		return grantTypeHandler.getAccessToken(req, clientIdAndSecretModel.getClientId(), scopes);
	};


	// ------------------ 凭证创建 ------------------

	/**
	 * 创建一个 code value
	 */
	public SaOAuth2CreateCodeValueFunction createCodeValue = (clientId, loginId, scopes) -> {
		return SaFoxUtil.getRandomString(60);
	};

	/**
	 * 创建一个 AccessToken value

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Add the needed grant type to the client's allowGrantTypes registration
  2. Alternatively switch the caller to a grant type the client already allows
  3. When catching, do not rely solely on code 30141 to distinguish system-level vs client-level disabling — inspect the message or the client config

Example fix

// before
new SaClientModel()
    .setClientId("1001")
    .setAllowGrantTypes(Collections.singletonList("authorization_code"));

// after
new SaClientModel()
    .setClientId("1001")
    .setAllowGrantTypes(Arrays.asList("authorization_code", "password"));
Defensive patterns

Strategy: validation

Validate before calling

SaClientModel cm = oauth2Template.checkClientModel(clientId);
if(!cm.getAllowGrantTypes().contains(grantType)) {
    throw new IllegalStateException("client " + clientId + " not registered for grant " + grantType);
}

Try / catch

catch(SaOAuth2Exception e) {
    // note: library reuses 30141 here; match on message too
    if("30141".equals(e.getCode()) && e.getMessage().contains("应用未开放")) return 403 "client not permitted";
}

Prevention

When it happens

Trigger: POST /oauth2/token with grant_type=password for a client registered with allowGrantTypes=[authorization_code]; any grant request whose type is missing from the client's registered list.

Common situations: Client provisioned only for web-server flow but a mobile or legacy integration uses password grant; client registry entries defined per environment with divergent allowGrantTypes.

Related errors


AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14). Data as JSON: /api/errors/4607c668e1df34e4. Report an issue: GitHub.