Spring Boot - Spring Security - InvalidBearerTokenException Error Handling - java

Similar to this question but it got no answers: Spring Security: Handle InvalidBearerTokenException in #ExceptionHandler
I have similar code and I'm trying to catch org.springframework.security.oauth2.server.resource.InvalidBearerTokenException when a user has supplied invalid/expired/bad JWT format.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
resolver.resolveException(request, response, null, e);
}
}
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private CustomAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception
{
// other config here
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer().jwt();
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}
I've also implemented the #ExceptionHandler of AuthenticationException for custom response.
#ExceptionHandler({AuthenticationException.class})
protected ResponseEntity<Object> handleAuthException(AuthenticationException ex, WebRequest req)
{
CustomResponse response = ...
return new ResponseEntity<>(response, ...);
}
InvalidBearerTokenException is a subclass of AuthenticationException.
Any idea why this AuthenticationEntryPoint code is not catching it? I've also tried adding logging inside the commence method but it's not being called when InvalidBearerTokenException is thrown, but other AuthenticationException does.

You have to specify this AuthenticationEntryPoint inside the OAuth2ResourceServerConfigurer, like so:
#Override
protected void configure(HttpSecurity http) throws Exception
{
// other config here
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer().jwt().and()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
When you set it, the Configurer will change the AuthenticationEntryPoint that is used inside the BearerTokenAuthenticationFilter, see here.

Related

spring AccessDeniedHandler interface don't get called when i have ExceptionHandler of RuntimeException in ControllerAdvice?

I have implemented the AccessDeniedHandler interface and added the interface to the WebSecurityConfigurerAdapter, and I also have an ExceptionHandler of RuntimeException in my ControllerAdvice.
When I receive accessDeniedException from MethodSecurity, ExceptionHandler calls RuntimeException before running AccessDeniedHandler.
How to call AccessDeniedHandler without deleting ExceptionHandler?
this is my WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
#Value("${payment.gateway.callback-path}")
private String paymentCallbackPath;
#Autowired
private ThingspodAccessDeniedHandler accessDeniedHandler;
#Autowired
private RestAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
protected JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
List<String> pathsToSkip = new ArrayList<>(Collections.singletonList(paymentCallbackPath));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(matcher, JWT_TOKEN_HEADER_PARAM, authenticationFailureHandler);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(paymentCallbackPath).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.and()
.authorizeRequests()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
}
and this is my AccessDeniedHander and ExceptionHandler in ControllerAdvice:
#Component
public class ThingspodAccessDeniedHandler implements AccessDeniedHandler {
private final ObjectMapper mapper;
public ThingspodAccessDeniedHandler(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
Object res = buildResponse(accessDeniedException, "You don't have permission to perform this operation!", HttpStatus.FORBIDDEN, ThingspodErrorCode.PERMISSION_DENIED, request);
response.setStatus(HttpStatus.FORBIDDEN.value());
mapper.writeValue(response.getWriter(), res);
}
}
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleAllUncaughtRuntimeException(
RuntimeException ex, WebRequest request){
return buildApiErrorResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, ThingspodErrorCode.GENERAL, request);
}
This is an unfortunate limitation of the way Spring Security handles its own exceptions. You can read more about it at the bug report (which was rejected) at Spring Security: https://github.com/spring-projects/spring-security/issues/6908#issuecomment-533269673
The only thing to do in this case is to spot Spring Security related exceptions in your ExceptionHandler and rethrow them.
When selecting a error handler, spring will look for the most specific one.
Since you have exception handler for the more general exception (RuntimeException) and another one for AccessDeniedException.
In that case first exception handler (for handling RuntimeException) won't be called when AccessDeniedException occurs.
So you could keep exception handler for handling RuntimeExceptions, but you will need to add exception handler for handling AccessDeniedException and move logic from ThingspodAccessDeniedHandler into that handler method, like this:
#ExceptionHandler(UnknownHostException.class)
public void handleUnknownAccessDeniedException(UnknownHostException e, WebRequest request) {
Object res = buildResponse(accessDeniedException, "You don't have permission to perform this operation!", HttpStatus.FORBIDDEN, ThingspodErrorCode.PERMISSION_DENIED, request);
response.setStatus(HttpStatus.FORBIDDEN.value());
mapper.writeValue(response.getWriter(), res);
}
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleAllUncaughtRuntimeException(
RuntimeException ex, WebRequest request){
return buildApiErrorResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, ThingspodErrorCode.GENERAL, request);
}

Spring security exception handling

I'm trying to create spring secured application
I implemented SecurityConfig like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.formLogin().disable()
.httpBasic().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
My JwtAuthenticationEntryPoint to handle exception's :
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}
Also I implemented UserDetail Service
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String usernameOrEmail)
throws UsernameNotFoundException {
User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
.orElseThrow(() ->
new UsernameNotFoundException
("User not found with username or email : " + usernameOrEmail)
);
return UserPrincipal.create(user);
}}
It was working fine before I added ControllerAdvice
#ControllerAdvice
public class ExceptionControllerAdvice {
/**** 500 Entity */
#ExceptionHandler(value = Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public final ErrorDetail handleAllExceptions(Exception ex) {
LOGGER.error("An unexpected error occurred", ex);
ErrorDetail problem = new ErrorDetail("Internal Error",
"An unexpected error has occurred");
problem.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return problem;
}
}
Now spring is trying to handle Exception in controller advice. As a consequence, I can not handle the error correctly. if I comment out Controller advice logic then AuthenticationEntryPoint is processed authentication error.
How can make AuthenticationEntryPoint and #ControllerAdvice work together?

Spring security with annotations: separated paths (multiply entry points)

I'm using Spring Boot with annotations and Spring Security.
I need to implement two different kind of authentication:
ProviderApiAuthenticationProvider only for "/providerrpc" and "/api/(system|provider|drm)/"
TestAuthFilter (Custom authentificator, now empty), only for "/test/**"
In current configuration on both URL's app requests httpBasic authentification and TestAuthFilter::doFilter() also called on both URL's.
So, whats wrong?
WebSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ProviderApiAuthenticationProvider providerApiAuthenticationProvider;
private final TestAuthFilter testAuthFilter;
#Autowired
public WebSecurityConfig(TestAuthFilter testAuthFilter, ProviderApiAuthenticationProvider providerApiAuthenticationProvider) {
this.testAuthFilter = testAuthFilter;
this.providerApiAuthenticationProvider = providerApiAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(providerApiAuthenticationProvider);
}
#SuppressWarnings("SpellCheckingInspection")
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(providerApiAuthenticationProvider)
.authorizeRequests()
.regexMatchers(
"^/providerrpc/",
"^/api/(system|provider|drm)/"
)
.hasAuthority(Role.ROLE_PROVIDER_API.getAuthority())
.and()
.httpBasic()
.realmName("Provider API")
.and()
.addFilterBefore(testAuthFilter, BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/test/**"
)
.authenticated()
.anyRequest()
.permitAll()
;
}
}
TestAuthFilter.java:
#Component
public class TestAuthFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// TODO: Later implement via SecurityContextHolder.getContext().setAuthentication();
chain.doFilter(request,response);
}
}
I found solution which provides two independent entry points for authentication in the official documentation: Spring Security: 5.7 Multiple HttpSecurity
Here is the solution:
MultiHttpSecurityConfig.java
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class RestApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/rest/**")
.authorizeRequests()
.anyRequest().hasAuthority(Role.ROLE_USER.getAuthority())
.and()
.httpBasic()
.realmName("Rest API")
.and().csrf().disable()
;
}
}
#Configuration
#Order(2)
public static class TestWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/test**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.realmName("Test zone");
}
}
}

Spring AuthenticationFailureHandler and WebSecurityConfigurerAdapter loginPage()

Edit: Solved. See my comment after this post
I am currently implementing a Webapplication with Spring-Security. I have implemented a custom AuthenticationFailureHandler which checks if a user tried to login with wrong credentials too often (and blocks him for serveral minutes). But normal failed logins should redirect the user to the login page with the parameter error (/login?error). This page shows an error message like "The password you typed in was wrong"
The AutenticationFailureHandler looks like this (without the uninteressting linse of code)
public class CustomAuthenticationHandler implements AuthenticationFailureHandler {
// Some variables
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// some logic here..
request.setAttribute("param", "error");
response.sendRedirect("/login?error");
}
My WebApplicationSecurity class looks like this:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationHandler customAuthenticationHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.permitAll()
.failureHandler(customAuthenticationHandler)
.and()
.logout()
.permitAll();
http.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**")
.permitAll()
.anyRequest()
.authenticated();
http
.csrf()
.disable();
}
#Bean
CustomAuthenticationHandler authenticationHandler() {
return new CustomAuthenticationHandler();
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("*******")
.password("*******")
.roles("USER");
}
}
}
The problem now is that the AuthenticationFailureHandler redirects to /login?error but (i don't know why) another redirect is done to /login.
Can you help me to solve my problem?
Well, i solved it by adding "/login**" to http.authorizeRequests().antMatchers("/css/**", "/img/**", "/js/**")

Spring security - Custom ExceptionTranslationFilter

This question is actually related to this issue problem.
Based on the suggestion from #harsh-poddar, I added the filter accordingly.
However, after adding that it seems like I can't login even with valid credential.
Following is the related code:
SecurityConfig
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #Bean
// public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
// return new CustomAuthenticationEntryPoint();
// }
#Bean
public CustomExceptionTranslationFilter customExceptionTranslationFilter() {
return new CustomExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//Note : Able to login without this filter, but after adding this, valid credential also fails
.addFilterAfter(customExceptionTranslationFilter(), ExceptionTranslationFilter.class)
// .exceptionHandling()
// .authenticationEntryPoint(new customAuthenticationEntryPoint())
// .and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider());
}
}
CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider() {
super();
}
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final String name = authentication.getName();
final String password = authentication.getCredentials().toString();
if (name.equals("admin") && password.equals("password")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
final UserDetails principal = new User(name, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
throw new BadCredentialsException("NOT_AUTHORIZED");
}
}
#Override
public boolean supports(final Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
CustomExceptionTranslationFilter
#Component
public class CustomExceptionTranslationFilter extends ExceptionTranslationFilter {
public CustomExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationEntryPoint);
}
}
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized.");
}
}
p/s : sorry for the basic question, I'm really new in spring & spring security.
The intended design for AuthenticationEntryPoint is to start/initiate an authentication. However, your implementation CustomAuthenticationEntryPoint does not do this. Instead, it simply sends back an unauthorized response. Please see javadoc for AuthenticationEntryPoint for more details on implementation specifics.
Based on your configuration you are using HTTP Basic for authentication:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
This specific configuration will automatically configure BasicAuthenticationEntryPoint which is an implementation of AuthenticationEntryPoint. The BasicAuthenticationEntryPoint will challenge the user with a http response header of WWW-Authenticate: Basic realm="User Realm" to authenticate, as per server protocol.
However, the fact that you are configuring your own CustomAuthenticationEntryPoint it will ultimately override the BasicAuthenticationEntryPoint which is not what you want to do.
The other post recommended this configuration which again is not what you want to do.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
If your main goal is to provide a custom response to the user when authentication fails than I would propose a form login configuration with a configured AuthenticationFailureHandler. Here is the configuration:
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().failureHandler(new DefaultAuthenticationFailureHandler())
.and()
.csrf().disable(); // NOTE: I would recommend enabling CSRF
Your implementation of DefaultAuthenticationFailureHandler would be:
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// Set status only OR do whatever you want to the response
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
The AuthenticationFailureHandler is specifically designed to handle a failed authentication attempt.

Categories

Resources