使Web API身份验证返回401而不是重定向到登录页面

时间:2016-01-19 15:36:31

标签: c# asp.net asp.net-mvc authentication asp.net-web-api

我在Web MVC中使用带有OWIN身份验证的Web API。 我在Web.Config中使用<authentication>用于我的Web MVC,因此它重定向到登录页面。

<authentication mode="Forms">
    <forms name="WEB.AUTH" loginUrl="~/login" domain="" protection="All" 
    timeout="43200" path="/" requireSSL="false" slidingExpiration="true" />
</authentication>

我使用[System.Web.Http.Authorize]属性来授权我的Web API。但不知何故,由于上述配置,API重定向到登录页面,就像我的MVC应用程序一样。

我想要做的是继续重定向Web MVC的功能,但返回401 for Web API。我怎样才能做到这一点?我应该为Web API创建自定义授权属性吗?

- 编辑 -

我在帖子SuppressDefaultHostAuthentication in WebApi.Owin also suppressing authentication outside webapi

中找到了答案

所以我只需在我的Startup.cs中加几行。我把所有的控制器配置为&#34; api&#34;前缀路由。

HttpConfiguration config = new HttpConfiguration();
//..some OWIN configuration
app.Map("/api", inner =>
{
  inner.UseWebApi(config);
});

确保在Web Api配置行之后放置app.Map()。否则,它将给MVC应用程序带来错误。

7 个答案:

答案 0 :(得分:2)

创建自定义AuthorizeAttribute

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unauthorized");
    }
}

如果您以后跳过web.config内容并使用owin设置身份验证,则可以在Startup.cs执行:

var provider = new CookieAuthenticationProvider();
var originalHandler = provider.OnApplyRedirect;
provider.OnApplyRedirect = context =>
{
    if (!context.Request.Uri.LocalPath.StartsWith(VirtualPathUtility.ToAbsolute("~/api")))
    {
        context.RedirectUri = new Uri(context.RedirectUri).PathAndQuery;
        originalHandler.Invoke(context);
    }
};

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    CookieName = FormsAuthentication.FormsCookieName,
    LoginPath = new PathString("/Account/LogOn"),
    ExpireTimeSpan = TimeSpan.FromMinutes(240),
    Provider = provider
});

答案 1 :(得分:0)

这对我有用。

创建自定义属性:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class NoRedirectAuthorizeAttribute : AuthorizeAttribute
{        
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
    }
}

使用控制器中的属性:

    [HttpDelete]
    [NoRedirectAuthorizeAttribute(Roles = "Admin")]
    [Route("api/v3/thingstodelete/{id=id}")]
    public IHttpActionResult DeleteThingToDelete(Guid id)
    {
      //delete code
    }

这里只是覆盖了AuthorizeAttribute的HandleUnauthorizedRequest方法。因此,我们不发送重定向(304)到登录页面,而是发送Forbidden(403)HTTP状态代码。

答案 2 :(得分:0)

为了根据URL定义的约定更改IIS的行为方式,您需要分支OWIN管道。您可以使用IApplicationBuilder.Map执行此操作。假设静态config

public void Configure(IApplicationBuilder app)
{
    ...
    app.Map("/api", HandleWebApiRequests);
    ...
}

private static void HandleWebApiRequests(IApplicationBuilder app)
{
    app.UseWebApi(config);
}

Map方法根据以HandleWebApiRequests开头的网址将管道分支到"/api"方法。

这应该导致401错误的行为与它们应该的一样,并且只返回401而没有重定向。

答案 3 :(得分:0)

我需要配置StatusCodePage中间件以避免重定向

@Override
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
        throws ServletException, IOException {
    resp.setContentType(MediaType.OCTET_STREAM.type());
    resp.setStatus(HttpServletResponse.SC_OK);
    resp.setBufferSize(4096);
    resp.flushBuffer();
    final AsyncContext async = req.startAsync();
    async.setTimeout(5_000); // millis
    final ServletOutputStream output = resp.getOutputStream();
    final QueueWriteListener writeListener = new QueueWriteListener(async, output);
    async.addListener(writeListener);
    output.setWriteListener(writeListener);
}

private static class QueueWriteListener implements AsyncListener, WriteListener {

    private static final Logger logger = LoggerFactory.getLogger(QueueWriteListener.class);

    private final AsyncContext asyncContext;
    private final ServletOutputStream output;

    @GuardedBy("this")
    private boolean completed = false;

    public QueueWriteListener(final AsyncContext asyncContext, final ServletOutputStream output) {
        this.asyncContext = checkNotNull(asyncContext, "asyncContext cannot be null");
        this.output = checkNotNull(output, "output cannot be null");
    }

    @Override
    public void onWritePossible() throws IOException {
        writeImpl();
    }

    private synchronized void writeImpl() throws IOException {
        if (completed) {
            return;
        }
        while (output.isReady()) {
            final byte[] message = getNextMessage();
            if (message == null) {
                output.flush();
                return;
            }
            output.write(message);
        }
    }

    private synchronized void completeImpl() {
        // also stops DataFeederThread to call bufferArrived
        completed = true;
        asyncContext.complete();
    }

    @Override
    public void onError(final Throwable t) {
        logger.error("Writer.onError", t);
        completeImpl();
    }

    public void dataArrived() {
        try {
            writeImpl();
        } catch (RuntimeException | IOException e) {
            ...
        }
    }

    public void noMoreData() {
        completeImpl();
    }

    @Override
    public synchronized void onComplete(final AsyncEvent event) throws IOException {
        completed = true; // might not needed but does not hurt
    }

    @Override
    public synchronized void onTimeout(final AsyncEvent event) throws IOException {
        completeImpl();
    }

    @Override
    public void onError(final AsyncEvent event) throws IOException {
        logger.error("onError", event.getThrowable());
    }

    ...
}

答案 4 :(得分:0)

在.NET Core中,我已经像Startup.cs这样解决了它:

    public void ConfigureServices(IServiceCollection services)
    {
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.Cookie.SameSite = SameSiteMode.Strict;
                options.Cookie.Name = "AuthCookie";
                options.Events.OnRedirectToAccessDenied = UnAuthorizedResponse;
                options.Events.OnRedirectToLogin = UnAuthorizedResponse;
            })
    ....
    }

    internal static Task UnAuthorizedResponse(RedirectContext<CookieAuthenticationOptions> context)
    {
        context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
        return Task.CompletedTask;
    }

答案 5 :(得分:0)

我在这个问题上苦苦挣扎,并且想出了一种方法,该方法仅在找不到用于WebApi的自定义手动授权的标头中的令牌时才进行重定向。这是我的设置(请注意Provider对象和OnApplyRedirect操作)

    app.UseCookieAuthentication(new CookieAuthenticationOptions
   {
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
     LoginPath = new PathString("/Account/Login"),
     ExpireTimeSpan = TimeSpan.FromMinutes(30),
     Provider =  new CookieAuthenticationProvider
      {
        OnApplyRedirect = (ctx) => {
            var token = HttpContext.Current.Request.Headers.Get("X-User-Token");
            if (token == null) ctx.Response.Redirect(ctx.RedirectUri);
         }
      }
   });  

答案 6 :(得分:0)

默认情况下,wep api 首先检查 cookie,但为了从 cookie 更改为 jwt,我使用以下属性

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

控制器上方