Springセキュリティでトークンを更新するにはどうすればよいですか?

Aug 15 2020

この行:

Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();

jwtトークンの有効期限が切れると、次のようなエラーがスローされます。

JWTは2020-05-13T07:50:39Zに期限切れになりました。現在の時刻:2020-05-16T21:29:41Z。

より具体的には、「ExpiredJwtException」例外をスローするのはこの関数です。

これらの例外を処理するにはどうすればよいですか?それらをキャッチしてクライアントにエラーメッセージを送り返し、強制的に再ログインさせる必要がありますか?

トークンの更新機能を実装するにはどうすればよいですか?私はバックエンドでSpringとmysqlを使用し、フロントエンドでvuejsを使用しています。

次のように初期トークンを生成します。

   @Override
        public JSONObject login(AuthenticationRequest authreq) {
            JSONObject json = new JSONObject();
    
            try {
                Authentication authentication = authenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
    
                UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
                List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
                        .collect(Collectors.toList());
    
                if (userDetails != null) {
    
                    final String jwt = jwtTokenUtil.generateToken(userDetails);
    
    
                    JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
                            userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
    
                    return json.put("jwtresponse", jwtres);
                }
            } catch (BadCredentialsException ex) {
                json.put("status", "badcredentials");
            } catch (LockedException ex) {
                json.put("status", "LockedException");
            } catch (DisabledException ex) {
                json.put("status", "DisabledException");
            }
    
            return json;
        }

そして、JwtUtilクラスでは:

   public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            return createToken(claims, userDetails.getUsername());
        }
    
   private String createToken(Map<String, Object> claims, String subject) {
            return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
                    .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
        }

詳細については、すべてのリクエストをフィルタリングするdoFilterInternal関数を次に示します。

   @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {

        try {

            final String authorizationHeader = request.getHeader("Authorization");

            String username = null;
            String jwt = null;

            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                jwt = authorizationHeader.substring(7);
                username = jwtUtil.extractUsername(jwt);
            }

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userService.loadUserByUsername(username);

                boolean correct = jwtUtil.validateToken(jwt, userDetails);

                if (correct) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());

                    usernamePasswordAuthenticationToken
                            .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

                }
            }

            chain.doFilter(request, response);
        } catch (ExpiredJwtException ex) {
            resolver.resolveException(request, response, null, ex);
        }
    } 

回答

3 doctore Aug 16 2020 at 17:38

このような状況に対処するには、主に2つのアプローチがあります。


アクセスを管理し、トークンを更新します

この場合、フローは次のようになります。

  1. ユーザーがアプリケーションにログインします(usernameおよびを含むpassword

  2. バックエンドアプリケーションは、必要な資格情報を返し、次のことを行います。

    2.1通常「低」(15、30分など)の有効期限が切れたJWTトークンにアクセスします

    2.2アクセスよりも有効期限が長いJWTトークン更新します。

  3. これ以降、フロントエンドアプリケーションはすべてのリクエストのヘッダーで使用access tokenしますAuthorization

バックエンドが戻る401と、フロントエンドアプリケーションはrefresh token、ユーザーに再度ログインを強制することなく、(特定のエンドポイントを使用して)新しい資格情報を取得しようとします。

更新トークンフロー (これは単なる例であり、通常は更新トークンのみが送信されます)

問題がなければ、ユーザーはアプリケーションを使い続けることができます。バックエンドが新しい401=>を返す場合、フロントエンドはログインページにリダイレクトする必要があります。


1つのJwtトークンのみを管理する

この場合、フローは前のフローと同様であり、そのような状況に対処するために独自のエンドポイントを作成できます:(/auth/token/extendたとえば)リクエストのパラメーターとして期限切れのJwtを含めます。

今、それはあなたが管理することです:

  • 期限切れのJwtトークンがそれを延長するために「有効」になる時間はどれくらいですか?

新しいエンドポイントは、前のセクションの更新と同様の動作をします。つまり401、フロントエンドの観点からは、フローは同じになるため、新しいJwtトークンなどが返されます。


重要なことの1つは、従うアプローチとは関係なく、セキュリティを自分で管理するため、「新しいエンドポイント」を必要なSpring認証済みエンドポイントから除外する必要があります。

public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  ..

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.
      ..
      .authorizeRequests()
      // List of services do not require authentication
      .antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
      // Any other request must be authenticated
      .anyRequest().authenticated()
      ..
   }
}
2 QuocTruong Aug 16 2020 at 18:18

以下のように、更新トークンを取得するためのAPIを呼び出すことができます

POST https://yourdomain.com/oauth/token 

Header
  "Authorization": "Bearer [base64encode(clientId:clientSecret)]" 

Parameters
  "grant_type": "refresh_token"
  "refresh_token": "[yourRefreshToken]"

注意してください、

  • base64encodeは、クライアント認証を暗号化する方法です。あなたはでオンラインで使用することができますhttps://www.base64encode.org/
  • refresh_tokenは、の文字列値ですgrant_type
  • yourRefreshTokenは、JWTアクセストークンで受信した更新トークンです。

結果は次のように見ることができます

{
    "token_type":"bearer",
    "access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
    "expires_in":20,
    "refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}

がんばろう。