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

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.

Related

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/

Unable to access Spring Boot login error page with custom login failure handler

Whenever we try to input wrong credentials in Spring Boot login page, we got Bad Credentials Error with link /login?error I'm trying to limit a login for which I've created a custom login failure handler and whenever I try to provide wrong credentials I'm not able to get any kind of error by Spring Security at this /login?error page in place of this, I'm getting Status 404 Error.
AppConfig
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout();
}
LoginFailureHandler
#Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Autowired
private UserService service;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String username = request.getParameter("username");
Employee emp = service.getByUsername(username);
if(emp!=null) {
if (emp.isAccountNonLocked()) {
if (emp.getFailedAttempt() < UserService.MAX_FAILED_ATTEMPTS - 1) {
service.increaseFailedAttempt(emp);
} else {
service.lock(emp);
exception = new LockedException("Your account has been locked due to three failed attempts"
+"Try again after 24 Hours....");
}
} else {
if(service.unlockWhenTimeExpired(emp)){
exception = new LockedException("Your Account is unLocked now...." +
"try to login again");
}
}
}
super.setDefaultFailureUrl("/login?error"); // I'm not getting this page while a fail login
super.onAuthenticationFailure(request, response, exception);
}
}
Since I'm unable to get this page /login?error I'm not able to display any message regarding failure login.
I assume you are using spring-boot-mvc. You can overwrite the default login page by creating a login.html in the src/main/resources/templates directory.
And in it you can display your error message by utilizing th:if="${param.error}" like so:
<div th:if="${param.error}">Wrong credentials!</div>

Springboot configuration 401 Unauthorized

I have this configure method and i want to make user be able to register but i get 401 Unathorized. It is caused by the .apply(**) and i am not able to do it.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/signin").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider, securityUtils));
}
JwtConfigurer.class
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenProvider jwtTokenProvider;
private final SecurityUtils securityUtils;
public JwtConfigurer(JwtTokenProvider jwtTokenProvider, SecurityUtils securityUtils) {
this.jwtTokenProvider = jwtTokenProvider;
this.securityUtils = securityUtils;
}
#Override
public void configure(HttpSecurity http) {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider, securityUtils);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
So when request is sent to /auth/register i dont want to add .apply(**). Do u have any suggestion please?
In your class that extends WebSecurityConfigurerAdapter where your http configure() method with .apply() is written, you can use the following to tell Spring Boot to bypass or ignore the filter if encountered with the uri for user registration.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/auth/register");
}
Edit: Since you are facing the exception:
Cross-origin Request Blocked (Reason: CORS header ‘Access-Control-Allow-Origin’ missing and Reason: CORS request did not succeed)
it means that your OPTIONS request (preflight request) is failing. Are you using a different project as Front End for your application? If yes, you will need to specify in your spring boot configuration to allow origin and allow the specified methods from that particular origin.
Please refer to this official documentation to learn how to do that. You can enable Cors at Controller level or at global level. This StackOverflow thread should also be helpful in doing the implementation in case you are unable to proceed.

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/**"))

How to capture Spring Security authentication token and save it in a database

We are using a two-pronged approach to user authentication where we are using an in-memory authentication token store in clustered environment and saving the token in a shared database as well. Now I am able to get token using the in-memory store and authenticate users. Everything is working as expected.
I am looking for the following things :
Capture the token and save it in the database? How to achieve this with Spring Security (this should happen after successful authentication)?
If server restarts then I should still be able to validate the token from database.(if user name password is correct.)
#Component
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println(request);
System.out.println(response);
}
}
Following are my HTTP settings:
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(
new AntPathRequestMatcher("/oauth/authorize"))
.disable().headers().frameOptions().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/hello/")
.permitAll().antMatchers("/secure/**").authenticated();
}
PS : It looks like adding an interceptor can help, however I am not sure how do I to get token from response. Any suggestions?

Resources