弹簧靴.请求无法到达控制器 [英] Spring boot. Request can't reach controller
问题描述
我正在使用 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 和 CustomUserDetailsService 类:
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;
}
}
CustomUserDetailsService.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:
- 它在 SecurityContext 中设置身份验证对象.
- 在 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:
- CustomUserDetails 服务中所有返回
false
的方法都返回true
.(来自 M. Deinum 的建议) - 打开 spring 框架安全日志:
logging.level.org.springframework.security=TRACE
.这帮助我跟踪了一个异常,即 FilterChain 抛出的异常.感谢 Marcus Hert da Coregio.
- All methods in CustomUserDetails service that were returning
false
returntrue
. (Advice from M. Deinum) - 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屋!