Reputation: 261
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..!!!
Upvotes: 2
Views: 3363
Reputation: 21
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
}
Upvotes: 2
Reputation: 81
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.
Upvotes: 2