Custom AuthenticationFailureHandler is not working as expected - java

Registered handler in security configuration:
.formLogin().loginPage(/login).failureHandler(new CustomAuthenticationFailureHandler())
My custom failure handler:
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String failureUrl="/login?error";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
this.redirectStrategy.sendRedirect(request, response, this.failureUrl);
}
}
onAuthenticationFailure is getting triggered when auth failures happen but along with that one more request is also triggering by Spring, please find the another req stack,
LoginUrlAuthenticationEntryPoint.commence(HttpServletRequest, HttpServletResponse, AuthenticationException) line: 169
ExceptionTranslationFilter.sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException) line: 204
ExceptionTranslationFilter.handleSpringSecurityException(HttpServletRequest, HttpServletResponse, FilterChain, RuntimeException) line: 178
So this request is overriding my sendredirect request.
Should I need to configure/do anything else to achieve this ? Please help on this.
Thanks in advance
My configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(SignUpFilter(userService), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/",
"/register",
"/retrieveUsernameForm",
.permitAll()
.and()
.anonymous()
.authenticationProvider(anonymousAuthenticationProvider())
.authenticationFilter(anonymousAuthenticationFilter())
.and()
.formLogin()
.loginPage(SecurityController.URI_LOGIN)
.failureHandler(new CustomAuthenticationFailureHandler())
.permitAll()
.defaultSuccessUrl(securityController.createUri(SecurityController.PATH_PART_LOGIN))
.and()
.rememberMe()
.rememberMeParameter("remember-me")
.key(REMEMBERME_TOKEN_KEY)
.rememberMeServices(tokenBasedRememberMeServices())
.and()
.logout()
.permitAll()
.and()
.headers()
.frameOptions().disable()
.addHeaderWriter(new StaticHeadersWriter("X-Frame-Options", "SAMEORIGIN"))
.and()
.csrf().disable();
http.formLogin()
.addObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
#Override
public UsernamePasswordAuthenticationFilter postProcess(UsernamePasswordAuthenticationFilter filter) {
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategyForContentAndStoreUsers());
return filter;
}
});
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}

Related

Spring Web Security entry point triggers

I am getting started with Spring Web Security for my application and I am trying to implement stateless JWT authentication. Curretly, the configure method in the Web Security config is the following
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/register", "/authenticate/{uuid}").permitAll()
.anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
My jwtAuthenticationEntryPoint is the following:
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
log.warn("Responding with unauthorized error. Message - {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource.");
}
}
The authentication works correctly besides for the "/authenticate/{uuid}" endpoint. The request is allowed (Status 200 and correct return of the function) but I keep getting the warning from the jwtAuthenticationEntryPoint class ("Responding with unauthorized error") in the console.
Why is the EntryPoint getting triggered for that specific request and how can I resolve it?
EDIT:
AuthenticationController:
#RestController
#CrossOrigin
public class AuthenticationController {
#RequestMapping(value = "/authenticate/{uuid}", method = RequestMethod.GET)
public ResponseEntity<?> authenticate(#PathVariable String uuid){
return ResponseEntity.ok(uuid);
}
}
Pls use web.ignoring() to try as the below:
public void configure(WebSecurity web) throws Exception {
String [] notauthlist = new String[]{"/login", "/register","/authenticate/**"};
web.ignoring().antMatchers(notauthlist);
}
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}

Maximum user sessions not working when user signs in Springboot/Security

I'm trying to add maximum sessions to my spring security context, however the settings are not taking effect. I've been following this code to implement JWT tokens on a spring backend using this tutorial.
I've added the relevant code I believe to the security config:
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
return filter;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.antMatchers("/api/configuration/**")
.permitAll()
.anyRequest()
.authenticated()
.and().sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry());
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
However when I try and login with a user - the user can login however many times they wish.
I tried looking in the user registry to see if there were multiple user principles being created for each login but there isn't.
I also tried to step through ConcurrentSessionControlAuthenticationStrategy.onAuthentication however that doesn't seem to be being called - but I have no idea why.
Edit:
Adding the customer AuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LogManager.getLogger(AuthController.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
log.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Sorry, You're not authorized to access this resource.");
}
}
EDIT:
The session is setting set correctly in the session registry now. However because my login endpoint has permitAll() (I think)
ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called and adding the newly created session to the registry.
I believe it might be something to do with the way my CustomAuthenticationProvider is setup:
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsernameOrEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
So signin is always allowing new logins as ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called after authentication.

Spring referer is null in login and won't redirect

I have a Spring-boot app that use security. What I want is to redirect to the original URL requested. I needed to implement a custom SimpleUrlAuthenticationSuccessHandler so I have this code:
public class MySavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public MySavedRequestAwareAuthenticationSuccessHandler() {
setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// do my stuff
logger.info(getTargetUrlParameter()); // this is = '/'
//logger.info(getDefaultTargetUrl()); // this is null
redirectStrategy.sendRedirect(request, response, "/");
}
}
The problem is that after login is never redirecting to the referer. I had to add the redirectStrategy.sendRedirect(request, response, "/"); part.
In the LoginController, "Referer" in the header is always null when is redirected from spring security. If I put in the url "xxxx/login" the value is set. Not very useful:
#RequestMapping(value = {"/login"}, method = RequestMethod.GET)
public ModelAndView login(HttpServletRequest request) {
String referer = request.getHeader("Referer");
modelAndView.addObject ("referer", referer); // this is NULL
//
modelAndView.setViewName("login");
return modelAndView;
}
This is my security config:
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/").authenticated()
.and()
.csrf().disable()
.formLogin()
.loginPage("/login")
.failureHandler(customAuthenticationFailureHandler)
.usernameParameter("email")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logged-out")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied")

Implement logout in Spring for Single Page Application

I'm using Java Web Token (JWT) for authentication in my web app. I want to create a /logout Rest endpoint that deletes the JSession cookie on the client side, invalidates the session AND does whatever else is necessary.
The front end is a SPA written in React.
I have the following configure method in the WebSecurityConfig class that extends WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.and()
.authorizeRequests()
.antMatchers("/signup").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
I had the following code, but it returns 404 Not found error with the path set to /login. I want to get a HTTP response of 200 and some cleanups instead. what should I do ?
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).logoutSuccessUrl("/login").invalidateHttpSession(true)
After some research I found out that if I want to use stateless Rest API design, I shouldn't use any cookie, including JSESSIONID.
Therefore I changed my code to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
Could you first try for login request like this. First add JWTLoginFilter in your WebSecurityConfig.
Pasting code from my sample project:
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
You wouldn't require CORSFilter if your front end and back end are on same server.
Also find below JWTLoginFilter class
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter
{
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(String url, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
AccountCredential class is simple POJO class containing two fields username and password, which I was using to receive the request.
Also please note that UsernamePasswordAuthenticationFilter that we are using require two fields in login request 'username' and 'password'. Like {"username":"user1","password":"secret1"}

How to restrict access to .html pages using Spring Boot REST

I'm trying to restrict access to a page called dashboard.html to unauthenticated users. So far, I've had no success. Here's my WebSecurityConfigurerAdapter:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**"
).permitAll()
.and()
.authorizeRequests()
.antMatchers("/dashboard/**", "/dashboard.html/**").authenticated()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new StatelessLoginFilter("/login", tokenAuthenticationService, userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout()
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterAfter(new CsrfTokenFilter(), CsrfFilter.class);
}
}
Whenever I have it set up this way, whenever I try to login, an infinite redirect loop is caused. The browser tries to navigate to dashboard.html but is restricted. This causes a redirect to the login page, which tries to redirect to the dashboard since there is a valid token.
If I have it set up like below, everyone can access dashboard.html and make calls to the /dashboard endpoint which is not desired:
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**",
"/dashboard/**", "/dashboard.html/**").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
My login uses JWT tokens and uses the filter below to set the SecurityContext placeholder:
class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
private final TokenAuthenticationService tokenAuthenticationService;
private final CustomUserDetailsService userDetailsService;
protected StatelessLoginFilter(String urlMapping, TokenAuthenticationService tokenAuthenticationService,
CustomUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(urlMapping));
this.userDetailsService = userDetailsService;
this.tokenAuthenticationService = tokenAuthenticationService;
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
final BusinessUser user = new ObjectMapper().readValue(request.getInputStream(), BusinessUser.class);
final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(
user.getEmail(), user.getPassword());
return getAuthenticationManager().authenticate(loginToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
final BusinessUser authenticatedUser = userDetailsService.loadUserByUsername(authentication.getName());
final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
tokenAuthenticationService.addAuthentication(response, userAuthentication);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
I'm using the line SecurityContextHolder.getContext().setAuthentication(userAuthentication); to set the authentication. This works perfectly fine. If a user is found in the DB matching the credentials sent from the user, then the security context is usable to retrieve various data associated to the user.
MY QUESTION: How can I restrict the page dashboard.html and calls to the /dashboard endpoint to unauthenticated users (those without an authentication object inside the SecurityContextHolder)?
You can use a custom RequestMatcher in combination with denyAll. First, your custom matcher:
public class PermittedPagesMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest httpServletRequest) {
if (matchesToPaths(httpServletRequest,"/index.html", "/", "/login.html","/signup.html", "/videos/**", "/login", "/logout", "/images/**", "/fonts/**", "/css/**", "/js/**", "/pages/**", "/sass/**", "/dashboard/**", "/dashboard.html/**")) {
return true;
}
if (matchesToPaths(httpServletRequest, "/dashboard/**", "/dashboard.html/**")) {
return httpServletRequest.getUserPrincipal() == null;
}
return false;
}
private boolean matchesToPaths(HttpServletRequest httpServletRequest, String... paths) {
for (String p : paths) {
if (new AntPathRequestMatcher(p).matches(httpServletRequest)) {
return true;
}
}
return false;
}
}
This custom RequestMatcher filters your request to permitted pages to all of your default pages and the dashboard is only available if the request is not authenticated.
Second, combine the matcher and denyAll()
http
.authorizeRequests()
.requestMatchers(new PermittedPagesMatcher())
.permitAll()
.and()
.antMatchers("/dashboard/**", "/dashboard.html/**")
.denyAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
denyAll() ensures, that by default no one is allowed to access this page.
Attention: The order of permit and deny is important!

Categories

Resources