Recently, I used spring boot + spring security + jwt to separate the front and back of a project to realize user login permission control and other operations. But when the user logs in, how to deal with the exception thrown by spring security? Using @RestControllerAdvice and @ExceptionHandler cannot handle exceptions thrown by Spring Security, such as UsernameNotFoundException, etc. I want to return prompt information to the front end friendly, such as, the username does not exist. Paste my code:
JWT authentication class: Override spring security UsernamaPasswordAuthenticationFilter
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private RedisServiceImpl redisService; private AppConfig appConfig; public JWTAuthenticationFilter(AuthenticationManager authenticationManager, RedisServiceImpl redisService, AppConfig appConfig) { this.authenticationManager = authenticationManager; this.redisService = redisService; this.appConfig = appConfig; } /** * @param req * @param res * @return * @throws AuthenticationException * @// TODO: 2018/4/12 Accept and parse user credentials */ @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { AuthEntity creds = new ObjectMapper() .readValue(req.getInputStream(), AuthEntity.class); // verification code if (appConfig.getCaptchaEnabled()) { //If the verification code login verification function is enabled if (StringUtils.isBlank(creds.getCaptcha())) { logger.error("Verification code is empty"); throw new WelendException(StatusCode.CAPTCHA_EMPTY); } if (!redisService.exists(appConfig.getCaptchaKey())) { logger.error("Verification code has expired"); throw new WelendException(StatusCode.CAPTCHA_OVERDUE); } String captcha = (String) redisService.get(appConfig.getCaptchaKey()); if (!creds.getCaptcha().equals(captcha)) { logger.error("The verification code is incorrect"); throw new WelendException(StatusCode.CAPTCHA_ERROR); } } return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( creds.getUsername(), creds.getPassword(), new ArrayList<>()) ); } catch (IOException e) { logger.error("Client's variables can't be parsed by com.fasterxml.jackson.core.JsonParse"); throw new WelendException(StatusCode.SERVER_ERROR); } } }
Verify username and password:
public class CustomAuthenticationProvider implements AuthenticationProvider { private UserDetailsServiceImpl userDetailsService; private BCryptPasswordEncoder bCryptPasswordEncoder; public CustomAuthenticationProvider(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) { this.userDetailsService = userDetailsService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // Get authenticated username & password String name = authentication.getName(); String password = authentication.getCredentials().toString(); // authentication logic JWTUserDetails userDetails = userDetailsService.loadUserByUsername(name); if (null != userDetails) { Boolean verifyPwd = bCryptPasswordEncoder.matches(password,userDetails.getLoginPwd()); if (verifyPwd) { // Generate a token This token is stored in: userDetails, password, authorities (authority list) Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()); return auth; } else { throw new BadCredentialsException("username or password wrong!"); } } else { throw new UsernameNotFoundException("can not find this account"); } } /** * Is it possible to provide authentication service of input type * @param authentication * @return */ @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
global exception handling
@RestControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(getClass()); /** * @param request * @param exception * @return * @throws Exception * @// TODO: 2018/4/25 parameter failed validation exception */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public Object MethodArgumentNotValidHandler(HttpServletRequest request, MethodArgumentNotValidException exception) throws Exception { //Re-encapsulate the error message that needs to be returned as needed //List<StatusCode> invalidArguments = new ArrayList<>(); //Parse the original error information, return it after encapsulation, here returns the illegal field name, original value, error information ResultObject resultMsg = ResultObject.dataMsg(exception.getBindingResult().getFieldError().getDefaultMessage(), StatusCode.VARIABLE_ERROR); return resultMsg; } /** * @param request * @param exception * @return * @throws Exception * @// TODO: 2018/4/25 Unable to parse parameter exception */ @ExceptionHandler(value = HttpMessageNotReadableException.class) public Object HttpMessageNotReadableHandler(HttpServletRequest request, HttpMessageNotReadableException exception) throws Exception { logger.info(exception.getMessage()); ResultObject resultMsg = ResultObject.dataMsg("The parameter cannot be parsed normally", StatusCode.VARIABLE_ERROR); return resultMsg; } /** * @param exception * @return * @throws Exception * @// TODO: 2018/4/25 Handle token expiration exception */ @ExceptionHandler(value = ExpiredJwtException.class) public Object ExpiredJwtExceptionHandler(ExpiredJwtException exception) throws Exception { logger.info(exception.getMessage()); ResultObject resultMsg = ResultObject.dataMsg("Login has expired!", StatusCode.FORBIDDEN); return resultMsg; } /** * @param request * @param exception * @return * @throws Exception * @// TODO: 2018/4/25 Insufficient method access permission exception */ @ExceptionHandler(value = AccessDeniedException.class) public Object AccessDeniedExceptionHandler(AccessDeniedException exception) throws Exception { logger.info(exception.getMessage()); ResultObject resultMsg = ResultObject.dataMsg("Insufficient permission!", StatusCode.FORBIDDEN); return resultMsg; } @ExceptionHandler(value = NoHandlerFoundException.class) public Object NoHandlerFoundExceptionHandler(NoHandlerFoundException exception) throws Exception { logger.info(exception.getMessage()); return ResultObject.dataMsg("Link does not exist", StatusCode.NOT_FOUND); } /** * Handle custom exceptions */ @ExceptionHandler(value = WelendException.class) public Object WelendExceptionHandler(WelendException e) { ResultObject r = new ResultObject(); r.setStatus(String.valueOf(e.getCode())); r.setMessage(e.getMessage()); return r; } @ExceptionHandler(value = AuthenticationException.class) public Object AuthenticationExceptionHandler(AuthenticationException e) { return ResultObject.dataMsg(e.getLocalizedMessage(),StatusCode.FORBIDDEN); } @ExceptionHandler(value = DuplicateKeyException.class) public Object DuplicateKeyExceptionHandler(DuplicateKeyException e) throws Exception { logger.error(e.getMessage(), e); return ResultObject.codeMsg(StatusCode.EXISTED); } @ExceptionHandler(value = BadCredentialsException.class) public Object BadCredentialsExceptionHandler(BadCredentialsException e) throws Exception { logger.error(e.getMessage(), e); return ResultObject.codeMsg(StatusCode.AUTH_ERROR); } @ExceptionHandler(value = Exception.class) public Object ExceptionHandler(Exception e) throws Exception { logger.error(e.getMessage(), e); return ResultObject.codeMsg(StatusCode.FAILED); } }
Wrong username entered when logging in
The console prints the information directly, and it is not processed by ExceptionHandler.
As shown above, I want to handle exceptions thrown by spring security in the global exception class in order to return friendly prompt information. Is there any solution?