简体   繁体   中英

What is the best way to write a AuthenticationFilter with REST features?

Below is what I have so far but it doesn't do what I want. What I want is a 415 if the content type isn't json, and a 400 jackson can't deserialize or if the validation is wrong. Currently of course this is all 401s and I'm doing something wrong with deserialization (was passing the wrong type to json). I'm thinking there may be some way to harness what Spring MVC would do under the hood for a regular controller.

@Component
public class JsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper;
    private final Validator validator;

    protected JsonAuthenticationFilter( final ObjectMapper objectMapper, final Validator validator ) {
        super( new AntPathRequestMatcher( "/authentication/password", "POST" ) );
        this.objectMapper = objectMapper;
        this.validator = validator;
    }

    @Override
    public Authentication attemptAuthentication( final HttpServletRequest request, final HttpServletResponse response )
        throws AuthenticationException, IOException, ServletException {

        if ( request.getContentType() == null
            || !MediaType.APPLICATION_JSON.isCompatibleWith( MediaType.parseMediaType( request.getContentType() ) ) ) {
            response.setStatus( HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE );
            throw new AuthenticationServiceException(
                "Media Type not supported: " + request.getContentType() );
        }

        PasswordCredentials credentials = objectMapper.readValue( request.getReader(), PasswordCredentials.class );
        DataBinder dataBinder = new DataBinder( credentials );
        dataBinder.setValidator( validator );
        dataBinder.validate();

        AbstractAuthenticationToken authRequest = credentials.toAuthenticationToken();

        setDetails( request, authRequest );

        return this.getAuthenticationManager().authenticate( authRequest );
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication
     * request's details property.
     *
     * @param request     that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     *                    set
     */
    protected void setDetails( HttpServletRequest request, AbstractAuthenticationToken authRequest ) {
        authRequest.setDetails( authenticationDetailsSource.buildDetails( request ) );
    }

    @Override
    @Autowired
    public void setAuthenticationManager( final AuthenticationManager authenticationManager ) {
        super.setAuthenticationManager( authenticationManager );
    }
}

I guess you're using JWT authentication, or at least some sort of token-based authentication, right? You're better off using a @RestController that delegates authentication to the authenticationManager than working with lower-level filter semantics.

Here's a controller I've used previously, a bit modified to comply with your DTO. You'll notice it looks a lot like the suggested JHipster example :

@RestController
@RequestMapping(value = "/api")
public class LoginController {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final AuthenticationManager authenticationManager;
    private final TokenAuthenticationService tokenAuthenticationService;

    public LoginController(
        final AuthenticationManager authenticationManager,
        final TokenAuthenticationService tokenAuthenticationService) {
        this.authenticationManager = authenticationManager;
        this.tokenAuthenticationService = tokenAuthenticationService;
    }

    @RequestMapping(
      value = "/login", 
      method = RequestMethod.POST, 
      consumes = MediaType.APPLICATION_JSON_VALUE) // will cause 415 errors
    public ResponseEntity<String> authenticate(
        @Valid @RequestBody PasswordCredentials request) { // validated implicitly
        try {
            final User principal = doAuthenticate(request);
            return ResponseEntity.ok(tokenFor(principal));
        } catch (Exception e) {
            logger.error("Error authenticating user", e);
            return new ResponseEntity<>(HttpStatus.FORBIDDEN);
        }
    }

    private String tokenFor(final User principal) {
        return tokenAuthenticationService.createTokenForUser(principal);
    }

    private User doAuthenticate(PasswordCredentials request) {
        final Authentication authentication = request.toAuthenticationToken();
        final Authentication authenticated = authenticationManager.authenticate(authentication);
        return (User) authenticated.getPrincipal();
    }
}

To see how Spring controllers resolve, parse and validate JSON request body parameters, you should look at RequestResponseBodyMethodProcessor , specifically its resolveArgument method and implement something similar in your filter. However, since your filter is not a controller you should adapt the code to suit your needs by remove all references to the MethodParameter parameter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM