如何在Spring Security中刷新令牌 [英] How can I refresh tokens in Spring security

查看:505
本文介绍了如何在Spring Security中刷新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此行:

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

当我的jwt令牌过期时,会引发如下错误:

Throws an error like this when my jwt token expires:

JWT在2020-05-13T07:50:39Z到期.当前时间: 2020-05-16T21:29:41Z.

JWT expired at 2020-05-13T07:50:39Z. Current time: 2020-05-16T21:29:41Z.

更具体地说,正是这个函数抛出了"ExpiredJwtException".例外 :

More specifically, it is this function that throws the "ExpiredJwtException" exception :

我该如何处理这些异常?我应该抓住它们并将错误消息发送回客户端并强迫他们重新登录吗?

How do I go about handling these exceptions? Should I catch them and send back to the client an error message and force them to re-login?

如何实现刷新令牌功能?我在后端使用Spring和mysql,在前端使用vuejs.

How can I implement a refresh tokens feature? I'm using Spring and mysql in the backend and vuejs in the front end.

我这样生成初始令牌:

   @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类中:

And then in the JwtUtil class:

   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函数,用于过滤每个请求:

For more info, here is my doFilterInternal function that filters every request:

   @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);
        }
    } 

推荐答案

有两种主要方法可以解决此类情况:

There are 2 main approaches to deal with such situations:

在这种情况下,流程如下:

In this case, the flow is the following one:

  1. 用户登录到应用程序(包括usernamepassword)

您的后端应用程序将返回所有必需的凭据信息,并且:

Your backend application returns any required credentials information and:

2.1 访问JWT令牌,其到期时间通常为低", (15、30分钟等).

2.1 Access JWT token with an expired time usually "low" (15, 30 minutes, etc).

2.2 刷新JWT令牌,其过期时间大于访问时间.

2.2 Refresh JWT token with an expired time greater than access one.

从现在开始,您的前端应用程序将对每个请求在Authorization标头中使用access token.

From now, your frontend application will use access token in the Authorization header for every request.

当后端返回401时,前端应用程序将尝试使用refresh token(使用特定端点)来获取新的凭据,而不会强制用户再次登录.

When backend returns 401, the frontend application will try to use refresh token (using an specific endpoint) to get new credentials, without forcing the user to login again.

刷新令牌流 (这只是一个示例,通常仅发送刷新令牌)

如果没有问题,则用户将能够继续使用该应用程序.如果后端返回新的401 =>前端应重定向到登录页面.

If there is no problem, then the user will be able to continue using the application. If backend returns a new 401 => frontend should redirect to login page.

在这种情况下,流程类似于上一个流程,您可以创建自己的端点来处理以下情况:/auth/token/extend(例如),其中包括过期的Jwt作为请求的参数.

In this case, the flow is similar to the previous one and you can create your own endpoint to deal with such situations: /auth/token/extend (for example), including the expired Jwt as parameter of the request.

现在由您决定:

  • 已过期的Jwt令牌多少时间将是有效的"?扩展它?

新端点在上一节中将具有类似的刷新行为,我的意思是,它将返回一个新的Jwt令牌或401,因此,从前端的角度来看,流程是相同的.

The new endpoint will have a similar behaviour of refresh one in the previous section, I mean, will return a new Jwt token or 401 so, from the point of view of frontend the flow will be the same.

一件重要的事情,与您要遵循的方法无关,新端点"应该从必需的Spring身份验证端点中排除,因为您将自己管理安全性:

One important thing, independently of the approach you want to follow, the "new endpoint" should be excluded from the required Spring authenticated endpoints, because you will manage the security by yourself:

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()
      ..
   }
}

这篇关于如何在Spring Security中刷新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆