How to allow public resources with MVC and Spring Security? - java

I would like to allow Javascript and CSS for my website, but I'm getting this error
The resource from “http://localhost:8080/login” was blocked due to
MIME type (“text/html”) mismatch (X-Content-Type-Options: nosniff)
This is my resource folder
I've tried to solve the problem with this config
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf().disable()
.authorizeHttpRequests(req -> req
.requestMatchers("/js/**", "/css/**", "/img/**").permitAll()
.requestMatchers("/", "/login").permitAll()
.requestMatchers("/profile").hasAnyRole("USER", "ADMIN").anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login"))
.build();
}

Related

Spring security: REST with MVC

Im begginer in spring security! I have MVC + REST application written on Spring Boot. I have a code for securing my app:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS).and()
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated())
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.anyRequest().authenticated())
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
Thats work fine, but when i trying getting data from MVC (that was authorized with formFilterChain) with ajax to /api/** (that controls by apiFilterChain) - i need use basic auth.
How i can fix that to take data from /api/** with authorized by form login method?
My method after reading documentation:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
private final Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>
.AuthorizationManagerRequestMatcherRegistry> securedRequests = authorize -> {
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated();
};
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.NEVER).and()
.authorizeHttpRequests(securedRequests)
// checking for "Authorization" header
.securityMatcher(request ->
request.getHeader("Authorization") != null)
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(securedRequests)
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
First filter will working if request has header "Authorization"
// checking for "Authorization" header
...
.securityMatcher(request ->
request.getHeader("Authorization") != null)
...
If header "Authorization" not found - then executing formFilterChain.

Query on Http Basic Authentication in Spring Security

Being new to Spring security, was trying a simple REST GET call with HTTP Basic Authentication. Scenario :
For correct usr/pwd, gives http ok. Then I change only the pwd. For incorrect pwd also it gives 200 ok. And the server log says :
o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate
Question - how do I reauth every time and send http 401 unauthorized to the client?
Code :
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("In securityFilterChain with HttpSecurity");
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()//Customizer.withDefaults())
.authenticationEntryPoint(authEntryPoint);
return http.build();
}
#Bean
public UserDetailsService initUser() {
UserDetails user = User.builder()
.username("basic")
.password(encoder.encode("basic123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(initUser())
.and()
.build();
}

Spring security 6 - Handle exceptions with #RestControllerAdvice

I have migrated to Spring Boot 3 on an application, that is used as an Oauth2ResourceServer. I am using custom AuthenticationManagerResolver for this.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers().cacheControl().disable().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.requestCache().disable()
.csrf().disable()
.authorizeHttpRequests(authorize -> {
authorize
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/endpoint1/**", "/endpoint2/**")
.authenticated();
}
)
.oauth2ResourceServer(rs -> {
rs.authenticationManagerResolver(multiTenantAuthenticationManagerResolver);
}
)
return http.build();
}
The problem is, that any error that happens in the custom authenticationManagerResolver will return status code 401 to the client, with no message whatsoever. The exceptions are not handled in my #RestControllerAdvice ResponseEntityExceptionHandler. So I tried adding authenticationEntryPoint.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.headers().cacheControl().disable().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.requestCache().disable()
.csrf().disable()
.authorizeHttpRequests(authorize -> {
authorize
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/endpoint1/**", "/endpoint2/**")
.authenticated();
}
)
.oauth2ResourceServer(rs -> {
rs.authenticationManagerResolver(multiTenantAuthenticationManagerResolver)
.authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint());
}
)
return http.build();
}
This is what I've found should work:
CustomOAuth2AuthenticationEntryPoint
public class CustomOAuth2AuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
resolver.resolveException(request, response, null, e);
}
}
However, there are 2 issues with this approach.
The exception does not contain the message that was thrown, it is always just the generic one org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
There is no default HandlerExceptionResolver, but according to examples, there should be. I receive null.
Anyone has any tips on how to propagate the original exceptions to the #RestControllerAdvice?

How to migrate deprecated WebSecurityConfigurerAdapter to SecurityFilterChain?

As they describe us here, the WebSecurityConfigurerAdapter will deprecated in a while.
I try to refactor the implementation of WebSecurityConfigurerAdapter with SecurityFilterChain due to I want to implement an JWT pattern.
The main consideration which I faced is that the configure in returns void.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean(), accessTokenExpiredInDays, refreshTokenExpiredInDays, jwtSecret);
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
http
.csrf().disable();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers("/error").permitAll();
http
.authorizeRequests()
.antMatchers("/api/login/**", "/api/token/refresh/**").permitAll();
http
.authorizeRequests()
.anyRequest().authenticated();
http
.addFilter(customAuthenticationFilter);
http
.addFilterBefore(new CustomAuthorizationFilter(jwtSecret), UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
Note that Spring Security has built-in support for JWT authentication and there is no need to create a custom filter.
You can find an example provided by the Spring Security team here.
However, if you do choose to create a custom filter, the recommended way to configure it is by creating a custom DSL.
This is the same way that Spring Security does it internally.
I've rewritten your configuration below using a custom DSL.
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers("/error").permitAll();
http
.authorizeRequests()
.antMatchers("/api/login/**", "/api/token/refresh/**").permitAll();
http
.authorizeRequests()
.anyRequest().authenticated();
// apply the custom DSL which adds the custom filter
http
.apply(customDsl());
http
.addFilterBefore(new CustomAuthorizationFilter(jwtSecret), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
#Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager =
http.getSharedObject(AuthenticationManager.class);
CustomAuthenticationFilter filter =
new CustomAuthenticationFilter(authenticationManager, accessTokenExpiredInDays, refreshTokenExpiredInDays, jwtSecret);
filter.setFilterProcessesUrl("/api/login");
http.addFilter(filter);
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
This configuration, as well as other examples, are described in the Spring blog post on migrating away from the WebSecurityConfigurerAdapter.

Spring boot throws 403 for Mobile requests even when csrf is disabled

I have a simple Spring boot application that has a POST rest api method to register users. This works perfectly when I test it through Postman. But when I test it from my Mobile application, this always throws a 403. This fails in the Options level as I don't see my backend logging an attempted request.
The usual solution given everywhere for this problem is to disable csrf in the spring security configuration. The funny thing is, I have this disabled and still getting a 403. I have searched as much as I could but cannot find a solution as to how this is still failing. Appreciate any help anyone could provide.
Here is the security configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// Some beans that are not relevant
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
I think it's also worth mentioning that I also tried adding cors disable option as advised in many other threads, to no avail.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
This is how my controller look like
#RestController
public class AuthenticationResource {
// Wiring and other methods
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> registerNewUser(#Valid #RequestBody UserRegistrationRequest request) {
if (!request.getConfirmPassword().equals(request.getPassword())) {
throw new MyException(ErrorUtil.generateErrorFieldsValue("password", "confirmPassword"),
"Passwords do not match!", HttpStatus.BAD_REQUEST);
}
UserAccountResponse savedUserAccount = userAccountService.save(request);
return ResponseEntity.ok(savedUserAccount);
}
}
Let me know if any other details are required.
From what I know this is related to CORS as you suggested, but by using the http.cors() you tell spring to look for a bean named corsFilter(). In my other projects this is how I solved it.
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource configSource = new
UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// GET , POST, HEAD
config.applyPermitDefaultValues();
config.addAllowedMethod(HttpMethod.PUT);
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedMethod(HttpMethod.OPTIONS);
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}

Categories