我是JWT的新手。网上没有太多的信息,因为我来这里是最后的手段。我已经开发了一个使用spring session的spring Boot 应用程序。现在,我们将从 Spring 会议转向JWT。我发现几个链接,现在我可以验证用户并生成令牌。现在困难的部分是,我想创建一个过滤器,它将验证每个请求到服务器,1.过滤器将如何验证令牌?(只要验证签名就够了吗?))1.如果其他人偷了令牌并进行了休息呼叫,我将如何验证。1.如何在过滤器中绕过登录请求?因为它没有授权头。
f87krz0w1#
这里有一个过滤器,可以做你需要的事情:
public class JWTFilter extends GenericFilterBean { private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class); private final TokenProvider tokenProvider; public JWTFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = this.resolveToken(httpServletRequest); if (StringUtils.hasText(jwt)) { if (this.tokenProvider.validateToken(jwt)) { Authentication authentication = this.tokenProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(servletRequest, servletResponse); this.resetAuthenticationAfterRequest(); } catch (ExpiredJwtException eje) { LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage()); ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); LOGGER.debug("Exception " + eje.getMessage(), eje); } } private void resetAuthenticationAfterRequest() { SecurityContextHolder.getContext().setAuthentication(null); } private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { String jwt = bearerToken.substring(7, bearerToken.length()); return jwt; } return null; } }
以及过滤器链中过滤器的包含:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public final static String AUTHORIZATION_HEADER = "Authorization"; @Autowired private TokenProvider tokenProvider; @Autowired private AuthenticationProvider authenticationProvider; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(this.authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { JWTFilter customFilter = new JWTFilter(this.tokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); // @formatter:off http.authorizeRequests().antMatchers("/css/**").permitAll() .antMatchers("/images/**").permitAll() .antMatchers("/js/**").permitAll() .antMatchers("/authenticate").permitAll() .anyRequest().fullyAuthenticated() .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll() .and().logout().permitAll(); // @formatter:on http.csrf().disable(); } }
TokenProvider类:
public class TokenProvider { private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class); private static final String AUTHORITIES_KEY = "auth"; @Value("${spring.security.authentication.jwt.validity}") private long tokenValidityInMilliSeconds; @Value("${spring.security.authentication.jwt.secret}") private String secretKey; public String createToken(Authentication authentication) { String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(",")); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS); Date issueDate = Date.from(now.toInstant()); Date expirationDate = Date.from(expirationDateTime.toInstant()); return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities) .signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact(); } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody(); Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream() .map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken); return true; } catch (SignatureException e) { LOGGER.info("Invalid JWT signature: " + e.getMessage()); LOGGER.debug("Exception " + e.getMessage(), e); return false; } } }
现在回答你的问题:1.在此过滤器中完成1.保护您的HTTP请求,使用HTTPS1.只允许/login URI上的所有内容(在我的代码中为/authenticate)
/login
/authenticate
rekjcdws2#
我将集中讨论JWT的一般技巧,而不讨论代码实现(参见其他答案)过滤器将如何验证令牌?(只要验证签名就够了吗?))RFC 7519规定了如何验证JWT(参见7.2.验证一个JWT),基本上是一个语法验证和签名验证。如果在身份验证流中使用JWT,我们可以查看OpenID连接规范3.1.3.4 ID Token Validation提出的验证。总结:
iss
aud
client_id
iat
exp
sub
如果其他人偷了令牌并进行了休息呼叫,我将如何验证。拥有JWT是身份验证的证明。窃取令牌的攻击者可以模拟用户。因此,请确保令牌安全
如何在过滤器中绕过登录请求?因为它没有授权头。登录表单不需要JWT令牌,因为您将验证用户凭证。将窗体保留在筛选器的范围之外。在成功验证后发出JWT,并将验证过滤器应用于其余服务那么过滤器应该拦截除登录表单外的所有请求,并检查:1.如果用户已通过身份验证?如果不抛出401-Unauthorized1.用户是否有权请求资源?如果不抛出403-Forbidden1.允许进入。将用户数据放在请求的上下文中(例如使用ThreadLocal)
401-Unauthorized
403-Forbidden
nfg76nw03#
看看this项目,它实现得非常好,并且有所需的文档。
1.在上面的项目中,这是唯一需要验证令牌的东西,这就足够了。其中token是请求报头中Bearer的值。
token
Bearer
try { final Claims claims = Jwts.parser().setSigningKey("secretkey") .parseClaimsJws(token).getBody(); request.setAttribute("claims", claims); } catch (final SignatureException e) { throw new ServletException("Invalid token."); }
2.窃取令牌并不容易,但根据我的经验,您可以通过为每次成功登录手动创建Spring会话来保护自己。还将会话唯一ID和Bearer值(令牌)Map到Map中(例如,使用API范围创建Bean)。
@Component public class SessionMapBean { private Map<String, String> jwtSessionMap; private Map<String, Boolean> sessionsForInvalidation; public SessionMapBean() { this.jwtSessionMap = new HashMap<String, String>(); this.sessionsForInvalidation = new HashMap<String, Boolean>(); } public Map<String, String> getJwtSessionMap() { return jwtSessionMap; } public void setJwtSessionMap(Map<String, String> jwtSessionMap) { this.jwtSessionMap = jwtSessionMap; } public Map<String, Boolean> getSessionsForInvalidation() { return sessionsForInvalidation; } public void setSessionsForInvalidation(Map<String, Boolean> sessionsForInvalidation) { this.sessionsForInvalidation = sessionsForInvalidation; } }
此SessionMapBean将可用于所有会话。现在,对于每个请求,您不仅要验证令牌,还要检查他是否计算会话(检查请求会话ID是否与存储在SessionMapBean中的会话ID匹配)。当然,会话ID也可能被盗,因此您需要保护通信。最常见的窃取会话ID的方式有Session Sniffing(或者中间人)和跨站脚本攻击。我不会去更多的细节,你可以阅读如何保护自己免受这种攻击。
SessionMapBean
**3.**你可以在我链接的项目中看到它。最简单的是,过滤器将验证所有/api/*,例如,您将登录到/user/login。
/api/*
/user/login
hxzsmxv24#
我使用了一种简单的方法来处理RequestFilter中的JWT异常。
RequestFiler.java
@Component public class RequestFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtUtil; @Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException { final String token = getAccessToken(request); JwtTokenValidity tokenValidity = jwtUtil.validateAccessToken(token); if (tokenValidity.isValid()) { setAuthenticationContext(token, request); filterChain.doFilter(request, response); } else { setUnauthorizedResponse(response, tokenValidity.getMessage()); } } private void setUnauthorizedResponse(HttpServletResponse response, String reason) { try { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getWriter().write(JsonUtils.toJson(ApiResponse.generateErrorResponse(HttpStatus.UNAUTHORIZED, reason))); } catch (IOException e) { logger.error(String.format(e.getMessage())); } }
JwtUtils.java
@Component public class JwtTokenUtil { public JwtTokenValidity validateAccessToken(final String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); logger.info(String.format("Token is valid/verified.")); return new JwtTokenValidity(true, "Token is valid/verified."); } catch (ExpiredJwtException ex) { logger.error("JWT expired.", ex.getMessage()); return new JwtTokenValidity(false, "Token is expired."); } catch (IllegalArgumentException ex) { logger.error("Token is null, empty or only whitespace.", ex.getMessage()); return new JwtTokenValidity(false, "Token is null, empty or only whitespace."); } catch (MalformedJwtException ex) { logger.error("Token is invalid.", ex); return new JwtTokenValidity(false, "Token is invalid."); } catch (UnsupportedJwtException ex) { logger.error("JWT is not supported.", ex); return new JwtTokenValidity(false, "JWT is not supported."); } catch (SignatureException ex) { logger.error("Signature validation failed."); return new JwtTokenValidity(false, "JWT Signature validation failed."); } } public static class JwtTokenValidity { private boolean isValid; private String message; public JwtTokenValidity(boolean isValid, String message) { this.isValid = isValid; this.message = message; } public boolean isValid() { return isValid; } public void setValid(boolean isValid) { this.isValid = isValid; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } }
4条答案
按热度按时间f87krz0w1#
这里有一个过滤器,可以做你需要的事情:
以及过滤器链中过滤器的包含:
TokenProvider类:
现在回答你的问题:
1.在此过滤器中完成
1.保护您的HTTP请求,使用HTTPS
1.只允许
/login
URI上的所有内容(在我的代码中为/authenticate
)rekjcdws2#
我将集中讨论JWT的一般技巧,而不讨论代码实现(参见其他答案)
过滤器将如何验证令牌?(只要验证签名就够了吗?))
RFC 7519规定了如何验证JWT(参见7.2.验证一个JWT),基本上是一个语法验证和签名验证。
如果在身份验证流中使用JWT,我们可以查看OpenID连接规范3.1.3.4 ID Token Validation提出的验证。总结:
iss
包含颁发者标识符(如果使用oauth,则aud
包含client_id
)iat
和exp
之间的当前时间sub
标识有效用户如果其他人偷了令牌并进行了休息呼叫,我将如何验证。
拥有JWT是身份验证的证明。窃取令牌的攻击者可以模拟用户。因此,请确保令牌安全
如何在过滤器中绕过登录请求?因为它没有授权头。
登录表单不需要JWT令牌,因为您将验证用户凭证。将窗体保留在筛选器的范围之外。在成功验证后发出JWT,并将验证过滤器应用于其余服务
那么过滤器应该拦截除登录表单外的所有请求,并检查:
1.如果用户已通过身份验证?如果不抛出
401-Unauthorized
1.用户是否有权请求资源?如果不抛出
403-Forbidden
1.允许进入。将用户数据放在请求的上下文中(例如使用ThreadLocal)
nfg76nw03#
看看this项目,它实现得非常好,并且有所需的文档。
1.在上面的项目中,这是唯一需要验证令牌的东西,这就足够了。其中
token
是请求报头中Bearer
的值。2.窃取令牌并不容易,但根据我的经验,您可以通过为每次成功登录手动创建Spring会话来保护自己。还将会话唯一ID和Bearer值(令牌)Map到Map中(例如,使用API范围创建Bean)。
此
SessionMapBean
将可用于所有会话。现在,对于每个请求,您不仅要验证令牌,还要检查他是否计算会话(检查请求会话ID是否与存储在SessionMapBean
中的会话ID匹配)。当然,会话ID也可能被盗,因此您需要保护通信。最常见的窃取会话ID的方式有Session Sniffing(或者中间人)和跨站脚本攻击。我不会去更多的细节,你可以阅读如何保护自己免受这种攻击。**3.**你可以在我链接的项目中看到它。最简单的是,过滤器将验证所有
/api/*
,例如,您将登录到/user/login
。hxzsmxv24#
我使用了一种简单的方法来处理RequestFilter中的JWT异常。
RequestFiler.java
JwtUtils.java