Spring Boot Security UsernamePasswordAuthenticationFilter does not limit the login url to only POST - spring

I looked all throughout and nobody else is having this issue that I can find. The authentication works correctly but the login url works on any HTTP method (GET, PUT, etc) vs. only working on POST. I tried manually setting filter.setPostOnly(true); on the custom JWTAuthenticationFilter I made, but it still allows on all methods.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String FILTER_PROCESS_URL = "/authentication";
private static final String HEALTH_RESOURCE_URL = "/health/**";
private CustomUserDetailService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(CowCalfUserDetailService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, FILTER_PROCESS_URL).permitAll()
.antMatchers(HttpMethod.GET, HEALTH_RESOURCE_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(getJWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilter(new JWTAuthorizationFilter(authenticationManagerBean()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
public JWTAuthenticationFilter getJWTAuthenticationFilter() throws Exception {
final JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManagerBean());
filter.setPostOnly(true);
filter.setFilterProcessesUrl(FILTER_PROCESS_URL);
return filter;
}
}

If i understand you correctly, you want to limit the /authentication only to the POST Http-Method. If yes, you could achieve it by adding the following code snippet to your method in the "RestController" for the Authentication:
#RequestMapping(value = "/authentication", method = RequestMethod.POST)
RequestMapping Docs

I found out how setPostOnly() works. UsernamePasswordAuthenticationFilter.attemptAuthentication() checks for POST before attempting the authentication and throws an exception. This method I have overridden with my custom JWTAuthenticationFilter. I just did the same and added a check in my overridden method too. Thank you for the suggestions!

Related

Spring Boot 3 security cannot access H2 console - 403

I'm in the process of re-learning Spring security in Spring Boot 3. Things changed a little and for some reason the same settings working for WebSecurityConfigurerAdapter's config method will not work in SecurityFilterChain.
HERE IS SOME CODE FROM PREVIOUS SETUPS- WORKING
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AppUserService userService;
private final PasswordEncoder bCryptPasswordEncoder;
public SecurityConfig(AppUserService userService, PasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
private static final String[] SWAGGER = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors(c -> {
CorsConfigurationSource cs = request -> {
CorsConfiguration cc = new CorsConfiguration();
cc.setAllowedOrigins(List.of("*"));
cc.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
cc.setAllowedHeaders(List.of("Origin", "Content-Type", "X-Auth-Token", "Access-Control-Expose-Header",
"Authorization"));
cc.addExposedHeader("Authorization");
cc.addExposedHeader("User-Name");
return cc;
};
c.configurationSource(cs);
});
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeRequests().antMatchers("/login/**").permitAll();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
http.authorizeRequests().antMatchers(SWAGGER).permitAll();
http.authorizeRequests().antMatchers("/users/password-reset-request").permitAll();
http.authorizeRequests().antMatchers("/users/password-change").permitAll();
http.authorizeRequests().antMatchers("/users/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers("/favorites/**").hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(GET).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(POST).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(PUT).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(DELETE).hasAnyAuthority("MODERATOR");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManager()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected UserDetailsService userDetailsService() {
return userService;
}
}
NOW SINCE
WebSecurityConfigurerAdapter
is no longer available:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationProvider authenticationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
http.authorizeHttpRequests().requestMatchers("/auth/**").permitAll();
http.authorizeHttpRequests().anyRequest().authenticated();
http.authenticationProvider(authenticationProvider);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Long story short- previously working
http.headers().frameOptions().disable();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
Will not work as
http.headers().frameOptions().disable();
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
Application.properties for H2 database are exact same, copied. I didn't changed default url for H2. It seems its the Spring Security standing in the way.
Please advice what to do.
Do you have any knowlage if anything changed for H2 setup since previous Spring Boot?
EDIT: If I simply http.authorizeHttpRequests().anyRequest().permitAll();, the console will work. It must be security related
The H2ConsoleAutoConfiguration will register a Servlet for H2's Web Console, therefore, the servletPath property is needed in order to use the MvcRequestMatcher, like so:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
MvcRequestMatcher h2RequestMatcher = new MvcRequestMatcher(introspector, "/**");
h2RequestMatcher.setServletPath("/h2-console");
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(h2RequestMatcher).permitAll()
// ...
);
}
In summary, we are permitting every (/**) request under the h2-console servlet path.
Another option is to use PathRequest.toH2Console() as shown in the Spring Boot H2 Console's documentation, which in turn will create an AntPathRequestMatcher for you.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toH2Console()).permitAll()
// ...
);
}
This problem has also been answered in the project's repository

Spring security with filters only for specific url

I want security to trigger only for specific URLs with a key in them, what I think is happening is that I bypass spring security, but the added Authorization filter that extends the BasicAuthenticationFilter is triggering with every url how can I set urls to the added BasicAuthenticationFilter also. I am implementing Jwt token.
This is my config:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserServiceImpl userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable().authorizeRequests()
.antMatchers("**/auth/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilter(new AuthorizationFilter(authenticationManager()))
.addFilter(authenticationFilter())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.headers().cacheControl();
}
public AuthenticationFilter authenticationFilter() throws Exception{
final AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager());
authenticationFilter.setFilterProcessesUrl("/login");
return authenticationFilter ;
}
}
Options
Extend BasicAuthenticationFilter and as a consequence of inheritance you can write your own authentication approach, during run time methods from sub-class will be taken into account from Spring security side.
Extend AbstractAuthenticationFilter and do required filtering there.

Wrong redirection in Spring MVC app

Im going to be quick. I have a Spring MVC project, and Im using Spring Security, so after I successfully log in the server redirects me to the application context instead of the index page. Any idea why this is happening, I suspect it may be a security problem, but so far I havenĀ“t figure it out, so please I need help on this one.
My login form action is this: ${loginUrl}
And the redirection problem only happens the first time i try to log in, if I log out and log in again the server redirects me correctly.
Here is my code:
Web Security Config class:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
ServicioInicioSesion inicioSesion;
#Autowired
MessageSource messageSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/addUsuarios").permitAll()
.antMatchers("/roles/**", "/usuarios/**").hasAuthority("Administrador")
.antMatchers("/editarPerfil").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/index")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/login")
.and()
.exceptionHandling().accessDeniedPage("/403");
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
#Bean(name = "authenticationManager")
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(inicioSesion);
auth.setMessageSource(messageSource);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Bean
public PasswordEncoder p`enter code here`asswordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
}
Index Controller class
#Controller
public class IndexController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String showIndex() {
return "index";
}
}
Alberto. Try this one:
1 - replace value = "/index" by value = {"/","/index"}
2 - remove method parameter
#RequestMapping(value = {"/","/index"})
When you submit authentication form in the request you have POST data, but in your case you have RequestMethod.GET

Spring - Add a check for 3rd parameter during authentication

At a glance, I have API Back-end App written in Spring Boot which uses JWT for secured data transmission. I want to add 3rd parameter for authorization, so I should have login, password and storeID parameters. I am inspired by this answer How implement Spring security when login page having more field apart from user name and password? but when I followed proposed solution my 3rd parameter in not used. My impression is that I am missing something important in Security Config. Could you please point to my mistake?
SecurityConfig
#SuppressWarnings("SpringJavaAutowiringInspection")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationDetailsSource<HttpServletRequest, ?> webAuthenticationDetailsSourceImpl;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(myAuthProvider());
}
#Bean
public CustomUserDetailsAuthenticationProvider myAuthProvider() throws Exception {
CustomUserDetailsAuthenticationProvider provider = new CustomUserDetailsAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return usernamePasswordAuthenticationFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
I was under impression I can check against storeID field in WebAuthenticationDetailsSourceImpl, but looks like it has never been executed because I don't see anything related in log.
WebAuthenticationDetailsSourceImpl:
#Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationRequest> {
#Override
public JwtAuthenticationRequest buildDetails(HttpServletRequest context) {
System.out.println("___#####_____");
System.out.println(context);
System.out.println("___#####_____");
return new JwtAuthenticationRequest();
}
}
cuz you don't insert "your" usernamePasswordAuthenticationFilter that set webAuthenticationDetailsSourceImpl to Spring Security's authentication filter chain.
perhaps current your authentication filter chain is
~
JwtAuthenticationTokenFilter
(Spring Security's original)UsernamePasswordAuthenticationFilter
~
hence,if you want to retrieve your additional parameter in "your" usernamePasswordAuthenticationFilter add this filter too like a JwtAuthenticationTokenFilter
but , if you want to simply retrieve parameter at JwtAuthenticationTokenFilter
use setAuthenticationDetailsSource at there
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationTokenFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return authenticationTokenFilter;
}

Spring boot OAuth successful login listener not triggering

Using Spring boot - After successfully authenticating with GitHub OAuth, the Audit listener is not being triggered.
public class AuthenticationListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(final InteractiveAuthenticationSuccessEvent event) {
System.out.println("+++++++ ================ ------------");
}
}
Do I need to register it anywhere else? I have tried as suggested else where on Stackoverflow to create a #Bean, but this made no difference.
Full code https://github.com/DashboardHub/PipelineDashboard/tree/feature/178-login-github
Update
SecurityConfig class
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//.failureUrl("/login?error")
.permitAll()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
;
}
#Bean
#ConfigurationProperties("security.oauth2")
ClientResourcesConfig github() {
return new ClientResourcesConfig();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(this.github(), "/login/github"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResourcesConfig client, String path) {
OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(client.getClient(),
oauth2ClientContext);
githubFilter.setRestTemplate(githubTemplate);
githubFilter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
return githubFilter;
}
}
I got this working by injecting the default ApplicationEventPublisher into your security config bean. Then setting this as the application event publisher on the processingfilter:
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
...
githubFilter.setApplicationEventPublisher(applicationEventPublisher);
For some reason, the application event publisher on the filter is a NullEventPublisher by default.
Use #Configuration annotation on AuthenticationListener class to register it in your application context.
EDIT
As I could not figure out why event wasn't fired, I present alternative solution for this problem. You have to create class that implements AuthenticationSuccessHanddler and implement its onAuthenticationSuccess method:
#Component(value="customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//implementation
}
}
Then add it to your configuration like:
#Resource
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
...
.loginPage("/login").and()
.successHandler(getCustomAuthenticationSuccessHandler())
.permitAll()
...
}
public CustomAuthenticationSuccessHandler getCustomAuthenticationSuccessHandler() {
return customAuthenticationSuccessHandler;
}
It's not exactly what you wanted, but should solve your problem.
Instead of an InteractiveAuthenticationSuccessEvent, listening for an AuthenticationSuccessEvent did the trick for me.
However, the listener is called twice: first one's event's Authentication is an UsernamePasswordAuthenticationToken, while the second one is an OAuth2Authentication

Resources