在 Spring Boot 安全性中,无法跳过登录 url 的 OncePerRequestFilter 过滤器(基本上是在初始登录期间获取 JWT 令牌)
Posted
技术标签:
【中文标题】在 Spring Boot 安全性中,无法跳过登录 url 的 OncePerRequestFilter 过滤器(基本上是在初始登录期间获取 JWT 令牌)【英文标题】:Unable to skip the OncePerRequestFilter filter for a login url (basically to get the JWT token during the initial login)in spring boot security 【发布时间】:2020-03-15 05:59:06 【问题描述】:我正在尝试使用 Spring Security 开发具有 JWT 授权的 Spring Boot Rest API。我希望我的所有请求都通过过滤器来验证 JWT 令牌,除了应该生成 jwt 令牌的 /authenticate
请求。但是使用下面的代码,/authenticate
请求也被过滤器拦截,因为它以 401 失败。请让我知道我在下面的代码中遗漏了什么。
JwtTokenFilter 类
@Component
public class JwtTokenFilter extends OncePerRequestFilter
@Autowired
private UserService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
jwtToken = requestTokenHeader.substring(7);
try
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
catch (IllegalArgumentException e)
System.out.println("Unable to get JWT Token");
catch (ExpiredJwtException e)
System.out.println("JWT Token has expired");
else
logger.warn("JWT Token does not begin with Bearer String");
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails))
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
chain.doFilter(request, response);
JwtConfig 类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtConfigurer extends WebSecurityConfigurerAdapter
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserService jwtUserDetailsService;
@Autowired
private JwtTokenFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
// We don't need CSRF for this example
httpSecurity.csrf().disable().
// dont authenticate this particular request
authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
控制器类
@RestController
@CrossOrigin
public class JwtAuthenticationController
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserService userDetailsService;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody User authenticationRequest) throws Exception
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
User u = new User();
u.setUsername(authenticationRequest.getUsername());
u.setToken(token);
return ResponseEntity.ok(u);
private void authenticate(String username, String password) throws Exception
try
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
catch (DisabledException e)
throw new Exception("USER_DISABLED", e);
catch (BadCredentialsException e)
throw new Exception("INVALID_CREDENTIALS", e);
【问题讨论】:
【参考方案1】:我为此苦苦挣扎了两天,最好的解决方案是Tom answer 与我的SecurityConfig
上的此设置相结合:
override fun configure(http: HttpSecurity?)
// Disable CORS
http!!.cors().disable()
// Disable CSRF
http.csrf().disable()
// Set session management to stateless
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//Add JwtTokenFilter
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter::class.java)
【讨论】:
【参考方案2】:基本上,OncePerRequestFilter 仅以这种方式工作。不确定这是否可以避免。引用文档:
过滤器基类,旨在保证每次执行一次 请求调度,在任何 servlet 容器上。
您也可以尝试添加方法类型以跳过端点上的身份验证。
.antMatchers(HttpMethod.GET, "/authenticate").permitAll()
【讨论】:
【参考方案3】:正如 Mohit 已经指出的那样,即使我在您的配置中也看不到任何错误。
如果您理解下面的解释,它将帮助您解决。
即使 /authenticate
请求是 permitAll 配置,请求也应该通过您的 JWT 过滤器。但是FilterSecurityInterceptor
是最后一个过滤器,它将检查配置的 antMatchers 和相关的限制/权限,基于它将决定是允许还是拒绝请求。
对于/authenticate
方法,它应该通过过滤器和requestTokenHeader,用户名应该为空,并确保chain.doFilter(request, response);
无任何异常到达。
当它达到FilterSecurityInterceptor
并且如果您已将日志级别设置为调试)应打印类似于下面给出的日志。
DEBUG - /app/admin/app-config at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG - Checking match of request : '/app/admin/app-config'; against '/resources/**'
DEBUG - Checking match of request : '/app/admin/app-config'; against '/'
DEBUG - Checking match of request : '/app/admin/app-config'; against '/login'
DEBUG - Checking match of request : '/app/admin/app-config'; against '/api/**'
DEBUG - Checking match of request : '/app/admin/app-config'; against '/app/admin/app-config'
DEBUG - Secure object: FilterInvocation: URL: /app/admin/app-config; Attributes: [permitAll]
DEBUG - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@511cd205: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 696171A944493ACA1A0F7D560D93D42B; Granted Authorities: ROLE_ANONYMOUS
DEBUG - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6df827bf, returned: 1
DEBUG - Authorization successful
附加这些日志,以便可以预测问题。
【讨论】:
我已经在这个答案中解释了(在给定的链接中)如何设置记录器级别以调试并将日志打印到 Eclipse/STS 控制台。 ***.com/a/57895752/2825798 @Archana 确保您的请求 URL 中没有拼写错误。它应该是/authenticate
并且如果您的请求 URL 像 /authentcate
或 /authanticate
或任何拼写错误都会导致 401,因为请求的 URL 与允许的 URL 不同。只是一种可能。只需检查一下。
@PraveenKumar,我收到错误“原因:org.xml.sax.SAXParseException; lineNumber: 3; columnNumber: 16; 添加 logback.xml 后必须声明元素类型“配置” . 下面是我的 logback.xml,我的 Eclipse 版本是面向 Web 开发人员的 Eclipse Java EE IDE。版本:2018-09 (4.9.0)。从帖子 (***.com/questions/35745105/…) 中尝试了一些解决方案,但没有奏效。
我也尝试过覆盖此方法以跳过 /authenticate 的过滤器,但在添加到 sn-p @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException System.out.println(request.getServletPath()); return new AntPathMatcher().match("/authenticate", request.getServletPath());
下方后,它会出现 403 失败
对不起,我忘了说我是在wildfly服务器上运行应用程序而不是在嵌入式tomcat上运行的。在服务器中启用调试日志后,我可以在日志中看到授权成功,后来甚至可以看到请求到达控制器,但由于我正在使用 BCryptPasswordEncoder 并且我的数据库中的密码与请求中的密码不匹配,因此在我的控制器中引发了异常。下面是日志16:40:45,989 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor](默认task-1)授权成功。【参考方案4】:
编写一个实现org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的配置类并像这样覆盖configur方法:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
// dont authenticate this particular request. you can use a wild card here. e.g /unprotected/**
httpSecurity.csrf().disable().authorizeRequests().antMatchers("/authenticate").permitAll().
//authenticate everything else
anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
【讨论】:
以上是关于在 Spring Boot 安全性中,无法跳过登录 url 的 OncePerRequestFilter 过滤器(基本上是在初始登录期间获取 JWT 令牌)的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security,Boot:安全规则无法正常工作
为啥我登录后无法进入管理 REST API 请求映射 - Spring Boot