WebFlux: JWT Token Authenticator

huangapple go评论78阅读模式
英文:

WebFlux: JWT Token Authenticator

问题

我们正在尝试使用JWT来保护我们的API。我们在应用程序开发中使用了WebFlux。我们的需求是从消费者那里读取JWT令牌,从JWT中提取证书并进行验证。

我们在应用程序中使用过滤器来处理TraceId。对于JWT身份验证,我最好的方法是什么?
是使用另一个过滤器吗?还是可以与现有的TraceId过滤器链接在一起?Spring是否提供了JWT身份验证的解决方案?

以下是我用于Trace Id过滤器的代码。

@Component
@Slf4j
public class TraceIdFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Map<String, String> headers = exchange.getRequest().getHeaders().toSingleValueMap();

        return chain.filter(exchange)
                .subscriberContext(context -> {
                    var traceId = "";
                    if (headers.containsKey("X-B3-TRACEID")) {
                        traceId = headers.get("X-B3-TRACEID");
                        MDC.put("X-B3-TraceId", traceId);
                    } else if (!exchange.getRequest().getURI().getPath().contains("/actuator")) {
                        log.warn("TRACE_ID not present in header: {}", exchange.getRequest().getURI());
                    }

                    // 简单的 hack,以便使用交换提供上下文,以便整个链可以获取相同的 trace id
                    Context contextTmp = context.put("X-B3-TraceId", traceId);
                    exchange.getAttributes().put("X-B3-TraceId", traceId);

                    return contextTmp;
                });

    }
}
英文:

We are trying to secure our api with JWT.We are using webflux for our app development.Our requirement is to read the JWT token coming from the consumer and extract the certificate from JWT and validate.

We are using filter for TraceId in our application.What is my best approach to JWT Authentication?
using another filter? or can i chain with existing TraceId Filter.Does spring provide any solutions for JWT Authentication ?

Here is the code I am using for Trace Id Filter.

@Component
@Slf4j
public class TraceIdFilter implements WebFilter {

    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, WebFilterChain chain) {
        Map&lt;String, String&gt; headers = exchange.getRequest().getHeaders().toSingleValueMap();

        return chain.filter(exchange)
                .subscriberContext(context -&gt; {
                    var traceId = &quot;&quot;;
                    if (headers.containsKey(&quot;X-B3-TRACEID&quot;)) {
                        traceId = headers.get(&quot;X-B3-TRACEID&quot;);
                        MDC.put(&quot;X-B3-TraceId&quot;, traceId);
                    } else if (!exchange.getRequest().getURI().getPath().contains(&quot;/actuator&quot;)) {
                        log.warn(&quot;TRACE_ID not present in header: {}&quot;, exchange.getRequest().getURI());
                    }

                    // simple hack to provide the context with the exchange, so the whole chain can get the same trace id
                    Context contextTmp = context.put(&quot;X-B3-TraceId&quot;, traceId);
                    exchange.getAttributes().put(&quot;X-B3-TraceId&quot;, traceId);

                    return contextTmp;
                });

    }


} 

答案1

得分: 1

在Java项目中处理JWT时,建议使用以下依赖项:

// https://mvnrepository.com/artifact/com.auth0/java-jwt
compile group: 'com.auth0', name: 'java-jwt', version: '3.11.0';

我个人偏向使用 implementation 而不是 compile,并且倾向于使用版本号中的 "+"。

由于你正在使用RSA,你需要在与你的代码片段中的代码附近建立一个类似这样的变量,最好是在 TraceIdFilter 类中:

RSAPublicKey publicKey;

假设你能够获取公钥的内容并将其放入名为 publicKeyStr 的字符串中,你可以按以下方式初始化你的公钥对象:

try {
    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
    publicKey = (RSAPublicKey)KeyFactory.getInstance("RSA").generatePublic(pubKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
    // 根据情况处理异常
}

在你的过滤器代码中,你可以这样查看JWT内容(使用 token 作为保存JWT令牌的字符串):

DecodedJWT decodedJwt = null;
try {			
    decodedJwt = JWT.require(Algorithm.RSA512(publicKey, null))
            .build()
            .verify(token);
} catch (JWTVerificationException e) {
    // 如果你的密钥有效,则令牌无效,因此根据情况处理
}

注意,JWT令牌有时可能包含诸如 iatexp 等字段,两者都接受日期,如果当前时间在 exp 之后或在 iat 之前,验证将失败。

如果验证成功并且你有一个有效的 DecodedJWT 对象,那么你可以查看令牌中的各种特性。

例如,如果你正在寻找一个名为 "valid" 的声明,并且期望它是布尔类型,你可以这样访问布尔值:

Claim idClaim = decodedJwt.getClaim("valid");
Boolean idBool = idClaim.asBoolean();

请注意,如果令牌中没有这样的声明,idClaim 将为null,如果它不是布尔值(或无法转换为布尔值),idBool 将为null。

以下是用于代码的一些导入:

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
英文:

When working with JWT in a Java project, it is recommended to use this dependency:

// https://mvnrepository.com/artifact/com.auth0/java-jwt
compile group: &#39;com.auth0&#39;, name: &#39;java-jwt&#39;, version: &#39;3.11.0&#39;

I personally use implementation instead of compile and tend to use "+" for the version.

Since you're using RSA, you'll need to establish a variable like this near the code you're workign with, ideally within the TraceIdFilter class from your code snippet.

RSAPublicKey publicKey;

Assuming you're able to get the contents of the public Key into a string called publicKeyStr you'll want to initialize your public Key Object like so:

try {
    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
    publicKey = (RSAPublicKey)KeyFactory.getInstance(&quot;RSA&quot;).generatePublic(pubKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e)
{
    // Handle Exception accordingly
}

In your filter code, you can explore the JWT contents like so (using token as the String holding your JWT token:

DecodedJWT decodedJwt = null;
		try
		{			
			decodedJwt = JWT.require(Algorithm.RSA512(publicKey,null))
					.build()
					.verify(token);
		}
		catch(JWTVerificationException e)
		{
		    // If your key is valid, then the token is invalid, so handle accordingly
		}

Note that JWT tokens can sometimes contain fields such as iat and exp, both of which take in dates and if the current time is after exp or before iat, then verification will fail.

should it succeed and you have a valid DecodedJWT object, then you can view various features available in the token.

For instance, if you are looking for a "valid" claim and expect it to be of a boolean type, you can access the boolean like-so.

Claim idClaim = decodedJwt.getClaim(&quot;valid&quot;);
		
Boolean idBool = idClaim.asBoolean();

Bear in mind, if no such claim is in the token, idClaim will be null and if it is not a Boolean value (or can't be converted to one), idBool will be null.

Here is a link to more of the Playload interface, which DecodedJWT extends.
JWT Payload (this link works better in Firefox than in Edge Chromium)

Not knowing what the Certificate within the JWT token is supposed to look like, hopefully, you'll be able to find it using the Claims feature of this dependency.

Most of my answer focuses on the JWT aspect, but I found a Stackoverflow Question that might help that might inspire you regarding the WebFilter aspect of your query.

Here are some of the imports used for the code:

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;

答案2

得分: 1

我的示例是hantsy/spring-reactive-jwt-sample

使用FILTER来填充SecurityContextHolder,并在安全过滤器链中注册它

英文:

My example is hantsy/spring-reactive-jwt-sample.

User a Fitler to populate SecurityContextHolder, register it in the Secuirty Filter Chain.

huangapple
  • 本文由 发表于 2020年10月6日 07:14:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/64217364.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定