弹簧靴.请求无法到达控制器 [英] Spring boot. Request can't reach controller

查看:39
本文介绍了弹簧靴.请求无法到达控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Boot 和 Spring Security 创建 API.我已经创建了一些基本的身份验证机制.目前在请求授权方面面临一些未知问题.这是我的配置类:

I'm creating an API with Spring Boot and Spring Security. I already created some basic authentication mechanism. And currently facing some unknown problem with authorization of requests. Here is my Configuration class:

// removed for brevity

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final CustomUserDetailsService customUserDetailsService;
    private final JwtTokenFilter jwtTokenFilter;
    private final CustomAuthenticationProvider customAuthenticationProvider;

    public SecurityConfiguration(CustomUserDetailsService customUserDetailsService,
                                 JwtTokenFilter jwtTokenFilter,
                                 CustomAuthenticationProvider customAuthenticationProvider) {
        this.customUserDetailsService = customUserDetailsService;
        this.jwtTokenFilter = jwtTokenFilter;
        this.customAuthenticationProvider = customAuthenticationProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // todo: provide an authenticationProvider for authenticationManager
        /* todo:
            In most use cases authenticationProvider extract user info from database.
            To accomplish that, we need to implement userDetailsService (functional interface).
            Here username is an email.
        * */
        auth.userDetailsService(customUserDetailsService);
        auth.authenticationProvider(customAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Enable CORS and disable CSRF
        http = http.cors().and().csrf().disable();

        // Set session management to Stateless
        http = http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and();

        // Set unauthorized requests exception handler
        http = http
                .exceptionHandling()
                .authenticationEntryPoint(
                        (request, response, ex) -> {
                            response.sendError(
                                    HttpServletResponse.SC_UNAUTHORIZED,
                                    ex.getMessage()
                            );
                        }
                )
                .and();

        // Set permissions and endpoints
        http.authorizeRequests()
                .antMatchers("/api/v1/auth/**").permitAll()
                .antMatchers("/api/v1/beats/**").hasRole("ADMIN")
                .anyRequest().authenticated();

        http.addFilterBefore(jwtTokenFilter,
                UsernamePasswordAuthenticationFilter.class);
    }

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

    // Used by spring security if CORS is enabled.
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source =
                new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Override @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }
}

为了检查用户是否有权访问资源,我使用来自 JWT 负载的信息.为此,我有一个过滤器类:

To check if user has rights to access resource, I use info from JWT payload. To do so I have a filter class:

// removed for brevity
@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    private final static Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class);
    private final JwtTokenUtil jwtTokenUtil;
    private final CustomUserDetailsService customUserDetailsService;

    public JwtTokenFilter(JwtTokenUtil jwtTokenUtil,
                          CustomUserDetailsService customUserDetailsService) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.customUserDetailsService = customUserDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (header == null || header.isEmpty() || !header.startsWith("Bearer ")) {
            logger.error("Authorization header missing");
            filterChain.doFilter(request, response);
            return;
        }
        final String token = header.split(" ")[1].trim();
        if (!jwtTokenUtil.validate(token)) {
            filterChain.doFilter(request, response);
            return;
        }
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(token);
        if (userDetails == null)
            throw new ServletException("Couldn't extract user from JWT credentials");
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, userDetails.getPassword(), userDetails.getAuthorities());
        authentication.setDetails(
                new WebAuthenticationDetailsSource().buildDetails(request)
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

为了表示 UserDetails,我实现了 CustomUserDetails 和 CustomUserDetailsS​​ervice 类:

To represent UserDetails, I've implemented CustomUserDetails and CustomUserDetailsService classes:

@Data
@NoArgsConstructor
public class CustomUserDetails implements UserDetails {
    private Long userId;
    private Long profileId;
    private String email;
    private String password;
    private String fullName;
    private String nickname;
    private String avatar;
    private String phoneNumber;
    private ProfileState profileState;
    private Collection<? extends GrantedAuthority> grantedAuthorities;

    public static CustomUserDetails fromUserAndProfileToMyUserDetails(Profile profile) {
       CustomUserDetails customUserDetails = new CustomUserDetails();
       customUserDetails.setUserId(profile.getUser().getId());
       customUserDetails.setEmail(profile.getUser().getEmail());
       customUserDetails.setPassword(profile.getUser().getPassword());
       customUserDetails.setProfileId(profile.getId());
       customUserDetails.setFullName(profile.getFullName());
       customUserDetails.setNickname(profile.getNickname());
       customUserDetails.setAvatar(profile.getAvatar());
       customUserDetails.setPhoneNumber(profile.getPhoneNumber());
       customUserDetails.setProfileState(profile.getState());
       return customUserDetails;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return nickname;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

CustomUserDetailsS​​ervice.java:

CustomUserDetailsService.java:

@Component
public class CustomUserDetailsService implements UserDetailsService {
    private Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
    private final ProfileRepository profileRepository;
    private final JwtTokenUtil jwtTokenUtil;

    public CustomUserDetailsService(ProfileRepository profileRepository, JwtTokenUtil jwtTokenUtil) {
        this.profileRepository = profileRepository;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @Override
    public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException {
        if (token == null || token.isEmpty()) throw new IllegalArgumentException("Token cannot be null or empty");
        try {
            final String nickname = jwtTokenUtil.getNickname(token);
            Profile profile = profileRepository
                    .findByNickname(nickname)
                    .orElseThrow(() -> new UsernameNotFoundException(
                            String.format("User: %s not found", token)
                    ));
            logger.info(String.format("Extracted Profile: %s", profile));

            CustomUserDetails customUserDetails = CustomUserDetails.fromUserAndProfileToMyUserDetails(profile);
            List<GrantedAuthority> authorities = new ArrayList<>(Collections.emptyList());
            authorities.add(new SimpleGrantedAuthority(profile.getType().getValue()));
            customUserDetails.setGrantedAuthorities(authorities);
            return customUserDetails;
        } catch (Exception e) {
            logger.error("Wasn't able to load user `{}`. Exception occurred `{}`", token, e.getMessage());
            return null;
        }
    }
}

这是我要访问的控制器:

Here is the controller that I want to access:

@RestController
@RequestMapping("/api/beats")
public class BeatController {
    private static final Logger logger = LogManager.getLogger(BeatController.class);

    private final BeatService beatService;

    public BeatController(BeatService beatService) {
        this.beatService = beatService;
    }

    @GetMapping("{id}")
    public Object getBeat(@PathVariable Long id) {
        try {
            return beatService.findById(id);
        } catch (Exception e) {
            logger.error("Can't find beat with id " + id);
            return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @GetMapping
    public Object getBeats(@RequestParam String filter, @RequestParam String page) {
        try {
            return beatService.findAll();
        } catch (Exception e) {
            logger.error("Can't find beats");
            return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PostMapping
    public Object createBeat(@RequestBody BeatDto beatDto) {
        try {
            beatDto.setId(null);
            return beatService.save(beatDto);
        } catch (Exception e) {
            logger.error("Can't create new Beat");
            return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PutMapping("{id}")
    public Object updateBeat(@PathVariable Long id, @RequestBody BeatDto newBeat) {
        try{
            BeatDto oldBeat = beatService.findById(id);
            if (oldBeat != null) {
                newBeat.setId(id);
            } else {
                throw new Exception();
            }
            return  beatService.save(newBeat);
        } catch (Exception e) {
            return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @DeleteMapping("{id}")
    public Object deleteBeat(@PathVariable Long id) {
        try {
            return beatService.deleteById(id);
        } catch (Exception e) {
            return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

因此,我提出请求,为其提供并更正授权标头和访问令牌.它从数据库获取用户并获取 GrantedAuthority.最后一步是:

So, I make a request, provide it with and correct Authorization header and access token. It gets a user from DB and fetches GrantedAuthority. Last steps are:

  1. 它在 SecurityContext 中设置身份验证对象.
  2. 在 FilterChain 中走得更远.

但它不会到达控制器,也不会抛出任何异常.只用 403 回复我.可能是我忘记设置什么了,或者问题可能出在其他地方?请指导我.

But it doens't reach controller, and it doens't throw any exceptions. Only responses me with 403. May be I forgot something to setup, or problem might be somewehere else? Guide me please.

推荐答案

所以终于找出问题所在.在这里帮助我的主要建议:

So finally figured out what was the problem. Main advices that helped me here:

  1. CustomUserDetails 服务中所有返回 false 的方法都返回 true.(来自 M. Deinum 的建议)
  2. 打开 spring 框架安全日志:logging.level.org.springframework.security=TRACE.这帮助我跟踪了一个异常,即 FilterChain 抛出的异常.感谢 Marcus Hert da Coregio.
  1. All methods in CustomUserDetails service that were returning false return true. (Advice from M. Deinum)
  2. Turned on spring framework security logs with: logging.level.org.springframework.security=TRACE. This helped me to trace an exception, that FilterChain was throwing. Thanks to Marcus Hert da Coregio.

我更改了什么来解决问题?首先我更新了 BeatController 中的 @RequestMapping 不匹配.堆栈跟踪告诉我,虽然它正确地从数据库中获取用户角色,但它未能匹配我的角色和我在配置类中编写的角色.默认情况下,它添加ROLE_";我们提供的实际角色名称之前的前缀.我认为定义这个 bean 会改变这种行为:

What I changed to fix a problem? First I updated @RequestMapping mismatch in BeatController. Stack trace showed me that while it was properly fetching user Role from DB, it failed to match my Role and the one I wrote in Configuration class. By default, it add "ROLE_" prefix before the actual role name we provide. I thought that defining this bean changes this behavior:

    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }

事实证明它不会影响前缀行为,因此它添加了ROLE_";在管理员"之前我提供的角色名称.添加ROLE_"验证请求时的前缀修复问题:

Turns out that it doesn't effect to prefixing behavior, so it was adding "ROLE_" before the "ADMIN" role name I provided. Adding "ROLE_" prefix while authenticating request fixed problem:

来自

authorities.add(new SimpleGrantedAuthority(profile.getType().getValue()));

authorities.add(new SimpleGrantedAuthority("ROLE_" + profile.getType().getValue()));

此外,我使用 gradle 清理了构建和重建项目.感谢所有帮助过的人!

Additionally I cleaned build and rebuild the project with gradle. Thanks to all people that helped!

这篇关于弹簧靴.请求无法到达控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆