Spring Session中的JSON Web Token

3

我在生产环境中有三个服务器以及一个全局负载均衡器。当第一个请求到来时,请求将被验证并生成JWT令牌,并且该令牌将被存储在Spring会话中的服务器中。但是,当新的请求来临并进入不同的服务器时,我被注销了。

我应该如何利用这里的资源?我阅读了一些关于在Redis中存储令牌的文章,但我不明白如何确切地使用它,或者我在这里有哪些不同选择?

在开发环境中,我只有一个服务器,所以无法测试它。

有人可以给我提供一些实际的例子,解决这个问题的具体方法是什么?


你好,欢迎加入社区!如果您在集群环境中没有使用由数据库支持的Spring Session,则会出现这种情况,因为会话与应用程序容器绑定。服务器必须有一个真实来源来检查给定主体是否存在会话。这个链接 https://www.baeldung.com/spring-session-jdbc 将向您展示如何轻松配置Spring Session JDBC,或者这个链接 https://www.javainuse.com/spring/springboot_session_redis 用于Redis实现。最后,您可以通过在不同端口上本地运行应用程序来模拟您的应用程序的N个实例来测试它。 - junbetterway
@junbetterway 我们也可以使用 cookie 来实现这个功能,就像创建一个 cookie 一样,我们需要在响应中将其发送给客户端,这样能够解决这个问题。 - Learners
1
Spring Session提供了一个默认实现,使用DefaultCookieSerializer,您可以通过访问以下链接进一步自定义它:https://docs.spring.io/spring-session/reference/api.html#api-cookieserializer - junbetterway
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
0
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private JWTRequestFilter jwtRequestFilter;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        ...
        }
    }

JWT 工具文件,包含所有与 JWT 验证和生成相关的函数

@Component
public class JWTUtil {

    public static final long EXPIRATION_TIME = 60 * 60 * 24;
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";

    @Value("${jwt.secret}")
    private String secret;

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(secret.getBytes()).build().parseClaimsJws(token).getBody();
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    private String doGenerateToken(Map<String, Object> claims, String username) {
        SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
        return Jwts.builder().setClaims(claims)
                .setSubject(username).setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME * 1000))
                .signWith(key).compact();
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public boolean validateToken(String token, String requestUsername) {
        final String username = getUsernameFromToken(token);
        return (username.equals(requestUsername) && !isTokenExpired(token));
    }
}

Jwt的一个重要部分是筛选器文件,它包含需要筛选或不需要筛选的URL。

@Service
public class JWTRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JWTUtil jwtUtil;

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    Set<String> urls = new HashSet<String>(Arrays.asList("/api/login", "/api/register", "/api/"));

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getRequestURI();
        return urls.contains(path);
    }

    protected boolean shouldNotFilterErrorDispatch() {
        return true;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String jwtToken = null;
        String username = null;

        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals("Authorization")) {
                jwtToken = cookie.getValue();
            }
        }

        if (jwtToken != null) {
            try {
                username = jwtUtil.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                System.out.println("JWT Token has expired");
            }
        } else {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }

        if (username != null) {
            UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
            if (!jwtUtil.validateToken(jwtToken, userDetails.getUsername())) {
                logger.warn("JWT Token validation failed");
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        doFilter(request, response, filterChain);
    }
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,