我正在尝试使用Spring Security开发一个具有JWT授权的Sping Boot rest API。我希望我的所有请求都通过过滤器来验证JWT标记,但/authenticate
请求除外,该请求应生成jwt标记。但使用下面的代码,/authenticate
请求也被过滤器拦截,因此它失败,并返回401。请告诉我下面的代码中遗漏了什么。
JwtToken筛选器类别
@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);
}
}
}
5条答案
按热度按时间mrzz3bfm1#
我为此纠结了两天,最好的解决方案是Tom answer结合我的
SecurityConfig
上的这个设置:insrf1ej2#
基本上,OncePerRequestFilter只以这种方式工作。不确定是否可以避免这种情况。引用文档:
旨在保证在任何servlet容器上每个请求分派执行一次的过滤器基类。
您也可以尝试添加方法类型以跳过端点上的身份验证。
.antMatchers(HttpMethod.GET, "/authenticate").permitAll()
ubby3x7f3#
正如Mohit已经指出的那样,即使是我也看不到你的配置中有任何错误。
如果你理解下面的解释,它将帮助你解决。
即使
/authenticate
request配置为permitAll,请求也应该通过您的JWT过滤器。但是FilterSecurityInterceptor
是最后一个过滤器,它将检查是否存在已配置的antMatchers以及关联的限制/权限,并根据这些限制/权限来决定是允许还是拒绝请求。对于
/authenticate
方法,它应该通过筛选器和requestTokenHeader,用户名应该为空,并确保chain.doFilter(request, response);
在没有任何异常的情况下到达。当它达到
FilterSecurityInterceptor
时,如果您已将日志级别设置为调试,则应打印类似于下面给出的日志。附上这些日志,以便可以预测问题。
olhwl3o24#
编写一个实现 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 的配置类,并重写configur方法,如下所示:
m528fe3b5#
我遇到过类似的问题,我通过比较请求路径和我不想过滤的路径来克服它。