英文:
spring security permitAll() not working for JWT Authentication filter
问题
问题出在使用了自定义的JWT身份验证过滤器的应用程序上,该过滤器扩展了UsernamePasswordAuthenticationFilter
,该过滤器接受用户凭据并返回一个长期有效的JWT。
问题似乎出在permitAll()
上,它应该绕过自定义的授权过滤器。然而,在调试模式下,我可以看到首先调用了自定义的JwtAuthorizationFilter
而不是自定义的JwtAuthenticationFilter
过滤器,这最终导致了403禁止访问的响应。
请注意.antMatchers(HttpMethod.POST, "/login").permitAll()
这一行。由于在用户尚未登录时尚未生成JWT,因此应该可以访问/login
端点而无需JWT。
以下是您的代码片段:
// JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
// ...
}
// JwtAuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
// ...
}
// SecurityConfiguration.java
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// ...
}
请求和响应的截图没有完整显示,所以如果您需要关于问题的更多信息,请提供完整的请求和响应内容。
英文:
The issue is with the app uses custom JWT authentication filter which extends UsernamePasswordAuthenticationFilter
which accepts user credentials and generates a long-lived JWT in return.
The issue seems to be with permitAll()
which should bypass custom Authorization filter.However in debug mode I could see call to custom JwtAuthorizationFilter
first instead of custom JwtAuthenticationFilter
Filter which eventually results with 403 forbidden Access denied response.
Note the .antMatchers(HttpMethod.POST, "/login").permitAll()
line. /login
endpoint should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.
Below is my code
> JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
//
private AuthenticationManager authenticationManager;
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/login");
}
/**
* Trigger when we issue POST request to login / we also need to pass in
* {"username: " username, "password": password} in the request body
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// Grab credentials and map them to login viewmodel
LoginViewModel credentials = null;
try {
credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
} catch (IOException e) {
e.printStackTrace();
}
// Create login token
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
credentials.getUsername(), credentials.getPassword(), new ArrayList<>());
// Authenciate user
Authentication auth = authenticationManager.authenticate(authenticationToken);
return auth;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// Grab principal
UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();
// Create JWT Token
String token = JWT.create().withSubject(principal.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.sign(HMAC512(JwtProperties.SECRET.getBytes()));
// add token in response
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access "
+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
// Add more descriptive message
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
}
> JwtAuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//Read the Authorization header, where the JWT token should be
String header = request.getHeader(JwtProperties.HEADER_STRING);
//If header does not contain BEARER or is null delegate to Spring impl and exit
if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// If header is present, try grab user principal from db and perform authorization
Authentication authentication = getUsernamePasswordAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Continue filter execution
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(HttpServletRequest request){
String token = request.getHeader(JwtProperties.HEADER_STRING)
.replace(JwtProperties.TOKEN_PREFIX, "");
if(token !=null){
//parse the token validate it
String userName = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
.build()
.verify(token)
.getSubject();
// Search in the DB if we find the user by token subject(username)
// If so, then grab user details and create auth token using username, pass, authorities/roles
if(userName != null){
User user = userRepository.findByUsername(userName);
UserPrincipal principal = new UserPrincipal(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
return authenticationToken;
}
return null;
}
return null;
}
}
> SecurityConfiguration.java
Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private UserPrincipalDetailsService userPrincipalDetailsService;
private UserRepository userRepository;
public SecurityConfiguration(UserPrincipalDetailsService userPrincipalDetailsService,
UserRepository userRepository) {
this.userPrincipalDetailsService = userPrincipalDetailsService;
this.userRepository = userRepository;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// remove csrf state in session because in jwt do not need them
.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/api/public/management/*").hasRole("MANAGER").antMatchers("/api/public/admin/*")
.hasRole("ADMIN").anyRequest().authenticated().and()
// add jwt filters (1. authentication, 2. authorization_)
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository));
// configure access rules
}
@Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService((UserDetailsService) this.userPrincipalDetailsService);
return daoAuthenticationProvider;
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Request,Response
Can someone suggest whats wrong here..Appreciate your help..Thanks in advance..!!!
答案1
得分: 1
似乎你的路径是错误的。当你查看你的请求体时,你会发现路径显示为:/login%0A
。这看起来你的URL末尾多了一个额外的字符。尝试在Postman中重新编写URL。
英文:
It seems that your path is wrong. When you look at your body you can see that the path shows following: /login%0A
. This seems that you have an extra character at the end of your URL. Just try to rewrite the URL in Postman.
答案2
得分: 1
请考虑使用BasicAuthenticationFilter
中的shouldNotFilter
方法。它继承自OncePerRequestFilter
,因此您可以在过滤器类中按如下方式使用:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// 在此处编写代码
}
英文:
please consider to use shouldNotFilter method from BasicAuthenticationFilter. It extends OncePerRequestFilter so you can use it in filtering class as below:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// code here
}
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论