请求未到达控制器,但仍获得200响应

uxhixvfz  于 2022-09-18  发布在  Spring
关注(0)|答案(3)|浏览(207)

我试图保护一个基于REST的应用程序,但却遇到了这个相当荒谬的问题。我的控制器上的所有操作都很好,请求被接受,但请求实际上从未到达控制器,并且总是返回200,没有任何内容。

我的安全配置如下所示:

package com.bpawan.interview.api.config;

import com.bpawan.interview.api.model.Error;
import com.bpawan.interview.api.security.JWTAuthenticationFilter;
import com.bpawan.interview.api.security.JWTAuthorizationFilter;
import com.bpawan.interview.service.UserDetailService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import static com.bpawan.interview.api.security.SecurityConstants.LOGIN_URL;
import static com.bpawan.interview.api.security.SecurityConstants.SIGN_UP_URL;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailService userDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .userDetailsService(userDetailService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler())
                .authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .addFilterBefore(corsFilter(), SessionManagementFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .antMatchers(HttpMethod.POST, LOGIN_URL).permitAll()
                .antMatchers(
                        "/v2/api-docs",
                        "/configuration/ui",
                        "/swagger-resources/**",
                        "/configuration/security",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/actuator/**"
                ).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:8080");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }

    private AuthenticationEntryPoint authenticationEntryPoint() {
        return (httpServletRequest, httpServletResponse, e) -> {
            var error = Error
                    .builder()
                    .message("Not authenticated")
                    .status(401)
                    .build();

            var responseBody = new ObjectMapper().writeValueAsString(error);

            httpServletResponse.setContentType(MediaType.APPLICATION_JSON.toString());
            httpServletResponse.getWriter().append(responseBody);
            httpServletResponse.setStatus(401);
        };
    }

    private AccessDeniedHandler accessDeniedHandler() {
        return (httpServletRequest, httpServletResponse, e) -> {
            var error = Error
                    .builder()
                    .message("Access denied")
                    .status(403)
                    .build();

            var responseBody = new ObjectMapper().writeValueAsString(error);

            httpServletResponse.getWriter().append(responseBody);
            httpServletResponse.setStatus(403);
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON.toString());
        };
    }
}

控制器如下所示:

package com.bpawan.interview.api.controller;

import com.bpawan.interview.dal.entity.Candidate;
import com.bpawan.interview.dal.repository.CandidateRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.List;

@RestController
@RequestMapping("api/candidate")
@RequiredArgsConstructor
@Slf4j
public class CandidateController {
    private final CandidateRepository candidateRepository;

    @GetMapping
    public List<Candidate> getAll(Principal principal) {

        log.info(principal.toString());

        return this.candidateRepository.findAll();
    }

    @GetMapping("/{candidateId}")
    public Candidate getById(@PathVariable Long candidateId) {
        return this.candidateRepository
                .findById(candidateId)
                .orElseThrow(() -> new RuntimeException("Could not find the candidate for the provided id."));
    }

    @PostMapping
    public Candidate addCandidate(@RequestBody Candidate candidate) {
        return this.candidateRepository.save(candidate);
    }

    @DeleteMapping("/{candidateId}")
    public void deleteCandidate(@PathVariable Long candidateId) {
        this.candidateRepository.deleteById(candidateId);
    }
}

授权过滤器如下所示:

package com.bpawan.interview.api.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

import static com.bpawan.interview.api.security.SecurityConstants.HEADER_STRING;
import static com.bpawan.interview.api.security.SecurityConstants.TOKEN_PREFIX;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {
        final var header = request.getHeader(HEADER_STRING);

        if (null != header) {
            final var headerContainsPrefix = header.startsWith(TOKEN_PREFIX);

            if (!headerContainsPrefix) {
                chain.doFilter(request, response);
                return;
            }
            return;
        }

        UsernamePasswordAuthenticationToken authentication = this.getAuthentication(request);

        SecurityContextHolder.getContext().setAuthentication(authentication);

        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        final var token = request.getHeader(HEADER_STRING);

        if (null != token) {
            final var user = JWT
                    .require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
                    .build().verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();

            if (null != user) {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }

            return null;
        }
        return null;
    }
}

我可以发出登录请求并获得有效的持有者令牌,并且似乎可以使用针对数据库的身份验证。

所有的执行器端点也工作得很好,摇摆式端点也是如此。只有我编写的控制器不需要。即使当我在控制器上设置断点,看看它是否真的进入了那里,但没有。

我觉得我可能在什么地方犯了个愚蠢的错误。任何帮助都将不胜感激。

以下是我使用Logback-Access执行的请求的示例日志:

logging uri: GET /api/candidate HTTP/1.1 | status code: 200 | bytes: 0 | elapsed time: 1 | request-log:  | response-log:

但无论如何都看不到致动器端点actuator/httptrace上的轨迹,就好像请求曾经发生过一样。

3z6pesqy

3z6pesqy1#

挣扎了一段时间后,我发现了问题所在。我确实犯了一个愚蠢的错误。它在授权过滤器上。变化

if (null != header) {
            final var headerContainsPrefix = header.startsWith(TOKEN_PREFIX);

            if (!headerContainsPrefix) {
                chain.doFilter(request, response);
                return;
            }
            return;
        }

致:

if (null != header) {
            final var headerContainsPrefix = header.startsWith(TOKEN_PREFIX);

            if (!headerContainsPrefix) {
                chain.doFilter(request, response);
                return;
            }
        }

而且似乎解决了这个问题。

apeeds0o

apeeds0o2#

您必须将请求传递到链,而不是这样做(Header!=NULL&&headerContainsprefix)

anauzrmj

anauzrmj3#

我希望我的回答能对某些人有所帮助。

在我的例子中,我有一个用于JWT令牌的定制过滤器JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter。我只用了一种方法:

public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        final JwtAuthenticationToken jwtAuthenticationToken = parseJwt(request);
        return getAuthenticationManager().authenticate(jwtAuthenticationToken);
    }

成功解析请求和令牌并返回有效的身份验证对象Authentication后,请求未到达控制器,但返回状态200,正文为空。

然后重写JwtAuthenticationFilter类中的以下方法对我起作用:

@Override
protected void successfulAuthentication(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain chain,
                                        Authentication authResult) throws IOException, ServletException {
    final SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(authResult);
    SecurityContextHolder.setContext(context);
    chain.doFilter(request, response);
}

相关问题