【问题标题】:How to log when jwt expired in spring boot如何在spring boot中jwt过期时记录
【发布时间】:2026-01-23 00:40:01
【问题描述】:

我想在登录和注销时登录用户,以及在 jwt 令牌过期时登录。要做到这一点,我在发生ExpiredJwtException 时调用loginHistoryService.saveUserLogout(),但问题是这个方法调用了多个,因为当用户打开表单时,这个表单包含很多ajax 请求,所以每个请求都会导致到此过滤器并多次记录用户注销。 有没有更好的方法来实现这个要求?

public class JWTAuthorizationFilter extends OncePerRequestFilter {

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

        if (!request.getRequestURI().contains("/signout")) {
            try {
                if (checkJWTToken(request)) {
                    Claims claims = validateToken(request);
                    if (claims.get(CLAIM_AUTHORITIES_KEY) != null) {
                        setUpSpringAuthentication(claims);
                    }
                    else {
                        SecurityContextHolder.clearContext();
                    }
                }
                else {
                    SecurityContextHolder.clearContext();
                }
                chain.doFilter(request, response);
            } catch (ExpiredJwtException e) {
                // call log method here, but this method called more than one
                loginHistoryService.saveUserLogout();
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.setHeader("ExpiredJwt", "true");
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your JWT token expired!");
            } catch (UnsupportedJwtException | MalformedJwtException e) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "forbidden!");
            }
        }
        else {
            chain.doFilter(request, response);
        }
    }
}

更新 1

loginHistoryService 的目的是存储 LoginHistory 具有以下声明的对象:

@Entity
@Table(name = "tbl_login_history")
public class LoginHistory extends BaseEntity<Long> {

    @Column(name = "user_name", nullable = false)
    private String userName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @Column(name = "is_success", nullable = false)
    private boolean success;

    @Column(name = "is_login", nullable = false)
    private boolean login;

    @Column(name = "browser_type")
    private BrowserType browserType;

    @Column(name = "browser_name")
    private String browserName;

    @Column(name = "operating_system_type")
    private OperatingSystemType operatingSystemType;

    @Column(name = "operating_system_name")
    private String operatingSystemName;

    @Column(name = "login_failure_reason")
    private LoginFailureReason loginFailureReason;
}

审计用户登录和注销。

更新 2

我更改了LoginHistory并添加了jwtToken字段来存储jwt并检查是否存在记录,然后不要插入。但问题是它的记录插入了多次。

@Service
public class LoginHistoryService implements ILoginHistoryService {

    @Autowired
    private ILoginHistoryRepository loginHistoryRepository;

    @Override
    @Transactional
    public void saveLogWhenExpiredUser(String token) {
        if (!loginHistoryRepository.checkExpiredTokenExistence(token)) {
            // save the log record
        }
    }
}

这是检查记录存在的方法:

@Override
public boolean checkExpiredTokenExistence(String token) {
    String hql =    " select cast((case when count(*) = 0 then 0 else 1 end) as boolean) " +
                    " from " + domainClass.getName() + " e " +
                    " where e.jwtToken = :jwtToken " +
                    "   and e.logoutType = :logoutType " ;

    Map<String, Object> params = new HashMap<>();
    params.put("jwtToken", token.getBytes());
    params.put("logoutType", LogOutType.Expired);

    return super.find(hql, params);
}

【问题讨论】:

    标签: spring spring-boot logging jwt


    【解决方案1】:

    因为当用户打开表单时,这个表单包含很多 assets 和 ajax 请求,所以每个请求都会导致去这个过滤器 并多次记录用户注销

    您的安全过滤器不得考虑您的公共资产。请忽略对您的公共资产的安全检查

    【讨论】:

    • 对不起,这是我的错,更新了一个问题。我忽略了资产,但是我可以用多个 ajax 请求做什么?
    • 您的loginHistoryService.saveUserLogout() 的目的是什么?让用户登录?
    • 在您的班级LoginHistory 中,您可以例如添加某个字段jwt,该字段使用特定的jwt 令牌记录用户。仅在未找到当前令牌的记录时检查重复并保存活动
    • 谢谢@dm_tr,我想到了,但我想知道是否有更好的方法或其他概念来实现此要求?
    • 也许有更好的方法来做到这一点。但是从语义上这也是一个很好的方法
    【解决方案2】:

    只需添加对jwtTokenlogoutType 唯一性的约束:

    @Entity
    @Table(uniqueConstraints={
        @UniqueConstraint(columnNames = {"jwtToken", "logoutType"})
    }, name = "tbl_login_history")
    public class LoginHistory extends BaseEntity<Long> {
        ...
    }
    

    【讨论】:

      最近更新 更多