Code is extracted from my notice board example application, which uses Spring Security 5.6.7
Spring Security configuration class
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // must put more restricted rule at first .antMatchers("/manage/*/approve").hasAuthority("ADMIN") .antMatchers("/manage/**").hasAuthority("USER") .and() .httpBasic().disable() .formLogin() .loginPage("/login") .loginProcessingUrl("/loginHandler") .failureUrl("/login?error=true") .permitAll() .and() .logout().logoutSuccessUrl("/"); } // ... }
The chain in SecurityFilterChain
(begin) WebAsyncManagerIntegrationFilter SecurityContextPersistenceFilter HeaderWriterFilter CsrfFilter LogoutFilter UsernamePasswordAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter FilterSecurityInterceptor (end)
Flow of anonymous user access /manage page by browser
- AnonymousAuthenticationToken is created by AnonymousAuthenticationFilter
- Permission check is performed in FilterSecurityInterceptor. The user does not have the required authority "USER", so org.springframework.security.access.AccessDeniedException is thrown
- The exception is caught by ExceptionTranslationFilter. In org.springframework.security.web.access.ExceptionTranslationFilter#handleAccessDeniedException method
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AccessDeniedException exception) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication); if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) { if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied", authentication), exception); } sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException( this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource"))); } else { if (logger.isTraceEnabled()) { logger.trace( LogMessage.format("Sending %s to access denied handler since access is denied", authentication), exception); } this.accessDeniedHandler.handle(request, response, exception); } }
- sendStartAuthentication method is called, and an instance of org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint is passed into FilterSecurityInterceptor on instance creation.
- LoginUrlAuthenticationEntryPoint then delegates the control to org.springframework.security.web.DefaultRedirectStrategy. Finally reply the browser, redirect to "/login" page
Flow of user access /manage/4/approve page without "ADMIN" authority
- org.springframework.security.access.AccessDeniedException is throw by FilterSecurityInterceptor and caught by ExceptionTranslationFilter
- In org.springframework.security.web.access.ExceptionTranslationFilter#handleAccessDeniedException method, this time this.accessDeniedHandler.handle method is called
- Directly return 403 Forbidden status
Add extra handling rules for anonymous user
Modifying Spring Security configuration class to following
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // must put more restricted rule at first .antMatchers("/manage/*/approve").hasAuthority("ADMIN") .antMatchers("/manage/**").hasAuthority("USER") .and() .httpBasic().disable() .formLogin() .loginPage("/login") .loginProcessingUrl("/loginHandler") .failureUrl("/login?error=true") .permitAll() .and() .logout().logoutSuccessUrl("/") .and() .exceptionHandling() .defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), new AntPathRequestMatcher("/manage/1")) .defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.GONE), new AntPathRequestMatcher("/manage/2")); } // ... }
- When accessing "/manage/1", HTTP status 403 will return
- When accessing "/manage/2", HTTP status 410 will return
- When accessing "/manage", will redirect to "/login"
The property authenticationEntryPoint in ExceptionTranslationFilter becomes org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint.
Top comments (0)