问题
I am working on a small .net core app that uses JWT authentication and websockets.
I have succesfully implemented generating and validating tokens for standard web api controllers. However I also want to validate the token for a WebSocket
request which of course won't work with the [Authorize]
attribute.
I have setup my middleware pipeline like this:
app.UseWebSockets();
app.Use(async (http, next) => {
if (http.WebSockets.IsWebSocketRequest == false) {
await next();
return;
}
/// Handle websocket request here. How to check if token is valid?
});
// secretKey contains a secret passphrase only your server knows
var secretKey = .....;
var signKey = new SigningCredentials (
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)),
SecurityAlgorithms.HmacSha256
);
var tokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = false,
ValidateAudience = false,
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signKey.Key,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.FromMinutes(1),
};
app.UseJwtBearerAuthentication(new JwtBearerOptions {
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
回答1:
I hope this can help someone, even though the post is kind of old.
I found out the answer, not after Googling, but Binging ! I inspired myself from this official code.
You can write your own class that handles authorization very simply, using the magic of JwtBearerOptions. This class (hopefully) contains everything you need to validate the JWTs by yourself.
So, you have to inject it as a Service, as well as using it to configure your Authentication. Something like that in your Startup.ConfigureServices
:
this.JwtOptions = new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = yourTokenValidationParameters
};
services.AddSingleton<JwtBearerOptions>(this.JwtOptions);
Then, you have to create a class that is gonna Validate your Token (This is where my code was inspired). Let's call it a Backer, because he's got your back !:
public class JwtBearerBacker
{
public JwtBearerOptions Options { get; private set; }
public JwtBearerBacker(JwtBearerOptions options)
{
this.Options = options;
}
public bool IsJwtValid(string token)
{
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, Options.TokenValidationParameters, out validatedToken);
}
catch (Exception ex)
{
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
validationFailures = new List<Exception>(1);
validationFailures.Add(ex);
continue;
}
return true;
}
}
return false;
}
}
Then, in your Middleware, just access the request header, the JwtOptions
dependency and call the Backer:
protected string ObtainAppTokenFromHeader(string authHeader)
{
if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.Contains(" "))
return null;
string[] authSchemeAndJwt = authHeader.Split(' ');
string authScheme = authSchemeAndJwt[0];
if (authScheme != "Bearer")
return null;
string jwt = authSchemeAndJwt[1];
return jwt;
}
protected async Task<bool> AuthorizeUserFromHttpContext(HttpContext context)
{
var jwtBearerOptions = context.RequestServices.GetRequiredService<JwtBearerOptions>() as JwtBearerOptions;
string jwt = this.ObtainAppTokenFromHeader(context.Request.Headers["Authorization"]);
if (jwt == null)
return false;
var jwtBacker = new JwtBearerBacker(jwtBearerOptions);
return jwtBacker.IsJwtValid(jwt);
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
return;
if (!await this.AuthorizeUserFromHttpContext(context))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("The door is locked, dude. You're not authorized !");
return;
}
//... Whatever else you're doing in your middleware
}
Also, the AuthenticationTicket
and any other information about the auth has been already handled by the framework's JwtBearerMiddleware
, and will be returned anyway.
Finally, the client side. I advise you using a client library that actually supports additional HTTP Headers. For example, as far as I know, the W3C Javascript client does not provide this functionality.
Here you are! Thanks to Microsoft for its Open Source codebase.
回答2:
I worked this out slightly different way as I was depending on WebSocket()
on the client side.
So on the client side first I authenticate user to get token and attach it to the header as subprotocol:
socket = new WebSocket(connectionPath, ["client",token]);
Token is sent within request header under sec-websocket-protocol
key.
So before authentication kicks in I extract token and append it to the context.
.AddJwtBearer(x =>
{
// ....
x.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Headers.ContainsKey("sec-websocket-protocol") && context.HttpContext.WebSockets.IsWebSocketRequest)
{
var token = context.Request.Headers["sec-websocket-protocol"].ToString();
// token arrives as string = "client, xxxxxxxxxxxxxxxxxxxxx"
context.Token = token.Substring(token.IndexOf(',') + 1).Trim();
context.Request.Headers["sec-websocket-protocol"] = "client";
}
return Task.CompletedTask;
}
};
Then on my WebSocket controller I simply stick [Authorize]
attribute:
[Authorize]
[Route("api/[controller]")]
public class WSController : Controller
{
[HttpGet]
public async Task Get()
{
var context = ControllerContext.HttpContext;
WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync("client"); // it's important to make sure the response returns the same subprotocol
// ...
}
来源:https://stackoverflow.com/questions/41785249/how-to-validate-jwt-during-websocket-request-net-core