I have a spring-boot application using spring-security. The security configuration is split into multiple instances of WebSecurityConfigurerAdapter.
I have one where I configure logout in general:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 1!");
})
.permitAll();
// ... more security configuration, e.g. login, CSRF, rememberme
}
And there is another WebSecurityConfigurerAdapter, where I want to do almost nothing, except adding another LogoutHandler:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 2!");
});
}
Both configure() methods are called. However, if I do log out, only the first LogoutHandler is called. Changing the #Order of both configurations does not change the result.
What is missing in my configuration?
When you create several security configurations Spring Boot will create a separate SecurityFilterChain for each of them. See WebSecurity:
#Override
protected Filter performBuild() throws Exception {
// ...
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
// ...
}
When application gets logout request FilterChainProxy will return only one SecurityFilterChain:
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
// Only the first chain that matches logout request will be used:
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
If you really need modular security configuration I would suggest to create a separate security configuration for logout and other realms. You can define logout handlers as beans (using #Bean annotation) in different configuration classes and collect these handlers in logout configuration:
WebSecurityLogoutConfiguration.java
#Configuration
#Order(99)
public class WebSecurityLogoutConfiguration extends WebSecurityConfigurerAdapter {
// ALL YOUR LOGOUT HANDLERS WILL BE IN THIS LIST
#Autowired
private List<LogoutHandler> logoutHandlers;
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure only logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
// USE CompositeLogoutHandler
.addLogoutHandler(new CompositeLogoutHandler(logoutHandlers));
http.csrf().disable(); // for demo purposes
}
}
WebSecurity1Configuration.java
#Configuration
#Order(101)
public class WebSecurity1Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ... more security configuration, e.g. login, CSRF, rememberme
http.authorizeRequests()
.antMatchers("/secured/**")
.authenticated();
}
// LOGOUT HANDLER 1
#Bean
public LogoutHandler logoutHandler1() {
return (request, response, authentication) -> {
System.out.println("logged out 1!");
};
}
}
WebSecurity2Configuration.java
#Configuration
#Order(102)
public class WebSecurity2Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.permitAll();
}
// LOGOUT HANDLER 2
#Bean
public LogoutHandler logoutHandler2() {
return (request, response, authentication) -> {
System.out.println("logged out 2!");
};
}
}
You should be solving this problem with the CompositeLogoutHandler on your single /logout operation endpoint.
You can still keep two WebSecurityConfigurerAdapter's as desired, but you'll be conglomerating the logout functionality for two LogoutHandlers into a single composite action:
new CompositeLogoutHandler(loggedOutHandler1, loggedOutHandler2);
The keypoint is you should create separated instance of AuthenticationManger.
Here is an sample for multiples WebSecurityAdapter
Related
I am new to Spring Security and Oauth2. In my spring boot application, I have implemented authentication with Oauth2 with following set of changes:
Custom Ouath2 User service is as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
...
}
}
Security Configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/bundle.js")
.antMatchers("/slds-icons/**")
.antMatchers("/assets/**")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-resources")
.antMatchers("/v2/api-docs")
.antMatchers("/api/redirectToHome")
.antMatchers("/test/**");
}
public void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private RegexRequestMatcher requestMatcher =
new RegexRequestMatcher("/api/", null);
#Override
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
};
http.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()//.and().oauth2ResourceServer().jwt()
.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(oauth2UserService())
;
http.cors().disable();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return customOAuth2UserService;
}
}
Content of application.properties is as follows:
spring.security.oauth2.client.registration.keycloak.client-id=abcd
spring.security.oauth2.client.registration.keycloak.client-name=Auth Server
spring.security.oauth2.client.registration.keycloak.scope=api
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
myapp.oauth2.path=https://internal.authprovider.com/oauth2/
spring.security.oauth2.client.provider.keycloak.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.keycloak.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.keycloak.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name
myapp.authentication.type=oauth
Now, with the existing authentication mechanism, I would like to add support for multiple authentication providers: LDAP, Form-Login, etc.
In this regard, I have gone through a few articles:
https://www.baeldung.com/spring-security-multiple-auth-providers
Custom Authentication provider with Spring Security and Java Config
But, I am not getting any concrete idea regarding what changes should I do in the existing code base in order to achieve this.
Could anyone please help here? Thanks.
I've created a simplified setup starting from your code with support for both OAuth2 and Basic Auth.
/tenant2/** will start a basic authentication.
/** (everything else) triggers an OAuth2 Authorization Code authentication.
The key to achieve this is to have one #Configuration class per authentication type.
Let's start with the controllers:
Tenant1HomeController
#Controller
public class Tenant1HomeController {
#GetMapping("/tenant1/home")
public String home() {
return "tenant1Home";
}
}
Tenant2HomeController
#Controller
public class Tenant2HomeController {
#GetMapping("/tenant2/home")
public String home() {
return "tenant2Home";
}
}
Now, the configuration classes:
Tenant1SecurityConfiguration
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority("ADMIN")
.antMatchers("/tenant1/**").authenticated()
.and()
.oauth2Login()
.and()
.cors()
.disable();
}
}
Tenant2SecurityConfiguration (Notice the #Order(90), that's important
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcher("/tenant2/**"))
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/tenant2/**").hasAuthority("BASIC_USER")
.and()
.httpBasic();
http.cors().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("BASIC_USER");
}
}
Finally the configuration:
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: myclient
client-secret: c6dce03e-ea13-4b76-8aab-c876f5c2c1d9
provider:
keycloak:
issuer-uri: http://localhost:8180/auth/realms/myrealm
With this in place, if we hit http://localhost:8080/tenant2/home, will be prompted with the basic auth popup:
Trying with http://localhost:8080/tenant1/home sends you to Keycloak's login form:
UPDATE:
It's completely viable to configure a multitenant application with the configuration above.
The key would be that each authentication provider works with a different set of users (tenants), e.g.:
TENANT 1 (OAuth2 authentication):
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
...
.and()
.oauth2Login()
.and()
...
This first subset of users is federated by the OAuth2 provider, Keycloak in this case.
TENANT 2 (Basic / form /xxx authentication):
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(XXX)
For the second tenant, you can use a userDetailsService that points to a different repository of users (LDAP, database...).
I'm using spring-security-saml2-service-provider for authentication in one of my spring boot applications and I'm using a custom JwtAuthorizationFilter (via a http Authentication header) in a different spring boot application.
They both work perfectly on their own.
Now I need to write a spring boot application that uses both of them. If the JWT token is available (Authentication header), then use the JwtAuthorizationFilter, otherwise use saml2Login.
The SAML2 configuration looks like this: (There is no filter, just the saml2Login)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**").permitAll()
.antMatchers("/**").authenticated().and()
// use SAML2
.saml2Login()
.addObjectPostProcessor(new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
public <O extends OpenSamlAuthenticationProvider> O postProcess(O samlAuthProvider) {
samlAuthProvider.setAuthoritiesExtractor(authoritiesExtractor());
samlAuthProvider.setAuthoritiesMapper(authoritiesMapper());
return samlAuthProvider;
}
})
;
}
The JWT configuration looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/**").authenticated().and()
// use JWT
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
I think I need something like a JwtOrSaml2AuthenticationFilter but don't know how to do that.
The solution is to
Duplicate the configuration with #Order and
Set a header based requestMatcher before the addFilter
#EnableWebSecurity
public class SecurityConfiguration {
#Order(100) // lower number = higher priority
#Configuration
#RequiredArgsConstructor
public static class AppSecurityJWT extends WebSecurityConfigurerAdapter {
final JWTUtil jwtUtil;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This configuration will only be active if the Authorization header is present in the request
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
}
#Order(101)
#Configuration
#RequiredArgsConstructor
public static class AppSecuritySAML2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This whole configuration will only be active, if the previous (100) didn't match
.saml2Login()
//...
;
}
}
I am currently mapping pages using Page Controller.
Every page needs to check for Session, which is a duplicate code.
How do I avoid duplicating this code?
#Controller
public class PageController {
...
#RequestMapping("/view/List")
public String list(Map<String, Object> model) {
String session_chk = Utils.SessionCheck();
if(session_chk.equals("none")){
return "/view/manager/Login";
}
return "/view/member/List";
}
#RequestMapping("/view/Detail")
public String detail(Map<String, Object> model) {
String session_chk = Utils.SessionCheck();
if(session_chk.equals("none")){
return "/view/manager/Login";
}
return "/view/member/Detail";
}
...
You could use Spring Security to avoid all duplicate code related with securing your web application and also it provides buit-in protection against attacks such as session fixation, clickjacking or cross site request forgery. It is the de-facto standard for securing Spring-based applications.
Here you can find a nice series of tutorials to learn Spring Security.
Here you can find an small example in where you'll see how I handled a similar situation to yours using Spring Security configuration only.
This is my Spring Security configuration class
#Configuration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder().encode("user1Pass")).roles("USER")
.and()
.withUser("user2").password(passwordEncoder().encode("user2Pass")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("adminPass")).roles("ADMIN");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
// Only users with admin role will access views starting with /admin
.antMatchers("/admin/**").hasRole("ADMIN")
// Anonymous users (users without sessions) will access this URL
.antMatchers("/anonymous*").anonymous()
// Allowing all users to access login page
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/homepage.html", true)
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/perform_logout")
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(logoutSuccessHandler())
.and()
.exceptionHandling().accessDeniedPage("/accessDenied");
}
#Bean
public LogoutSuccessHandler logoutSuccessHandler() {
return new CustomLogoutSuccessHandler();
}
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Add a filter which will redirect, and add check
#Component
public class SessionFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!request.getRequestURI().contains("view/manager/Login") && "none".equals(Utils.SessionCheck())) {
httpResponse.sendRedirect("view/manager/Login.jsp");
//....
} else {
doFilterInternal(request, response, filterChain);
}
}
You can add it to all urls:
FilterRegistration sessionFilter = servletContext.addFilter("SessionFilter", SessionFilter.class);
sessionFilter.addMappingForUrlPatterns(null, false, "/*");
I have a Spring Boot application that is only exposing a REST API. I need to secure it and I'm using a token-based approach ― specifically JWT.
So far, this is what I have implemented:
//
// The Spring Security configuration class
#EnableGlobalAuthentication
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/login", "/api/logout").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtFilter(), BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
//
// The JWT filter class to check for the token in the HTTP request (headers)
public final class JwtFilter extends GenericFilterBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws
IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest)request;
final String header = req.getHeader("Authorization");
logger.debug("{} {}", req.getMethod(), req.getRequestURI());
if ((null == header) || !header.startsWith("Bearer ")) {
logger.debug("Missing or invalid Authorization header");
}
try {
// Check the token here; the implementation is not relevant here
/*SecurityContextHolder.getContext()
.setAuthentication(manager.authenticate(new JwtToken(JWTParser.parse(header.substring(7)))));*/
chain.doFilter(request, response);
} catch (final AuthenticationException e) {
SecurityContextHolder.clearContext();
// Do some other stuff here
} catch (final ParseException e) { /* ... */ }
}
}
The issue is that the filter executes correctly for every single URI, but I want to be able to exclude some endpoints from the same set. My API is placed in this context /api/* and I want to exclude, for instance, /api/login and /api/logout.
NOTE: My Spring Boot application.yml file doesn't have settings to enable/modify any security-related features.
Filters will be executed for all the endpoints that are configured through HttpSecurity. If you do not want filters to be applied for certain endpoints, include them in a method that configures WebSecurity. For example,
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/api/login", "/api/logout");
}
Please read this post for more details.
I am doing the same as mentioned in the solution
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().ignoringAntMatchers("/api/auth/**").disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("api/content/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("api/auth/signup");
}
Every signup request is still hitting the doFilterInternal() method which was is a custom method.
While running EnableWebSecurity in debug mode i get :
Security filter chain:
[WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
AuthTokenFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor ]
What should i do to solve this ?
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.