Keycloak secured Spring Boot Application: Try to configure publicly available pages - spring-boot

We have a Keycloak server that is securing our Spring Boot application. That works fine so far. However we now need a forgot password page, which has to be reachable without login of course. We are not able to accomplish this.
We are implementing a KeycloakWebSecurityConfigurerAdapter and overriding the configure(HttpSecurity) method. Implementation looks like this:
super.configure(http);
http.csrf().disable()
.exceptionHandling()
.accessDeniedPage("/accessDenied");
http.anonymous.disable();
http.authorizeRequests();
With that code only, indeed every page is freely accessible, except the root page. As soon as we add calls to antMatcher() or anyRequest() method followed by permitAll() or fullyAuthenticated(), just to achieve the differentiation in allowed and disallowed pages, all pages are secured/disallowed. We played around a lot and tried to find help here and anywhere else but found no solution. Current implemented example is:
http.authorizeRequests().antMatchers(HttpMethod.GET, "/public/forgotPassword").permitAll()
.anyRequest().fullyAuthenticated();
The result is, as stated, that every pages needs authentication, also the public/forgotPassword page.
Does anyone have an idea about what the problem might be?
Thx in advance!

I've implemented this springboot.keycloak.mre1 to demonstrate — in a stripped-down way — how a previous project I worked on similarly implemented what I think you're requesting.
In a nutshell, the gist of the solution is…
…
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/login", "/login.html")
.permitAll().antMatchers("/dashboard", "/dashboard.html")
.authenticated();
}
…
}
The steps to build and run the MRE are straightforward. But if you get stuck building or running it, let me know if I can help you in any way.
And if I've completely misinterpreted what you've requested, then please feel free to clone and modify the project to be more like your use case. If you then upload your modifications, and elaborate on the specifics of your use case in the repo's Issues area, I will investigate and get back to you.
1 The MRE uses docker-compose because the original project it's based on did.

In my applications I am using the following config scheme:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.wissance.orgstructure.application.configuration;
import com.goodt.drive.goals.application.authentication.AppAuthenticationEntryPoint;
import com.goodt.drive.goals.application.services.users.KeyCloakUserInfoExtractorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin(); // it is to fix issue with h2-console access
http.cors();
http.csrf().disable()
.authorizeRequests().antMatchers("/", "/callback", "/login**", "/webjars/**", "/error**").permitAll()
.and()
.authorizeRequests().antMatchers("/api/**").authenticated()
.and()
.authorizeRequests().antMatchers("/h2-console/**").permitAll()
.and()
.authorizeRequests().antMatchers("/swagger-ui.html").permitAll()
.and()
.authorizeRequests().antMatchers("/swagger-ui/**").permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new AppAuthenticationEntryPoint())
.and()
.logout().permitAll().logoutSuccessUrl("/");
}
#Bean
public PrincipalExtractor getPrincipalExtractor(){
return new KeyCloakUserInfoExtractorService();
}
#Autowired
private ResourceServerTokenServices resourceServerTokenServices;
}
#ControllerAdvice
public class AppAuthenticationEntryPoint implements AuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 401
logger.debug(String.format("Access to resource is denied (401) for request: \"%s\" message: \"%s\"", request.getRequestURL(), authException.getMessage()));
setResponseError(response, HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
#ExceptionHandler (value = {AccessDeniedException.class})
public void commence(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
// 403
logger.debug(String.format("Access to resource is forbidden (403) for request: \"%s\" message: \"%s\"", request.getRequestURL(), accessDeniedException.getMessage()));
setResponseError(response, HttpServletResponse.SC_FORBIDDEN, String.format("Access Denies: %s", accessDeniedException.getMessage()));
}
#ExceptionHandler (value = {NotFoundException.class})
public void commence(HttpServletRequest request, HttpServletResponse response, NotFoundException notFoundException) throws IOException {
// 404
logger.debug(String.format("Object was not found (404) for request: \"%s\" message: \"%s\"", request.getRequestURL(), notFoundException.getMessage()));
setResponseError(response, HttpServletResponse.SC_NOT_FOUND, String.format("Not found: %s", notFoundException.getMessage()));
}
#ExceptionHandler (value = {Exception.class})
public void commence(HttpServletRequest request, HttpServletResponse response, Exception exception) throws IOException {
logger.error(String.format("An error occurred during request: %s %s error message: %s",
request.getMethod(), request.getRequestURL(), exception.getMessage()));
// 500
setResponseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.format("Internal Server Error: %s", exception.getMessage()));
}
private void setResponseError(HttpServletResponse response, int errorCode, String errorMessage) throws IOException{
response.setStatus(errorCode);
response.getWriter().write(errorMessage);
response.getWriter().flush();
response.getWriter().close();
}
private final Logger logger = LoggerFactory.getLogger(this.getClass());
}
Config of spring security (application-local.yml) related to KeyCloak was listed below, in my app i have at least 3 different keycloak servers and i switch them from time to time, all my KeyCloak values passes from base settings (application.yml) currently using defined in appConfig.keyCloak.using as yml placeholder to selected keycloak? example of spring security config section:
security:
basic:
enabled: false
oauth2:
client:
clientId: ${appConfig.keyCloak.using.clientId}
clientSecret: ${appConfig.keyCloak.using.clientSecret}
accessTokenUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/token
userAuthorizationUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/auth
authorizedGrantTypes: code token
scope: local
username: ${appConfig.keyCloak.using.serviceUsername}
password: ${appConfig.keyCloak.using.servicePassword}
resource:
userInfoUri: ${appConfig.keyCloak.using.baseUrl}/protocol/openid-connect/userinfo
Example of one of KeyCloak server config:
baseUrl: http://99.220.112.131:8080/auth/realms/master
clientId: api-service-agent
clientSecret: f4901a37-efda-4110-9ba5-e3ff3b221abc
serviceUsername: api-service-agent
servicePassword: x34yui9034*&1
In my above example all pages that have the /api path in their url, i.e. /api/employee or /api/employee/find/? or others, are accessible only after authentication + authorization. All Swaggers pages or the login page are available without any authentication.

Related

How to configure Spring-Security (Spring 6) for not having Filters executed on unsecured routes?

somewhat related to this other stackoverflow topic which doesn't give a proper solution nor is applicable to Spring 6 (Spring Boot 3).
I came up with a basic spring-boot app to make my case.
There is a controller with two end-points, where one must be secured and the other accessible.
#RestController
public class TestController {
#GetMapping("/secured-api")
public String securedApi() {
return "secured";
}
#GetMapping("/public/open-api")
public String openApi() {
return "open";
}
}
Security context as follow, imagine that MyFilter is doing something fancy, e.g: validating a JWT token and firing an exception if the token is invalid / expired.
#Configuration
public class ComponentSecurityContext {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.addFilterAt(new MyFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(customizer -> customizer
.requestMatchers(new AntPathRequestMatcher("/public/**"))
.permitAll()
.anyRequest()
.authenticated())
.build();
}
public static class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Filter is called for uri: " + request.getRequestURI());
// performs some authentication
filterChain.doFilter(request, response);
}
}
}
Executing the following two curls on the server
curl http://localhost:9003/public/open-api
curl http://localhost:9003/secured-api
is triggering MyFilter
Filter is called for uri: /public/open-api
Filter is called for uri: /secured-api
I would expect MyFilter to be called only for secured end-points, I don't care if an expired token is used to access an unprotected end-point.
Any advise on how to properly wire spring-security to achieve just that?
Working solution where the filter is scoped by the securityMatcher:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/public/**")))
.addFilterAt(new MyFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.build();
}

Spring boot security, always redirects to login page, if navigate through address bar

I have a react project, and the security works fine untill I navigate within page - i.e. clicking buttons etc. But, if I refresh page, or input url directly into adress field, it always navigates to login form.
This is my security config:
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.and()
.csrf().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
web.ignoring().antMatchers("/rest/system/getVersion");
}
}
This is restAuthenticationEntryPoint -
#Slf4j
#Component
#RequiredArgsConstructor
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ResponseWrapMessage responseWrapMessage;
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.error(e.getLocalizedMessage(), e);
BaseResponse baseResponse = new BaseResponse(UNKNOWN_ERROR, e.getLocalizedMessage());
insufficientAuthenticationWrapper(baseResponse, e);
responseWrapMessage.wrap(httpServletResponse, baseResponse);
}
private void insufficientAuthenticationWrapper(BaseResponse baseResponse, AuthenticationException e) {
if (e instanceof InsufficientAuthenticationException) {
baseResponse.setContent(CREDENTIAL_NO_VALID);
}
}
}
This is accessDeniedHandler:
#Slf4j
#Component
#RequiredArgsConstructor
public class RestAccessDeniedHandler implements AccessDeniedHandler {
private final ResponseWrapMessage responseWrapMessage;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error(accessDeniedException.getLocalizedMessage(), accessDeniedException);
BaseResponse baseResponse = new BaseResponse(UNKNOWN_ERROR, accessDeniedException.getLocalizedMessage());
responseWrapMessage.wrap(response, baseResponse);
}
}
How can it be changed? I want to persist session, untill I do the logout.
UPDATE:
I see that cookie JSESSIONID is set on the logine page like -
set-cookie: JSESSIONID=9E3BD2B1CF7C69A49902DAA7E71E393E; Path=/mctm-bh; HttpOnly
And then it is sent out when I navigate pressing buttons within the page -
Cookie: JSESSIONID=9E3BD2B1CF7C69A49902DAA7E71E393E
But if I press enter in the address URL then it is NOT sent out, and hence I am redirected to login page
How can it be changed? Maybe I there is some problem with the cookie - like there is now expires attribute. But.. specification does not say that it should be additionally customized and also it is not clear how to do it.
!UPDATE2! I heard, that it is a common problem with basic authorization. If we authorise with 'Authorization: basic ...' then browser saves auth in some cash. And that cash is invalidated if we type something in browser address field. And the way out is not to use basic auth, and to migrate as an option to spring tokens solution.
And some more details could also be found here:
https://habr.com/ru/post/488388/
I suggest you check that your session tracking mechanics is working, e.g. check that you have cookies allowed in your browser, and define the tracking-mode parameter explicitly in the application.properties:
server.servlet.session.tracking-modes=COOKIE
This chooses where to store the JSESSIONID — in the cookie
Typing in the Address bar is equivalent to creating a new session/ or opening the link in a new tab. This can be one reason why it is asking for a new log-in every time you navigate through the address bar.
I heard, that it is a common problem with basic authorization. If we authorise with 'Authorization: basic ...' then browser saves auth in some cash. And that cash is invalidated if we type something in browser address field. And the way out is not to use basic auth, and to migrate as an option to spring tokens solution. And some more details could also be found here: https://habr.com/ru/post/488388/

Spring Security: oauth2Login redirect only on certain paths

I have Spring Security configured to authenticate my website, such that all paths are automatically redirected to the OAuth2 authorization URL (using .oauth2Login()). However, I want unauthenticated requests to the API (i.e. /api/**) to return 401 Unauthorized instead of being redirected. I can't figure out how to do this. Any help would be much appreciated.
Here is my current configuration:
http
.authorizeRequests()
.antMatchers("/api/auth/oauth2/callback").permitAll()
.anyRequest().authenticated()
.oauth2Login()
.authorizationEndpoint()
.baseUri(this.oauth2AuthorizationRedirectBaseUri);
http.logout()
.logoutUrl("/auth/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
You can define a custom authentication entry point for /API/** and add t to your configuration:
#Component
public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("developers");
super.afterPropertiesSet();
}
}
in your Http security configs add:
http.
...
.exceptionHandling()
.defaultAuthenticationEntryPointFor(
new CustomAuthenticationEntryPoint(),
new AntPathRequestMatcher("/api/**"))

Spring Security blocks POST requests despite SecurityConfig

I'm developing a REST API based on Spring Boot (spring-boot-starter-web) where I use Spring Security (spring-security-core e spring-security-config) to protect the different endpoints.
The authentication is done by using a local database that contains users with two different sets of roles: ADMIN andUSER. USER should be able toGET all API endpoints and POST to endpoints based onrouteA. ADMIN should be able to do the same asUSER plus POST andDELETE to endpoints based on `routeB
However the behavior I'm getting is that I can do GET requests to any endpoint but POST requests always return HTTP 403 Forbidden for either type of user - ADMIN and USER - which is not expected what I'm expecting based on my SecurityConfiguration.
Any ideas of what am I missing?
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
logger.info("Using database as the authentication provider.");
builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/*").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/*").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
RouteBController .java
#RestController
public class RouteBController {
static final Logger logger = LoggerFactory.getLogger(RouteBController.class);
public RouteBController() { }
#RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
public String getStuff() {
return "Got a hello world!";
}
#RequestMapping(value = "routeB", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.POST)
public String postStuff() {
return "Posted a hello world!";
}
}
RESTAuthenticationEntryPoint.java
#Component
public class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("AppNameHere");
super.afterPropertiesSet();
}
}
BEFORE disabling the CSFR as a way of fixing this issue, please check the resources on Mohd Waseem's answer to better understand why it is important and to have an idea of how it can be properly set up. As RCaetano has said, CSFR is here to help us from attacks and it should not be disabled blindly.
Since this answer still explained the 2 issues on my original questions, I'll leave it as the marked answer to create awareness about possible issues with the CSFT and security routes but don't take it literally.
There were 2 issues in SecurityConfiguration.java that made it misbehave.
Although the 403 Forbidden error message didn't contain any message indication of why it was failing (see example below) it turns out it was due to having CSRF enabled. Disabling it allowed for POST and DELETE requests to be processed.
{
"timestamp": "2018-06-26T09:17:19.672+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/routeB"
}
Also the expression used in antMatched(HttpMethod, String) for RouteB was incorrect because /routeB/* expects it to have something after /. The correct configurtion is /routeB/** since more paths can be present (or not).
The corrected SecurityConfiguration.java is
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
authorizeRequests().antMatchers(HttpMethod.GET, "/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeA/**").hasAnyRole("ADMIN", "USER")
.antMatchers(HttpMethod.POST, "/routeB/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/routeB/**").hasRole("ADMIN").and().
requestCache().requestCache(new NullRequestCache()).and().
httpBasic().authenticationEntryPoint(authenticationEntryPoint).and().
cors().and().
csrf().disable();
}
Source: StackOverflow em Português
Cross-site request forgery is a web security vulnerability that allows an attacker to induce users to perform actions that they do not
intend to perform.
In your case disabling CSRF protection exposes user to this vulnerability.
Note: If it was pure Rest API with O-Auth protection then CSRF was not
needed. Should I use CSRF protection on Rest API endpoints?
But In your case when user logs in a session is created and cookie is returned in response and without CSRF token Attacker can exploit it and perform CSRF.
It wouldn't be a good idea to disable CSRF instead you can configure your app to return CSRF token in response headers and then use it in all your subsequent state changing calls.
Add this line of code in your SecurityConfiguration.java
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
CsrfTokenResponseHeaderBindingFilter.java
public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
if (token != null) {
response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
}
filterChain.doFilter(request, response);
}
}
Header Response form Server:
Note that we now have CSRF token in the header. This will not change untill the session expires.
Also read: Spring Security’s CSRF protection for REST services: the client side and the server side for better understanding.
It's simple CSRF enabled issue that doesn't allow POST requests. I faced the same problem here's the solution: (Explained)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/form").hasRole("ADMIN") // Specific api method request based on role.
.antMatchers("/home","/basic").permitAll() // permited urls to guest users(without login).
.anyRequest().authenticated()
.and()
.formLogin() // not specified form page to use default login page of spring security
.permitAll()
.and()
.logout().deleteCookies("JSESSIONID") // delete memory of browser after logout
.and()
.rememberMe().key("uniqueAndSecret"); // remember me check box enabled.
http.csrf().disable(); // ADD THIS CODE TO DISABLE CSRF IN PROJECT.**
}
Above code:
http.csrf().disable();
will solve the problem.

Send a http 401 error code instead of default login page, spring security

I am using a basic authorization with the Spring Security. I configure the latter via Java config.
I would like to send to a client the HTTP 401 error code with the message "Invalid login and password" if they are invalid. However, currently Spring Security simply displays me a default pop-up window.
Here is my Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated().and()
.httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
http.exceptionHandling().authenticationEntryPoint(new AjaxAuthorizationPoint());
}
As far as I understood, I have to add the custom authentificationEntryPoint to handle the case of the invalid credentials i.e. I have to send there a 401 error code with the error message
Here is the code for it. For the sake of simplicity, the body of the method is rather simple.
public class AjaxAuthorizationPoint extends BasicAuthenticationEntryPoint{
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
System.out.println("blah");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
However, the method public void commence() doesn't fire up, when I enter an invalid login and password and Spring simply sends me the default login pop-up window.
How can I redefine the default strategy? How can I configure the Spring security to send a HTTP 401 error code instead of displaying a default login page?
I think, I have found a solution for my problem. Here the code that does exactly what I need.
Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new AjaxAuthorizationPoint("/ajax_login"));
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
}
And custom ajax authorization point:
public class AjaxAuthorizationPoint extends LoginUrlAuthenticationEntryPoint {
public AjaxAuthorizationPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setStatus(403);
response.getWriter().print("Invalid login/password");
response.getWriter().flush();
}
}
Would appreciate any code review.

Resources