9

I'm upgrading an application to Spring Boot 2.0.3.

But my login request is unauthorized:

curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/login" -X POST -d "{ \"email\" : \"[email protected]\", \"password\" : \"xxxxx\" }" -i

The response is a 401 Unauthorized access. You failed to authenticate.

It is given by my custom entry point:

@Component
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
 private static Logger logger = LoggerFactory.getLogger(RESTAuthenticationEntryPoint.class);
 @Override
 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
 logger.debug("Security - RESTAuthenticationEntryPoint - Entry point 401");
 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access. You failed to authenticate.");
 }
 @Override
 public void afterPropertiesSet() throws Exception {
 setRealmName("User REST");
 super.afterPropertiesSet();
 }
}

The debugger shows the authenticate method of my CustomAuthenticationProvider is not called as I expect it to be:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
 @Autowired
 CredentialsService credentialsService;
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 String email = authentication.getName();
 String password = authentication.getCredentials().toString();
 List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
 User user = null;
 try {
 user = credentialsService.findByEmail(new EmailAddress(email));
 } catch (IllegalArgumentException e) {
 throw new BadCredentialsException("The login " + email + " and password could not match."); 
 }
 if (user != null) {
 if (credentialsService.checkPassword(user, password)) {
 grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
 return new UsernamePasswordAuthenticationToken(email, password, grantedAuthorities);
 } else {
 throw new BadCredentialsException("The login " + user.getEmail() + " and password could not match."); 
 }
 }
 throw new BadCredentialsException("The login " + authentication.getPrincipal() + " and password could not match.");
 }
 @Override
 public boolean supports(Class<?> authentication) {
 return authentication.equals(UsernamePasswordAuthenticationToken.class);
 }
}

But the filter is exercised and a null token is found:

@Component
public class AuthenticationFromTokenFilter extends OncePerRequestFilter {
 @Autowired
 private TokenAuthenticationService tokenAuthenticationService;
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
 throws IOException, ServletException {
 tokenAuthenticationService.authenticateFromToken(request);
 chain.doFilter(request, response);
 }
}
@Service
public class TokenAuthenticationServiceImpl implements TokenAuthenticationService {
 private static Logger logger = LoggerFactory.getLogger(TokenAuthenticationServiceImpl.class);
 private static final long ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
 private static final String TOKEN_URL_PARAM_NAME = "token";
 @Autowired
 private ApplicationProperties applicationProperties;
 @Autowired
 private UserDetailsService userDetailsService;
 public void addTokenToResponseHeader(HttpHeaders headers, String username) {
 String token = buildToken(username);
 headers.add(CommonConstants.AUTH_HEADER_NAME, token);
 }
 public void addTokenToResponseHeader(HttpServletResponse response, Authentication authentication) {
 String username = authentication.getName();
 if (username != null) {
 String token = buildToken(username);
 response.addHeader(CommonConstants.AUTH_HEADER_NAME, token);
 }
 }
 private String buildToken(String username) {
 String token = null;
 UserDetails userDetails = userDetailsService.loadUserByUsername(username);
 if (userDetails != null) {
 Date expirationDate = new Date(System.currentTimeMillis() + ONE_WEEK);
 token = CommonConstants.AUTH_BEARER + " " + Jwts.builder().signWith(HS256, getEncodedPrivateKey()).setExpiration(expirationDate).setSubject(userDetails.getUsername()).compact(); 
 }
 return token;
 }
 public Authentication authenticateFromToken(HttpServletRequest request) {
 String token = extractAuthTokenFromRequest(request);
 logger.debug("The request contained the JWT token: " + token);
 if (token != null && !token.isEmpty()) {
 try {
 String username = Jwts.parser().setSigningKey(getEncodedPrivateKey()).parseClaimsJws(token).getBody().getSubject();
 if (username != null) {
 UserDetails userDetails = userDetailsService.loadUserByUsername(username);
 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 SecurityContextHolder.getContext().setAuthentication(authentication);
 logger.debug("Security - The filter authenticated fine from the JWT token");
 }
 } catch (SignatureException e) {
 logger.info("The JWT token " + token + " could not be parsed.");
 }
 }
 return null;
 }
 private String extractAuthTokenFromRequest(HttpServletRequest request) {
 String token = null;
 String header = request.getHeader(CommonConstants.AUTH_HEADER_NAME);
 if (header != null && header.contains(CommonConstants.AUTH_BEARER)) {
 int start = (CommonConstants.AUTH_BEARER + " ").length();
 if (header.length() > start) {
 token = header.substring(start - 1);
 }
 } else {
 // The token may be set as an HTTP parameter in case the client could not set it as an HTTP header
 token = request.getParameter(TOKEN_URL_PARAM_NAME);
 }
 return token;
 }
 private String getEncodedPrivateKey() {
 String privateKey = applicationProperties.getAuthenticationTokenPrivateKey();
 return Base64.getEncoder().encodeToString(privateKey.getBytes());
 }
}

My security configuration is:

@Configuration
@EnableWebSecurity
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.user.rest.security", "com.thalasoft.user.rest.filter" })
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 private UserDetailsService userDetailsService;
 @Autowired
 private AuthenticationFromTokenFilter authenticationFromTokenFilter;
 @Autowired
 private SimpleCORSFilter simpleCORSFilter;
 @Autowired
 private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
 @Bean
 public AuthenticationManager authenticationManagerBean() throws Exception {
 return super.authenticationManagerBean();
 }
 @Override
 protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
 authenticationManagerBuilder.authenticationProvider(new CustomAuthenticationProvider());
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http
 .exceptionHandling()
 .authenticationEntryPoint(restAuthenticationEntryPoint)
 .and()
 .csrf().disable()
 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 .and()
 .addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class)
 .addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
 .headers().cacheControl().disable().frameOptions().disable()
 .and()
 .userDetailsService(userDetailsService)
 .authorizeRequests()
 .antMatchers(RESTConstants.SLASH + UserDomainConstants.USERS + RESTConstants.SLASH + UserDomainConstants.LOGIN).permitAll()
 .antMatchers(RESTConstants.SLASH + RESTConstants.ERROR).permitAll()
 .antMatchers("/**").hasRole(UserDomainConstants.ROLE_ADMIN).anyRequest().authenticated();
 }
}

The user details service is:

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
 @Autowired
 private CredentialsService credentialsService;
 @Override
 @Transactional
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 if (username != null && !username.isEmpty()) {
 User user = credentialsService.findByEmail(new EmailAddress(username));
 if (user != null) {
 return new UserDetailsWrapper(user);
 }
 }
 throw new UsernameNotFoundException("The user " + username + " was not found.");
 }
}

Why is the custom authentication provider not authenticating the username and password ?

UPDATE: I read something interesting and puzzling in this guide

Note that the AuthenticationManagerBuilder is @Autowired into a method in a @Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way (using an @Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one. In a Spring Boot application you can @Autowired the global one into another bean, but you can’t do that with the local one unless you explicitly expose it yourself.

So, is there anything wrong with my usage of the configure method for setting up the authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider); ? Instead of the above configuration, I tried the following configuration:

@Autowired
public void initialize(AuthenticationManagerBuilder authenticationManagerBuilder) {
 authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}

But it still didn't exercise the custom authentication provider upon a request.

I also tried to have the filter after as in:

http.addFilterAfter(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class);

instead of addFilterBefore but it didn't change anything to the issue.

asked Jul 25, 2018 at 8:45
2
  • This question has an answer at https://stackoverflow.com/a/51772848/958373 Commented Aug 9, 2018 at 17:32
  • after the version 5.8. there are in pre-5.8 and post-5.8 approaches. stackoverflow.com/questions/25633477 Commented Oct 6, 2024 at 13:59

2 Answers 2

1

In WebSecurityConfiguration inside configure(HttpSecurity http) method:

http.authorizeRequests().antMatchers("/api/users/login").permitAll(); 
http.authorizeRequests().anyRequest().authenticated();

Add in the same order.

Explanation: Login and logout requests should be permitted without any authentication

A sample configure method that works is:

http.formLogin().disable().logout().disable().httpBasic().disable();
 http.authorizeRequests().antMatchers("/logout", "/login", "/").permitAll();
 http.authorizeRequests().anyRequest().authenticated();
 http.addFilterBefore(new SomeFilter(), SecurityContextHolderAwareRequestFilter.class);
 http.addFilterBefore(new CORSFilter(env), ChannelProcessingFilter.class);
 http.addFilterBefore(new XSSFilter(),CORSFilter.class);
answered Aug 6, 2018 at 17:01
Sign up to request clarification or add additional context in comments.

3 Comments

What version of Spring Boot are you using ?
Tested with 1.3.0.RELEASE
I'm on Spring Boot 2.0.3. Do you have one filter to parse an incoming token only ? Or do you also have another filter to parse an incoming username-password payload ? Or do you use a custom provider for that ?
0

According to me when we implement our own ApplicationFilter by implementing GenericFilterBean we need to check if the token received from the request is valid or not. If it is not valid then we need to dump the token into the security context (for the authentication-provider to pick up). I haven't gone through your filter class. But this worked for me :

@Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 HttpServletRequest httprequset=(HttpServletRequest)request;
 String uname=request.getParameter("username");
 String pwd=request.getParameter("password");
 String role=request.getParameter("role");
 List<GrantedAuthority> l = new ArrayList<>();
 l.add( new SimpleGrantedAuthority(role.toUpperCase()) );
 UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(uname,pwd,l);
 token.setAuthenticated(false);
 SecurityContextHolder.getContext().setAuthentication(token);
 chain.doFilter(httprequset, response);
 }
Stephane
12.9k28 gold badges127 silver badges188 bronze badges
answered Jul 25, 2018 at 11:23

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.