Spring Boot安全+jwt

8yparm6h  于 2021-07-14  发布在  Java
关注(0)|答案(2)|浏览(341)

**赏金明天到期。回答此问题可获得+250声望奖励。卡恩修道院çada正在寻找一个可靠的答案。

我有一个springboot2.4.2应用程序,它使用jsonweb令牌(jwt,有时发音为/d)ʒɒt/,与英语单词“jot”[1])相同,是一个internet提议的标准,用于创建具有可选签名和/或可选加密的数据,其有效负载包含json,该jsonAssert了一些声明。令牌使用私钥或公钥/私钥进行签名。例如,服务器可以生成声明为“以管理员身份登录”的令牌,并将其提供给客户端。然后,客户机可以使用该令牌来证明它是以admin身份登录的。
这是我的网站安全配置:

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  5. private static final String SALT = "fd23451*(_)nof";
  6. private final JwtAuthenticationEntryPoint unauthorizedHandler;
  7. private final JwtTokenUtil jwtTokenUtil;
  8. private final UserSecurityService userSecurityService;
  9. @Value("${jwt.header}")
  10. private String tokenHeader;
  11. public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,
  12. UserSecurityService userSecurityService) {
  13. this.unauthorizedHandler = unauthorizedHandler;
  14. this.jwtTokenUtil = jwtTokenUtil;
  15. this.userSecurityService = userSecurityService;
  16. }
  17. @Autowired
  18. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  19. auth
  20. .userDetailsService(userSecurityService)
  21. .passwordEncoder(passwordEncoder());
  22. }
  23. @Bean
  24. public BCryptPasswordEncoder passwordEncoder() {
  25. return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
  26. }
  27. @Bean
  28. @Override
  29. public AuthenticationManager authenticationManagerBean() throws Exception {
  30. return super.authenticationManagerBean();
  31. }
  32. @Override
  33. protected void configure(HttpSecurity httpSecurity) throws Exception {
  34. httpSecurity
  35. // we don't need CSRF because our token is invulnerable
  36. .csrf().disable()
  37. .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
  38. // don't create session
  39. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
  40. .authorizeRequests()
  41. // Un-secure H2 Database
  42. .antMatchers("/h2-console/**/**").permitAll()
  43. .antMatchers("/api/v1/users").permitAll()
  44. .anyRequest().authenticated();
  45. // Custom JWT based security filter
  46. JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
  47. httpSecurity
  48. .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  49. // disable page caching
  50. httpSecurity
  51. .headers()
  52. .frameOptions()
  53. .sameOrigin() // required to set for H2 else H2 Console will be blank.
  54. .cacheControl();
  55. }
  56. @Override
  57. public void configure(WebSecurity web) {
  58. // AuthenticationTokenFilter will ignore the below paths
  59. web
  60. .ignoring()
  61. .antMatchers(
  62. HttpMethod.POST,
  63. "/api/v1/users"
  64. );
  65. }
  66. }

这是我的过滤器:

  1. @Provider
  2. @Slf4j
  3. public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
  4. private UserDetailsService userDetailsService;
  5. private JwtTokenUtil jwtTokenUtil;
  6. private String tokenHeader;
  7. public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, String tokenHeader) {
  8. this.userDetailsService = userDetailsService;
  9. this.jwtTokenUtil = jwtTokenUtil;
  10. this.tokenHeader = tokenHeader;
  11. }
  12. @Override
  13. protected boolean shouldNotFilter(HttpServletRequest request) {
  14. return new AntPathMatcher().match("/api/v1/users", request.getServletPath());
  15. }
  16. @Override
  17. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException,
  18. IOException {
  19. log.info("processing authentication for '{}'", request.getRequestURL());
  20. final String requestHeader = request.getHeader(this.tokenHeader);
  21. String username = null;
  22. String authToken = null;
  23. if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
  24. authToken = requestHeader.substring(7);
  25. try {
  26. username = jwtTokenUtil.getUsernameFromToken(authToken);
  27. } catch (IllegalArgumentException e) {
  28. logger.info("an error occured during getting username from token", e);
  29. } catch (ExpiredJwtException e) {
  30. logger.info("the token is expired and not valid anymore", e);
  31. }
  32. } else {
  33. logger.info("couldn't find bearer string, will ignore the header");
  34. }
  35. log.info("checking authentication for user '{}'", username);
  36. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  37. logger.info("security context was null, so authorizating user");
  38. // It is not compelling necessary to load the use details from the database. You could also store the information
  39. // in the token and read it from it. It's up to you ;)
  40. UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  41. // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
  42. // the database compellingly. Again it's up to you ;)
  43. if (jwtTokenUtil.validateToken(authToken, userDetails)) {
  44. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  45. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  46. log.info("authorizated user '{}', setting security context", username);
  47. SecurityContextHolder.getContext().setAuthentication(authentication);
  48. }
  49. }
  50. chain.doFilter(request, response);
  51. }
  52. }

  1. @Component
  2. @Slf4j
  3. public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
  4. private static final long serialVersionUID = -8970718410437077606L;
  5. @Override
  6. public void commence(HttpServletRequest request,
  7. HttpServletResponse response,
  8. AuthenticationException authException) throws IOException {
  9. log.info("user tries to access a secured REST resource without supplying any credentials");
  10. // This is invoked when user tries to access a secured REST resource without supplying any credentials
  11. // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
  12. response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
  13. }
  14. }

这是我启动应用程序时的控制台:

  1. 18:02:51.974 [restartedMain] DEBUG com.agrumh.Application - Running with Spring Boot v2.4.2, Spring v5.3.3
  2. 18:02:51.974 [restartedMain] INFO com.agrumh.Application - No active profile set, falling back to default profiles: default
  3. 18:02:57.383 [restartedMain] INFO o.s.s.web.DefaultSecurityFilterChain - Will secure Ant [pattern='/api/v1/users', POST] with []
  4. 18:02:57.414 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/h2-console/**/**']
  5. 18:02:57.415 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/api/v1/users']
  6. 18:02:57.416 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
  7. 18:02:57.422 [restartedMain] INFO o.s.s.web.DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@24c68fed, org.springframework.security.web.context.SecurityContextPersistenceFilter@1537eb0a, org.springframework.security.web.header.HeaderWriterFilter@95de45c, org.springframework.security.web.authentication.logout.LogoutFilter@733cf550, com.dispacks.config.JwtAuthorizationTokenFilter@538a96c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@8d585b2, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@784cf061, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@64915f19, org.springframework.security.web.session.SessionManagementFilter@21f180d0, org.springframework.security.web.access.ExceptionTranslationFilter@2b153a28, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4942d157]
  8. 18:02:58.619 [restartedMain] INFO com.dispacks.DispacksApplication - Started DispacksApplication in 6.974 seconds (JVM running for 7.697)
  9. 18:04:03.685 [http-nio-1133-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing POST /error
  10. 18:04:03.687 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
  11. 18:04:03.689 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
  12. 18:04:03.694 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [POST /error] with attributes [authenticated]
  13. 18:04:03.698 [http-nio-1133-exec-1] INFO c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials
  14. 18:04:03.699 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request

但是当我和 Postman 联系时,我有一个错误:

  1. 22:58:33.562 [http-nio-1133-exec-2] WARN o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported]
  2. 22:58:33.579 [http-nio-1133-exec-2] INFO c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials
rsaldnfx

rsaldnfx1#

你从 Postman 那叫什么路?如果是的话 /api/v1/users 我可以看到你在 shouldNotFilter 过滤器的方法。这不意味着你忽略了这个路径的jwt过滤器吗?
顺便说一句,如果您不需要任何附加功能,您可以使用springsecurity的支持来验证jwts。看看这个教程,看看它是如何配置的。这样你就不需要自己的过滤器了。

nr9pn0ug

nr9pn0ug2#

授权和认证在岗位上是不同的 /api/v1/users 是允许的,因为不需要授权访问资源发布。
在你的代码里,

  1. @Override
  2. public void commence(HttpServletRequest request,
  3. HttpServletResponse response,
  4. AuthenticationException authException // AuthenticationException means authentication failed, not "without supplying any credentials".
  5. ) throws IOException {
  6. // Break point here, or print authException.
  7. log.info("user tries to access a secured REST resource without supplying any credentials"); // Wrong message. You can say "Authentication failed.", or log.info(authException.getMessage()).
  8. response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
  9. }

身份验证错误实际上发生在访问 /error 资源。

  1. 18:04:03.694 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [POST /error] with attributes [authenticated]

我假设发生了一些错误,您的应用程序正在将您重定向到 /error ,但是 /error 受到保护。所以在上发生了authenticationexception /error .
添加 /error 之前 .permitAll() .
中断authenticationexception,以便更新此答案。

展开查看全部

相关问题