java 如何将JWT标记从容器应用程序同步到子应用程序的会话

eufgjt7s  于 2022-10-30  发布在  Java
关注(0)|答案(3)|浏览(137)

bounty已结束。回答此问题可获得+50的声望奖励。奖励宽限期将在20小时后结束。iPhoneJavaDev正在寻找来自声誉良好来源的答案

我们正在实施一个微前端、微服务架构。
App 1是构建在React上的微前端app - ui,后端构建在Sping Boot 上。它处理身份验证并向其子应用提供令牌。令牌使用Jwts生成,如下所示:

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

App 2是微前端设置的子应用。它的UI构建在React上,后端构建在Sping Boot 上。App 1通过react-iframe连接App 2,同时传递令牌,如下所示:

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

useEffect上的App 2检查window.location.search是否具有token字段,并使用此字段在其安全上下文中设置身份验证。这是通过调用App 2中的终结点/user来完成的。然后,App 2后端将调用App 1中的终结点/validate以检查令牌是否有效。如果令牌有效,App 2解析该标记并创建一个Authentication对象,然后将其保存到其上下文,如下所示:

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

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

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

这是因为/user被调用一次来检查令牌是否有效,并且App 2上的会话是否被初始化,所以对于后续的请求,它将检查它是否具有适当的权限。
问题是,App 2上的会话与令牌上设置的过期时间不同。我们如何将App 2上的会话的过期时间与App 1提供的令牌同步?

wmtdaxz3

wmtdaxz31#

这里有几件事;
1.您将以明文形式公开令牌即使当使用HTTPS/TLS时,该URL本身也总是明文的,并且可以被网络上任一端的任何人看到,或者可以被沿着将网络分组从客户端中继到服务器的物理设备的中间方看到,任何人都可以看到加入同一个wifi网络的令牌,但最大的问题是URL中的敏感信息泄露这些URL记录在任何地方,在同一设备上其他软件可以访问的未受保护文件中,在沿着网络路径上的所有设备上(甚至可能通过您认为无法读取此流量的国家/地区进行路由),在服务器上创建相同的日志,通常也不受保护,而且这些日志几乎总是发送到日志服务,如Datadog、SUMO、(其他数百人),他们也在这些日志的URL中看到了此敏感令牌。
底线是,永远不要在URL中发送任何敏感信息。
1.您正在为客户机实现哪种JWT方案?

a)是否存在由客户端生成的私钥和发送到服务器的公钥,以形成加密的JWT?或者;
B)它是否使用HMAC(本质上是伴随数据的签名)来提供完整性检查,该检查仅确保数据未被修改,这种类型的JWT没有加密,因此JWT通过设计可由任何人完全“读取”
c)客户端上根本没有实际的身份验证,为客户端提供了JWT以避免必须进行身份验证。
如果(a),则客户端生成了密钥/密钥对,并且需要将公钥发送到服务器,而不是服务器与客户端共享任何内容。
如果(B),则客户端需要共享密钥来完成签名,它将在每次向微服务端点发出请求时生成新的JWT“令牌”。

因此,你不给予客户端一个令牌,客户端使用共享密钥生成(或者说构造)它自己的签名(令牌)。服务器也有共享密钥,并且也可以在服务器上构造相同的签名(令牌),而不需要通过通信信道将密钥与数据一起发送。
您将需要为客户机找到一个带外方法来获取共享密钥,因为如果使用JWT,则以相同的通信方法发送共享密钥将无法实现整个目的。

**如果(c)**这不是JWT的用例。如果您没有在客户端上进行实际的身份验证,那么就不能使用作为身份验证机制的一种形式而建立的安全特性来证明客户端是“可信的”。从服务器向客户端端点传递字符串只是服务器上的身份验证,当客户端稍后使用您的方法进行通信时,它会声称自己是进行“身份验证”的“可信”服务器,但事实并非如此,它是客户端。

这就是为什么JWT在这种情况下是没有意义的。如果从来没有在客户端做什么验证,那么客户端必须被视为未验证的,不需要授权(JWT),因为他们没有建立可信的身份,彼此无法区分。
如果想要客户端授权,你首先需要客户端认证。也许你在客户端认证的实现上有问题,你会发现OIDC更容易

jckbn6z7

jckbn6z72#

如果App2是子项,则App1应指定验证到期协议。为什么不直接将会话到期设置为等于标记到期?每次用户登录到App1时,对JWT进行解码并将到期作为变量传递给App2。
或者,将超时设置为到期前的时间,并以静默方式传递令牌刷新请求,直到两个到期时间几乎相等。
此外,不要以这种方式公开传递令牌。相反,传递加密的标头或cookie。或者散列令牌并将其保存到安全数据库中,以便根据需要进行查询或更新。

kxeu7u2r

kxeu7u2r3#

将JWT标记到期从容器应用程序同步到子应用程序会话的一种可能方法是在子应用程序上使用会话监听程序,该监听程序将在标记到期时使会话无效。会话监听程序是实现HttpSessionListener接口的类,可以在创建或销毁会话时执行操作。您可以通过添加**@WebListener注解或在web.xml文件中声明该注解,在子应用程序中注册会话侦听器。
要在令牌过期时使会话无效,可以在
/user端点中创建会话时将令牌过期时间存储为session属性**。然后,在会话监听程序的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);
}

相关问题