如何将 JWT 令牌从容器应用同步到子应用的会话



我们正在实现一个微平台、微服务架构。

App1是基于React构建的微平台应用程序ui,是基于spring引导构建的后端。它处理身份验证并将令牌提供给它的子应用程序。令牌是使用Jwts生成的,如下所示:

Jwts.build().setClaims(claims).setSubject(username).setExpiration(expirationDate)...

App2是microfrontend设置的子应用程序。它的ui构建在React上,后端构建在spring-boot上。App1通过react iframe连接App2,同时按如下方式传递令牌:

<Iframe url={`${urlOfApp2}`?token={jwtToken}} ... />

useEffect上的App2检查window.location.search是否具有token字段,并使用该字段在其安全上下文中设置Authentication。这是通过调用App2中的端点/user来完成的。然后,App2后端将从App1调用端点/validate来检查令牌是否有效。如果它是有效的,App2解析令牌并创建一个Authentication对象,并将其保存到其上下文中,如下所示:

final Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);

这将创建JSESSIONID。因此,每次请求来自App2的端点(例如/someendpoint)时,它都会检查该请求是否具有上面代码中设置的所需权限。安全配置如下:

http...
.antMatchers("/user").permitAll()
.anyRequest().hasAuthority("SOME_AUTHORITY_PARSED_FROM_THE_TOKEN")...

这是因为/user被调用一次以检查令牌是否有效,并且App2上的会话是否初始化。因此,对于后续请求,它将检查自己是否拥有适当的权限。

问题是,与令牌上设置的会话相比,App2上的会话具有不同的过期时间。我们如何将App2上会话的过期时间与App1提供的令牌同步?

这里有几件事;

  1. 您在cleartext(iframeurl)中公开了令牌。即使使用HTTPS/TLS,该URL本身也始终是明文的,并且可以被网络两端的任何人或将网络数据包从客户端中继到服务器的物理设备上的中间方看到,任何人都可以看到加入同一个wifi网络的令牌作为一个例子,但在URL中暴露敏感信息的最大问题是,这些URL被记录在任何地方,在其他软件可以访问的未受保护的文件中的同一设备上,在沿途网络路径上的所有设备上(甚至可能通过你不认为可以读取这些流量的国家路由),在服务器上,同样的日志被创建,但通常也不受保护,这些日志几乎总是被发送到日志服务,如Datadog、SUMO(数百其他人),他们也从这些日志中的URL中看到了这个敏感令牌

最重要的是,永远不要在URL中发送任何敏感信息。

  1. 您正在为客户实施哪种JWT方案

a)是否存在客户端生成的私钥和发送到服务器以形成加密JWT的公钥?或

b)是否使用HMAC,HMAC

c)客户端上根本没有发生任何实际的身份验证,为客户端提供JWT以避免必须这样做。

如果(a),则客户端生成一个密钥/对,并且需要将公钥发送到服务器,而不是服务器与客户端共享任何内容。

如果(b)则客户端需要共享密钥来完成签名,则每次向微服务端点发出请求时,它都会生成JWT"令牌"本身。

因此,您不向客户端提供令牌,客户端使用共享密钥生成(或者更确切地说构造)自己的签名(令牌)。服务器还具有共享的秘密,并且还可以在服务器上构造相同的签名(令牌),而无需通过通信信道将秘密与数据一起发送。

您需要为客户端找到一种带外方法来获取共享机密,因为如果使用JWT,以相同的通信方法发送机密将无法实现整个目的。

If(c)这不是JWT的用例。如果您没有对客户端进行实际的身份验证,那么客户端就不会被证明是"可信的",使用的安全特性是一种身份验证机制。从服务器向客户端端点传递字符串只是服务器上的身份验证,当客户端稍后使用您的方法进行通信时,它声称自己是"经过身份验证"的"真实"服务器,但事实并非如此,它是客户端。。

这就是为什么JWT在这种情况下没有意义。如果您从来没有在客户端进行身份验证,那么客户端必须被视为未经身份验证,不需要授权(JWT),因为他们没有建立真实身份,彼此无法区分。

如果您确实想要客户端授权,则需要首先进行客户端身份验证。也许你在客户端上实现身份验证时遇到了问题,你可能会发现OIDC更容易

如果App2是子级,那么App1应该规定身份验证过期协议。为什么不将会话到期时间设置为等于令牌到期时间呢?每次用户登录App1时,对JWT进行解码,并将过期时间作为变量传递给App2。

或者将超时设置为等于到期前的瞬间,并静默地传递令牌刷新请求,直到两个到期时间几乎相等。

此外,不要以这种方式公开传递代币。相反,传递加密的标头或cookie。或者散列令牌并将其保存到安全数据库中,以便根据需要进行查询或更新。

将JWT令牌过期从容器应用程序同步到子应用程序的会话的一种可能方法是在子应用程序上使用会话侦听器,该侦听器将在令牌过期时使会话失效。会话侦听器是一个实现HttpSessionListener接口的类,可以在创建或销毁会话时执行操作。您可以通过添加@WebListener注释或在web.xml文件中声明会话侦听器,在子应用程序中注册会话侦听器。

要在令牌过期时使会话无效,可以在/user端点中创建会话时将令牌过期时间存储为会话属性。然后,在会话侦听器的sessionDestroyed方法中,可以将当前时间与存储的过期时间进行比较,并在令牌过期时使会话无效。这样,子应用程序在令牌到期后将不接受来自会话的任何请求,并且将需要来自容器应用程序的新令牌。

示例

以下是一个会话侦听器类的示例,该类在令牌过期时使会话无效:

@WebListener
public class TokenExpirationSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
// do nothing
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// get the session and the token expiration time
HttpSession session = se.getSession();
Long tokenExpiration = (Long) session.getAttribute("tokenExpiration");
// check if the token has expired
if (tokenExpiration != null && System.currentTimeMillis() > tokenExpiration) {
// invalidate the session
session.invalidate();
}
}
}

以下是如何将令牌过期时间作为会话属性存储在/user端点中的示例:

// get the token from the query parameter
String token = request.getParameter("token");
// validate the token with the container app
boolean isValid = validateToken(token);
// if the token is valid, parse it and create the authentication object
if (isValid) {
// parse the token and get the expiration time
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
Long expiration = claims.getExpiration().getTime();
// get the username and authorities from the claims
String username = claims.getSubject();
List<GrantedAuthority> authorities = getAuthoritiesFromClaims(claims);
// create the authentication object and set it to the security context
Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
// get the session and store the token expiration time as a session attribute
HttpSession session = request.getSession();
session.setAttribute("tokenExpiration", expiration);
}

最新更新