UsernamePasswordAuthenticationFilter skips success handler - java

I am having a hard time configuring my spring security. The problem is, my authentication filter always skips my success and failure handlers whenever I authenticate via a custom UsernamePasswordAuthenticationFilter. I don't seem to know why this happens.
First off, I pass the authentication parameter as JSON, and filter out the username and password, then I pass those two parameters into a new UsernamePasswordAuthenticationToken(username, password) then I get the authentication manager and authenticate the returned token. At the point of success full authentication I expect that the success handler should take over but no it doesn't get called at all.
This is my security configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/signup")
.permitAll()
.antMatchers("/", "/security/login", "/request", "/request.html")
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/security/login")
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/logout")
.permitAll()
.and()
.addFilterAfter
(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//.and()
.userDetailsService(userDetailsServiceBean());
}
The relevant beans are
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetails();
}
#Bean
public RestAuthenticationSuccessHandler authenticationSuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
#Bean
JsonAuthenticationFilter authenticationFilter() throws Exception {
logger.debug("Authenication filter processing loggin request ");
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
The filter is
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
and finally my success handler
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
logger.debug("Successful login");
System.out.println("\n\n\n\n\n\n\n\nresponse here\n\n\n\n\n\n\n");
response.getWriter().write("{This is a login success response}");
response.getWriter().flush();
response.getWriter().close();
}
I have been battling for too long

Spring Security will back off on a given bean configuration when you supply that bean.
So, because you supplied your filter (JsonAuthenticationFilter), Spring Security expects that you'll know best how to compose it.
So, then, you'd instead do:
#Bean
JsonAuthenticationFilter authenticationFilter() {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
// .. other configs
filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler());
}
It looks like there is a lot going on, so if that doesn't solve your issue, feel free to put together a sample, say on GitHub, and I'd be happy to look it over.

Related

Spring Boot fails to boot up after add #EnableWebSecurity

I'm trying to add security headers to my Spring Boot application.
It already had a Java class with multiple filters extending from WebSecurityConfigurerAdapter. But whenever I try to add the annotation #EnableWebSecurity to this class or even with a new custom one I always receive NullPointerException for the bean springSecurityFilterChain.
Changing the order to add some filters seems to solve this problem but whenever I try to enter the app I can't because it seems the HTTP Authorization header field is null (which I recover inside one of my custom filters).
Do any have a clue of what is happening?
EDIT: After some days of cheking this I noted that the Authorization header was not the problem as the code is built to let that call enter without it and before any change it was already sent without header.
Still with the same call and the changes I'm receiving a 403 FORBIDDEN (before any change this call was receiving 302 FOUND).
This happens before even reaching the controller and I can only get debugging until the filter.
As there were no other changes in the code except the #EnableWebSecurity and the way to add one filter I suspect the problem is around here but i can't find what is causing it exactly.
EDIT: I'm adding the code in case anyone need to see it.
This is the class that has the multiple filters:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity //ADDED THIS ONE
public class MultipleEntryPointsSecurityConfig {
#Configuration
#Order(1)
public class OauthSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Bean
public CustomFilterOneFilter customFilterOneFilter() {
final CustomFilterOneFilter filter = new CustomFilterOneFilter ("/testLogin");
filter.setRestTemplate(restTemplate);
return filter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/login")
.cors()
.and()
.csrf().disable()
//CHANGED THIS
// .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
//FOR THESE TWO
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/testLogin"))
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSecurityFilter authenticationJwtTokenFilter() {
return new JwtSecurityFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.addFilterAfter(new UsernamePasswordAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//CHANGED THE BELOW ONE FOR THE TWO ABOVE
//http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(3)
public static class PublicConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll();
}
}
}
And this is the custom filter where I try to recover the Authorization header:
#Component
public class JwtSecurityFilter extends OncePerRequestFilter{
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
//FAILS HERE!
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
SecurityContextHolder.getContext().setAuthentication(null);
chain.doFilter(request, response);
return;
}
...
}
}

How to bypass spring security on an authenticated endpoint for specific domain?

I am using jwt token based spring security.
I have an endpoint '/sample-endpoint' which requires authentication. However, I need to bypass security for this endpoint when the request comes from a specific domain called xyz.com.
Is it possible to do so? If so, how to do that?
Here is what I have so far.
SecurityConfig
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// cant add the end point here as it would open up for everybody.
public static final String[] unauthedUrls = { "healthcheck","some-other-endpoint"}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.disable()
.csrf()
.disable()
.cors()
.and()
.headers().frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterAfter(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(unauthedUrls)
.permitAll()
.anyRequest()
.authenticated();
}
Here is JwtSecurityFilter implementation.
public class JwtSecurityFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtSecurityFilter.class);
private static final String JWT_PREFIX = "Bearer ";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
setAuthenticationContext(request);
chain.doFilter(request, response);
}
private void setAuthenticationContext(HttpServletRequest request) {
try {
String token = getJwt(request);
if (StringUtils.isBlank(token)) {
throw new RuntimeException("Authorization token not provided");
}
// some logic here...
} catch (Exception ex) {
if (request != null && Arrays.stream(SecurityConfig.unauthedUrls).anyMatch(url -> request.getRequestURI().contains(url))) {
// it's a URL that isn't authenticated so an exception here is normal
// if we couldn't get a token
return;
}
LOGGER.warn("Unable to authenticate request: {} {}", ex.getMessage(), request == null ? null : request.getRequestURI());
}
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JWT_PREFIX)) {
return "";
}
return authHeader.replaceFirst(Pattern.quote(JWT_PREFIX), "");
}
}
What you want is to ignore certain URLs for this override the configure method that takes WebSecurity object and ignores the pattern. For example, using the api:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/signup");
}
And remove that line from the HttpSecurity part. This will tell Spring Security to ignore this URL and don't apply any filters to them.
I have a better way:
http
.authorizeRequests()
.antMatchers("/api/v1/signup/**").permitAll()
.anyRequest().authenticated()

Create a custom annotation to validate userToken from header

I am trying to create an independent packageable jar with a custom annotation, which on inclusion in a controller mapping function (and taking userToken as input in header), returns a boolean whether the user is authenticated or now.
// Expected way of inclusion
public #ResponseBody boolean isAuthenticated(#Authenticator(#RequestHeader("userToken")) Boolean isUserAuthenticated) {
return isUserAuthenticated;
}
I know that this won't be the right syntax, since using this code gives the error that RequestMapping cannot be converted to String (and annotations only accept primitive values).
I am also open to other approaches, but it should have the flexibility to return authentication boolean only when needed and not through global interception.
Important: Please note #Authenticator comes from an independent package, imported in the current package through Maven. Would HTTPServletRequest pass in ConstraintValidator.
Use the spring security BasicAuthenticationFilter :
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
private AuthenticationManager authenticationManager;
public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
this.authenticationManager=authenticationManager;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// do you checks here
super.doFilterInternal(request, response, chain);
}
}
Then add this to your security config with something like:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(new MyBasicAuthenticationFilter(authenticationManager());
}
#Bean
public AuthenticationManager authenticationManager() {
return new MyAuthenticationManager();
}

Spring Boot: 2 different login pages for 2 URLs

I have a Spring Boot application. I have 2 login pages for 2 different sets of users in the system.
Any URL starting with /expert, the user should have the role of either EXPERT or ADMIN. The user uses form login to login into the system, and the login page path is /login.
For some URLs, such as css, js, etc, no authentication is required.
For all other URLs, the user needs no special role, an authentication is enough. The login page for the user should be /loginTwo.
I looked at this stackoverflow question and this documentation to implement this. However, when I access a URL that contains /expert, it takes me to the login page /loginTwo, instead of /login.
Here is my code below:
#Autowired
#Qualifier("userService")
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new Md5PasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Configuration
#Order(1)
public static class ExpertWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login/**").permitAll();
http
.antMatcher("/expert/**")
.authorizeRequests()
.anyRequest().access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.and()
.formLogin()
.loginPage("/login").permitAll();
}
}
#Configuration
public static class StudentWebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Here in stu");
http
.authorizeRequests()
.antMatchers("/css/**,/js/**").permitAll()
.antMatchers("/error/**").permitAll()
.antMatchers("/student/**").permitAll()
.antMatchers("/filter/**").permitAll()
.antMatchers("/loginTwo").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.csrf();
http
.formLogin()
.loginPage("/loginTwo").permitAll()
.and()
.logout().permitAll();
http
.sessionManagement()
.maximumSessions(20)
.expiredUrl("/loginTwo")
.maxSessionsPreventsLogin(false);
http
.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
}
Request you to help me with this.
You need to add different authentication filters for each different endpoint.
For HttpSecurity you can define something like this, giving the constructor your UserDetailsService and passing the authentication manager as well:
.addFilterBefore(new FirstLoginFilter("/api/login/first", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new SecondLoginFilter("/api/login/second", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AdminLoginFilter("/api/login/admin", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
The implementation for the filters would look something like this.
Let's start with abstract authentication filter which is parent for all the above ones:
public abstract class LoginFilter extends AbstractAuthenticationProcessingFilter {
protected final SimpleUserDetailsService userService;
public LoginFilter(String pattern, SimpleUserDetailsService userService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(pattern));
this.userService = userService;
this.setAuthenticationManager(authManager);
this.setAuthenticationSuccessHandler(new FormAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new FormAuthenticationFailureHandler());
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws ServletException {
User authenticatedUser = this.userService.loadUserByUsername(authentication.getName());
UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
}
And for example one of the implementations for the actual filter:
public class FirstLoginFilter extends LoginFilter {
public FirstLoginFilter(String pattern, SimpleUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(pattern, userDetailsService, authManager);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
User user = (User)(new ObjectMapper()).readValue(request.getInputStream(), User.class);
UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authentication = this.getAuthenticationManager().authenticate(loginToken);
if(!Role.isRolePresent(authentication.getAuthorities(), Role.YOUR_ROLE)) {
throw new BadCredentialsException("Bad credentials");
} else {
return authentication;
}
}
}
My example uses stateless authentication mechanism, so you need to modify the filter accordingly. As I see from your initial example, you are using sessions instead, so in fact it should be much easier for you, since it is already built in to Spring Security
Hope it helps.

Spring Security HTTP Basic for RESTFul and FormLogin (Cookies) for web - Annotations

In Specific
I want to have HTTP Basic authentication ONLY for a specific URL pattern.
In Detail
I'm creating an API interface for my application and that needs to be authenticated by simple HTTP basic authentication. But other web pages should not be using HTTP basic but rather a the normal form login.
Current Configuration - NOT Working
#Override
protected void configure(HttpSecurity http) throws Exception {
http //HTTP Security
.csrf().disable() //Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.antMatchers("/connect/**").permitAll()
.antMatchers("/", "/register").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("API")
.anyRequest().authenticated()
.and() //HTTP basic Authentication only for API
.antMatcher("/api/**").httpBasic()
.and() //Login Form configuration for all others
.formLogin().loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
Waited for 2 days and didn't get any help here. But my research provided me a solution :)
Solution
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasAnyRole("ADMIN", "API")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //HTTP with Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.antMatchers("/connect/**").permitAll()
.antMatchers("/", "/register").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
}
}
I dunno if it can be helpful but I couldn't implement the above solution. I found a workaround defining a single Security
#Configuration class
extending
WebSecurityConfigurerAdapter
with both httpBasic() and formLogin() configured. Then I created a custom
CustomAuthEntryPoint implements AuthenticationEntryPoint
that has this logic in the commence method:
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
{
String urlContext = UtilityClass.extractUrlContext(request);
if (!urlContext.equals(API_URL_PREFIX))
{
String redirectUrl = "urlOfFormLogin"
response.sendRedirect(request.getContextPath() + redirectUrl);
}
else
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
Dunno which is the "best practice strategy" about this issue

Categories