如何编写 Azure API 管理策略以允许范围或角色
How do I author Azure API Management policy to allow either a scope or a role
我想限制 API 一组使用作用域的 AD 用户和一组使用应用角色的守护程序应用。但是,遵循 Azure APIM 策略将仅检查是否存在两个声明。我如何重写以下策略以允许范围或应用程序角色出现在 JWT 令牌中:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
<claim name="roles" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
我的想法是结合choose when
和jwt-validate
,这是我的策略,它可以选择验证scp或角色,但我不知道为什么它不能正确验证值,我真的不是apim专家
<inbound>
<base />
<set-variable name="isScp" value="@{
string isScp = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
if(jwt.Claims.GetValueOrDefault("scp", "null") != "null"){
isScp = "true";
}
}
}
}
return isScp;
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("isScp") == "false")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for roles">
<required-claims>
<claim name="roles" match="any">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</when>
<otherwise>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for scp">
<required-claims>
<claim name="scp" match="any" separator=" ">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</otherwise>
</choose>
</inbound>
对了,验证也可以写在代码里,不用validate-jwt
,那就是:
<inbound>
<base />
<set-variable name="pass" value="@{
bool isContainScp = false;
bool isContainRoles = false;
string pass = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
string tempScp = jwt.Claims.GetValueOrDefault("scp", "null");
if(tempScp != "null"){
isContainScp = tempScp.Contains("User.ReadWrite.All");
}else{
//write logic here
isContainRoles = true;
}
}
}
}
if(isContainScp || isContainRoles){
pass = "true";
}
return pass;
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("pass") == "false")">
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</when>
<otherwise />
</choose>
</inbound>
感谢Tiny Wang 给我样片。 authHeader 已经包含令牌。因此,不需要进一步拆分它来获取令牌。这是更新的一个:
<inbound>
<base />
<set-variable name="clientType" value="@{
bool isAuthorized = false;
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length == 0) {
return "No Authorization Header";
}
Jwt jwt;
if (!authHeader.TryParseJwt(out jwt)) {
return "Parse JWT Token failed";
}
bool isPublicClient = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("0");
bool isClientSecret = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("1");
bool isClientCertificate = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("2");
if (isPublicClient) {
return "Public Client";
} else if (isClientSecret) {
return "Client Secret";
} else if (isClientCertificate) {
return "Client Certificate";
}
return "Unauthorized";
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("clientType") == "Public Client")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/YourTenantHere/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your audience here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/YourTenantHere/v2.0</issuer>
</issuers>
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<when condition="@(context.Variables.GetValueOrDefault("clientType") == "Client Secret")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/your tenant here/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your Audience Here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/Your tenant here/v2.0</issuer>
</issuers>
<required-claims>
<claim name="roles" match="any">
<value>App.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<otherwise>
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</otherwise>
</choose>
</inbound>
实现 JWT 角色声明的最佳途径是在 validate-jwt 策略中添加 'output-token-variable-name' 属性。
这是一个例子(这里要求的声明是 'emails' 但同样的想法):
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-scheme="Bearer" output-token-variable-name="jwt">
<openid-config url="https://csmsorg.b2clogin.com/csmsorg.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_susi_reset_v2" />
<audiences>
<audience>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx</audience>
</audiences>
<issuers>
<issuer>https://xxxxxx.b2clogin.com/e748014c-129c-4d13-accc-e5e1bb7c38eb/v2.0/</issuer>
</issuers>
<required-claims>
<claim name="tfp" match="any">
<value>B2C_1_susi_reset_v2</value>
<value>B2C_1__SUSI</value>
</claim>
</required-claims>
</validate-jwt>
<choose>
<when condition="@(!((Jwt)context.Variables["jwt"]).Claims["emails"].Contains("xxxxxx@gmail.com"))">
<return-response>
<set-status code="403" reason="Forbidden" />
</return-response>
</when>
</choose>
</inbound>
我想限制 API 一组使用作用域的 AD 用户和一组使用应用角色的守护程序应用。但是,遵循 Azure APIM 策略将仅检查是否存在两个声明。我如何重写以下策略以允许范围或应用程序角色出现在 JWT 令牌中:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
<claim name="roles" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
我的想法是结合choose when
和jwt-validate
,这是我的策略,它可以选择验证scp或角色,但我不知道为什么它不能正确验证值,我真的不是apim专家
<inbound>
<base />
<set-variable name="isScp" value="@{
string isScp = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
if(jwt.Claims.GetValueOrDefault("scp", "null") != "null"){
isScp = "true";
}
}
}
}
return isScp;
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("isScp") == "false")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for roles">
<required-claims>
<claim name="roles" match="any">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</when>
<otherwise>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Server is unavailable for scp">
<required-claims>
<claim name="scp" match="any" separator=" ">
<value>User.ReadWrite.All</value>
</claim>
</required-claims>
</validate-jwt>
</otherwise>
</choose>
</inbound>
对了,验证也可以写在代码里,不用validate-jwt
,那就是:
<inbound>
<base />
<set-variable name="pass" value="@{
bool isContainScp = false;
bool isContainRoles = false;
string pass = "false";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
string tempScp = jwt.Claims.GetValueOrDefault("scp", "null");
if(tempScp != "null"){
isContainScp = tempScp.Contains("User.ReadWrite.All");
}else{
//write logic here
isContainRoles = true;
}
}
}
}
if(isContainScp || isContainRoles){
pass = "true";
}
return pass;
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("pass") == "false")">
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</when>
<otherwise />
</choose>
</inbound>
感谢Tiny Wang 给我样片。 authHeader 已经包含令牌。因此,不需要进一步拆分它来获取令牌。这是更新的一个:
<inbound>
<base />
<set-variable name="clientType" value="@{
bool isAuthorized = false;
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length == 0) {
return "No Authorization Header";
}
Jwt jwt;
if (!authHeader.TryParseJwt(out jwt)) {
return "Parse JWT Token failed";
}
bool isPublicClient = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("0");
bool isClientSecret = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("1");
bool isClientCertificate = jwt.Claims.GetValueOrDefault("azpacr", "null").Equals("2");
if (isPublicClient) {
return "Public Client";
} else if (isClientSecret) {
return "Client Secret";
} else if (isClientCertificate) {
return "Client Certificate";
}
return "Unauthorized";
}" />
<choose>
<when condition="@(context.Variables.GetValueOrDefault("clientType") == "Public Client")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/YourTenantHere/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your audience here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/YourTenantHere/v2.0</issuer>
</issuers>
<required-claims>
<claim name="scp" match="any">
<value>API.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<when condition="@(context.Variables.GetValueOrDefault("clientType") == "Client Secret")">
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized by APIM policy." require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/your tenant here/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>Your Audience Here</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/Your tenant here/v2.0</issuer>
</issuers>
<required-claims>
<claim name="roles" match="any">
<value>App.Request.Get</value>
</claim>
</required-claims>
</validate-jwt>
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
</return-response>
</when>
<otherwise>
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized hhh" />
</return-response>
</otherwise>
</choose>
</inbound>
实现 JWT 角色声明的最佳途径是在 validate-jwt 策略中添加 'output-token-variable-name' 属性。 这是一个例子(这里要求的声明是 'emails' 但同样的想法):
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-scheme="Bearer" output-token-variable-name="jwt">
<openid-config url="https://csmsorg.b2clogin.com/csmsorg.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_susi_reset_v2" />
<audiences>
<audience>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx</audience>
</audiences>
<issuers>
<issuer>https://xxxxxx.b2clogin.com/e748014c-129c-4d13-accc-e5e1bb7c38eb/v2.0/</issuer>
</issuers>
<required-claims>
<claim name="tfp" match="any">
<value>B2C_1_susi_reset_v2</value>
<value>B2C_1__SUSI</value>
</claim>
</required-claims>
</validate-jwt>
<choose>
<when condition="@(!((Jwt)context.Variables["jwt"]).Claims["emails"].Contains("xxxxxx@gmail.com"))">
<return-response>
<set-status code="403" reason="Forbidden" />
</return-response>
</when>
</choose>
</inbound>