我正在尝试创建一个基于 MVC 的网站,通过 WebApi 向本地页面和外部客户端提供一些服务,因此需要 JSONP 来避免同源策略错误。问题是该网站正在使用基本身份验证,据我从其他帖子中了解到,该身份验证无法使用 JSONP。我尝试按照帖子 How do I make a JSONP call with JQuery with Basic Authentication? 中的建议在 URL 中注入(inject) user:pass ,但这不起作用,服务器返回未经授权的代码。另外,我尝试在不注入(inject)的情况下进行调用,因为我可以在浏览器中输入用户名和密码:浏览器按预期要求我提供凭据,但随后由于某种原因它们被拒绝,再次以未经授权的代码结束。然而,凭据是好的,因为我可以通过从同一域成功运行完全相同的代码来确认。谁能告诉我我的代码有什么问题吗?
我的 MVC WebApi Controller 操作如下:
[BasicAuthorize(Roles = "administrator,customer,trial")]
public class TextApiController : ApiController
{
// ...
public SomeResult Get([FromUri] SomeParams p)
{
// some processing which returns a SomeResult object
//...
}
}
其中BasicAuthorize属性是我从http://kevin-junghans.blogspot.it/2013/02/mixing-forms-authentication-basic.html修改而来的类,如下:
[AttributeUsageAttribute(AttributeTargets.Class |
AttributeTargets.Method, Inherited = true,
AllowMultiple = true)]
public sealed class BasicAuthorizeAttribute : AuthorizeAttribute
{
static private string DecodeFrom64(string sEncodedData)
{
byte[] encodedDataAsBytes = Convert.FromBase64String(sEncodedData);
return Encoding.ASCII.GetString(encodedDataAsBytes);
}
static private bool GetUserNameAndPassword(HttpActionContext context,
out string sUserName,
out string sPassword,
out bool bCookieAuthorization)
{
bCookieAuthorization = false;
bool bSuccess = false;
sUserName = sPassword = "";
IEnumerable<string> headerVals;
if (context.Request.Headers.TryGetValues("Authorization", out headerVals))
{
try
{
string sAuthHeader = headerVals.First();
string[] authHeaderTokens = sAuthHeader.Split();
if (authHeaderTokens[0].Contains("Basic"))
{
string sDecoded = DecodeFrom64(authHeaderTokens[1]);
string[] aPairMembers = sDecoded.Split(new[] { ':' });
sUserName = aPairMembers[0];
sPassword = aPairMembers[1];
}
else
{
if (authHeaderTokens.Length > 1)
sUserName = DecodeFrom64(authHeaderTokens[1]);
bCookieAuthorization = true;
}
bSuccess = true;
}
catch
{
bSuccess = false;
}
}
return bSuccess;
}
static private bool Authenticate(HttpActionContext actionContext,
out string sUserName)
{
bool bIsAuthenticated = false;
string sPassword;
bool bCookieAuthorization;
if (GetUserNameAndPassword(actionContext,
out sUserName, out sPassword, out bCookieAuthorization))
{
// if the header tells us we're using Basic auth then log the user in
if (!bCookieAuthorization)
{
if (WebSecurity.Login(sUserName, sPassword, true))
bIsAuthenticated = true;
else
WebSecurity.Logout();
}
// else get authentication from web security
else
{
if (WebSecurity.IsAuthenticated) bIsAuthenticated = true;
sUserName = WebSecurity.CurrentUserName;
}
}
else actionContext.Response =
new HttpResponseMessage(HttpStatusCode.BadRequest);
return bIsAuthenticated;
}
private bool IsAuthorized(string sUserName)
{
SimpleRoleProvider roles =
(SimpleRoleProvider)System.Web.Security.Roles.Provider;
string[] aRoles = Roles.Split(new[] {','});
return (aRoles.Any(sRole => roles.IsUserInRole(sUserName, sRole)));
}
public override void OnAuthorization(HttpActionContext actionContext)
{
string sUserName;
if (Authenticate(actionContext, out sUserName))
{
if (!IsAuthorized(sUserName))
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
}
我的客户端代码是一个简单的 HTML 页面,带有一些 Javascript(使用 jQuery),例如:
...
<form>
<fieldset>
<legend></legend>
<ol>
<li>
input text
<input type="text" id="input"/>
</li>
<li>
username
<input type="text" id="user"/>
</li>
<li>
password
<input type="password" id="password"/>
</li>
<li><a href="#" id="apip">API: JSONP</a></li>
</ol>
</fieldset>
</form>
<div id="result"></div>
<script>
function getAuthorizationHeader(username, password) {
"use strict";
var authType;
if (password == "") {
authType = "Cookie " + $.base64.encode(username);
}
else {
var up = $.base64.encode(username + ":" + password);
authType = "Basic " + up;
};
return authType;
};
function ajaxSuccessHandler(data) {
"use strict";
$("#result").text(data);
};
function ajaxErrHandler(jqXHR, textStatus, errorThrown) {
"use strict";
$("#result").text(errorThrown + " : " + textStatus);
}
$(function () {
"use strict";
$("#apip").click(function () {
"use strict";
var text = $("#input").val();
$.ajax({
url: "https://somesiteurl.com/api/wordapi?Text=" + encodeURIComponent(text),
dataType: "jsonp",
type: "GET",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", getAuthorizationHeader($("#user").val(), $("#password").val()));
},
success: ajaxSuccessHandler,
error: ajaxErrHandler
});
});
});
</script>
跨域资源共享
抱歉回复晚了...我正在按照建议尝试使用 CORS,但我肯定错过了一些明显的东西,因为我的客户发送的 header 不包含 Origin。这是我所做的,使用 http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/ 中的库:
我创建了一个新的 MVC4 互联网应用程序来测试此场景,并使用 NuGet 添加 Thinktecture.IdentityModel。
在 App_Start 中我创建了这个 CorsConfig 类:
static public class CorsConfig
{
public static void RegisterCorsForWebApi(HttpConfiguration httpConfig)
{
WebApiCorsConfiguration corsConfig = new WebApiCorsConfiguration();<p></p>
<pre><code> // this adds the CorsMessageHandler to the HttpConfiguration’s
// MessageHandlers collection
corsConfig.RegisterGlobal(httpConfig);
corsConfig
.ForResources("Products")
.ForOrigins("http://hello.net")
.AllowAll();
}
public static void RegisterCorsForMvc(MvcCorsConfiguration corsConfig)
{
corsConfig
.ForResources("Products.GetProducts")
.ForOrigins("http://hello.net")
.AllowAll();
}
</code></pre>
<p>}
</p>
在 Global.asax.cs 中我调用了该类的两个方法。
在 web.config 中添加:
我在 MVC Controller 中创建一个简单的操作方法,返回一些 JSON。我计划稍后在客户端正确调用调用后用 [Authorize] 进行装饰,这样我就可以测试身份验证(和授权、添加角色)。
在 View 中,我将我的方法称为:
var text = $("#input").val();
var json = "{'text': " + JSON.stringify(text) + "}";
$.ajax({
url: "/Home/GetSomeJson",
dataType: "json",
data: json,
type: "GET",
beforeSend: function (xhr) {
xhr.withCredentials = true;
},
crossDomain: true,
username: $("#user").val(),
password: $("#password").val(),
success: ajaxSuccessHandler,
error: ajaxErrHandler
});
然而,检查标题我发现没有来源。另外,这是为 MVC 操作/WebApi 的 CORS 调用传递凭据(当然在现实世界中这将是 HTTPS)的正确方法吗?
最佳答案
关于asp.net-mvc-4 - MVC WebApi + 基础认证 + JSONP跨域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15300696/