是否有可能注销用户从一个网站,如果他是使用基本身份验证?

杀死会话是不够的,因为一旦用户通过身份验证,每个请求都包含登录信息,因此用户下次使用相同的凭据访问站点时将自动登录。

目前唯一的解决方案是关闭浏览器,但从可用性的角度来看,这是不可接受的。


当前回答

实际上,我认为基本身份验证的目的是用于静态页面,而不是用于任何复杂的会话管理或CGI页面。

因此,当需要会话管理时,你应该设计一个经典的“登录表单”来查询用户和密码(可能也是第二个因素)。 CGI表单处理程序应该将成功的身份验证转换为服务器上记住的会话(ID)(在cookie中或作为URI的一部分)。

然后,通过使服务器(和客户端)“忘记”会话。 另一个优点是(即使加密后)用户和密码不会随每个请求一起发送到服务器(而是发送会话ID)。

如果服务器上的会话ID与执行的“最后一个动作”的时间戳相结合,则会话超时可以通过比较该时间戳与当前时间来实现: 如果时间跨度太大,则通过忘记会话ID来“超时”会话。

对无效会话的任何请求都将导致重定向到登录页面(或者如果您想让它更舒服,您可以使用“重新验证表单”再次请求密码)。

作为概念的证明,我实现了一个完全无cookie的会话管理,它完全基于URI(会话ID始终是URI的一部分)。 然而,完整的代码对于这个答案来说太长了。

当希望处理数千个并发会话时,必须特别注意性能。

其他回答

bobince对这个问题的补充回答是…

使用Ajax,您可以将“注销”链接/按钮连接到Javascript函数。让这个函数发送带有错误用户名和密码的XMLHttpRequest。这应该会得到401。然后设置文档。位置返回到预登录页面。这样,用户将永远不会在注销期间看到额外的登录对话框,也不必记得输入错误的凭据。

对于使用Windows身份验证(也称为协商、Kerberos或NTLM身份验证)的任何人,我使用ASP。NET Core和Angular。

我找到了一种有效的方式来改变用户!

我修改我的登录方法在javascript方面,就像这样:

protected login(changeUser: boolean = false): Observable<AuthInfo> {
  let params = new HttpParams();
  if(changeUser) {
    let dateNow = this.datePipe.transform(new Date(), 'yyyy-MM-dd HH:mm:ss');
    params = params.set('changeUser', dateNow!);
  }
  const url: string = `${environment.yourAppsApiUrl}/Auth/login`;
  return this.http.get<AuthInfo>(url, { params: params });
}

下面是我在后台的方法:

[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
[Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
public class AuthController : Controller
{
  [HttpGet("login")]
  public async Task<IActionResult> Login(DateTime? changeUser = null)
  {
      if (changeUser > DateTime.Now.AddSeconds(-3))
          return Unauthorized();

      ...
      ... (login process)
      ...

      return Ok(await _authService.GetToken());
    }
}

return Unauthorized()返回401代码,导致浏览器识别弹出窗口出现,以下是过程:

如果我想更改用户,我现在将日期作为参数传输。 如果从那一刻起不超过3秒,我就返回401代码。 我完成了我的凭证,并将具有相同参数的相同请求发送到后端。 由于已经过去了3秒多,我继续登录过程,但这次使用的是新的凭据!

正如其他人所说,我们需要获得相同的URL并发送一个错误(例如401:StatusUnauthorized之类的),仅此而已。

我使用Get方法让它知道我需要登出,

下面是一个使用golang进行写作的完整示例。

package main

import (
    "crypto/subtle"
    "fmt"
    "log"
    "net/http"
)

func BasicAuth(username, password, realm string, handlerFunc http.HandlerFunc) http.HandlerFunc {

    return func(w http.ResponseWriter, r *http.Request) {
        queryMap := r.URL.Query()
        if _, ok := queryMap["logout"]; ok { // localhost:8080/public/?logout
            w.WriteHeader(http.StatusUnauthorized) // 401
            _, _ = w.Write([]byte("Success logout!\n"))
            return
        }

        user, pass, ok := r.BasicAuth()

        if !ok ||
            subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 ||
            subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`", charset="UTF-8"`)
            w.WriteHeader(http.StatusUnauthorized)
            _, _ = w.Write([]byte("Unauthorised.\n"))
            return
        }

        handlerFunc(w, r)
    }
}

type UserInfo struct {
    name string
    psw  string
}

func main() {

    portNumber := "8080"
    guest := UserInfo{"guest", "123"}

    // localhost:8080/public/  -> ./public/everyone
    publicHandler := http.StripPrefix(
        "/public/", http.FileServer(http.Dir("./public/everyone")),
    )

    publicHandlerFunc := func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            publicHandler.ServeHTTP(w, r)
        /*
            case http.MethodPost:
            case http.MethodPut:
            case http.MethodDelete:
        */
        default:
            return
        }
    }

    http.HandleFunc("/public/",
        BasicAuth(guest.name, guest.psw, "Please enter your username and password for this site",
            publicHandlerFunc),
    )

    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", portNumber), nil))
}

当您已经登出时,您需要刷新(F5)页面。否则,您可能会看到旧内容。

基本身份验证不是为管理注销而设计的。你可以这样做,但不是完全自动的。

你所要做的就是让用户点击登出链接,然后发送一个“401未授权”作为响应,使用与你发送的请求登录的正常401相同的域和URL文件夹级别。

接下来,他们必须被引导输入错误的凭证。一个空白的用户名和密码,作为回应,您发回一个“您已成功注销”页面。错误的/空白的凭证将覆盖之前的正确凭证。

简而言之,登出脚本颠倒了登录脚本的逻辑,只有在用户没有传递正确的凭据时才返回成功页面。

问题是这个有点奇怪的“不要输入密码”的密码框是否能让用户接受。试图自动填充密码的密码管理器也会在这里起到阻碍作用。

编辑添加以回应评论:重新登录是一个稍微不同的问题(除非你显然需要两步注销/登录)。您必须拒绝(401)第一次访问重新登录链接的尝试,然后接受第二次(可能有不同的用户名/密码)。有几种方法可以做到这一点。一种方法是在登出链接中包含当前用户名。/relogin?username),并在凭据与用户名匹配时拒绝。

为了记录,有一个新的HTTP响应头叫做Clear-Site-Data。如果你的服务器回复包含一个Clear-Site-Data: "cookies"头,那么身份验证凭证(不仅仅是cookies)应该被删除。我在Chrome 77上测试了它,但控制台显示了这个警告:

Clear-Site-Data header on 'https://localhost:9443/clear': Cleared data types:
"cookies". Clearing channel IDs and HTTP authentication cache is currently not
supported, as it breaks active network connections.

并且认证凭证没有被删除,所以这不能(目前)实现基本的认证注销,但也许将来会。没有在其他浏览器上测试。

引用:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

https://www.w3.org/TR/clear-site-data/

https://github.com/w3c/webappsec-clear-site-data

https://caniuse.com/#feat=mdn-http_headers_clear-site-data_cookies