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)