我正在开发一个Spring Boot&使用JSON Web令牌的Spring Security应用程序。
我有一个spring安全过滤器,用于检查是否存在现有JWT,如果存在,则注入UsernamePasswordAuthenticationToken:
public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
@Value("${api.token.header}")
String tokenHeader;
@Autowired
TokenUtility tokenUtility;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String incomingToken = httpRequest.getHeader(tokenHeader);
if (SecurityContextHolder.getContext().getAuthentication() == null && incomingToken != null) {
UserDetails userDetails = null;
try {
userDetails = tokenUtility.validateToken(incomingToken);
} catch (TokenExpiredException e) {
throw new ServletException("Token has expired", e);
}
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
此过滤器注入如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Autowired
EntryPointUnauthorizedHandler unauthorizedHandler;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public AuthenticationTokenFilter authenticationTokenFilter() throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManager());
return authenticationTokenFilter;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// filter injected here
httpSecurity.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
如果用户传入已过期的令牌,则会收到以下错误:
{
"timestamp":1496424964894,
"status":500,
"error":"Internal Server Error",
"exception":"com.app.exceptions.TokenExpiredException",
"message":"javax.servlet.ServletException: Token has expired",
"path":"/orders"
}
我知道Spring安全性会在请求进入控制器层之前拦截请求,因此我无法使用现有的@ControllerAdvice来处理这些异常。
我的问题是,如何自定义此处返回的错误消息/对象?在其他地方,我使用JSON序列化的POJO来返回错误消息,我希望保持一致。我也不希望用户看到javax.servlet.ServletException
答案 0 :(得分:1)
当你使用.exceptionHandling()时,我相信你可以配置一个新的ExceptionHandler;
另一种方法是覆盖您希望与之不同的邮件,例如post
答案 1 :(得分:-1)
首先,修改JWTTokenProvider类,以使用setAttribute()方法将自定义标头添加到Http Servlet请求。
public boolean validateToken(String token,HttpServletRequest httpServletRequest){
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
}catch (SignatureException ex){
System.out.println("Invalid JWT Signature");
}catch (MalformedJwtException ex){
System.out.println("Invalid JWT token");
}catch (ExpiredJwtException ex){
System.out.println("Expired JWT token");
httpServletRequest.setAttribute("expired",ex.getMessage());
}catch (UnsupportedJwtException ex){
System.out.println("Unsupported JWT exception");
}catch (IllegalArgumentException ex){
System.out.println("Jwt claims string is empty");
}
return false;
}
然后修改JwtAuthenticationEntryPoint类中的begin方法,以检查上面添加的http servlet请求标头中的过期标头。
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
final String expired = (String) httpServletRequest.getAttribute("expired");
System.out.println(expired);
if (expired!=null){
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
}else{
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
}
}
有关更多详细信息,请参见此Post。一个很好的简单解决方案。