405 Method Not Allowed, spring boot, but i used csrf token header - java

I am making a Web Application Server with Spring boot.
In the early days of development, I used the settings http.csrf().disable() and the web page worked well.
But errors have been occurring ONLY at host address page ever since the disable setting was erased. When I requested GET, it works totally fine, however, it doesn't work with POST, PUT, DELETE... When I test at my localhost, all requests works including those methods.
Already, I used csrf token header, hidden csrf inputs of forms, and beforeSend that set csrf header in ajax. I even changed the nginx setting (error_page 405 =200 $uri;), but it remains the same.
This is my environment.
jdk 11
spring 2.3.5
AWS EC2 - Amazon Linux 2 AMI
AWS RDS - PostgreSQL 13
AWS S3
Nginx 1.18.0
gradle 5.6.4
thymeleaf
Is there anyone who can help me, please?
My Github Repository Link
# aws server- nohup.out
[ec2-user#teamcoder-webservice ~]$ vim ~/app/step3/nohup.out
2020-12-01 23:24:18.953 INFO 3697 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#3ad85136, org.springframework.security.web.context.SecurityContextPersistenceFilter#2bc426f0, org.springframework.security.web.header.HeaderWriterFilter#176f7f3b, org.springframework.security.web.csrf.CsrfFilter#7d3c09ec, org.springframework.security.web.authentication.logout.LogoutFilter#1f53481b, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter#7f93dd4e, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter#5ad5be4a, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#33425811, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#4cb2918c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#737d100a, org.springframework.security.web.session.SessionManagementFilter#58740366, org.springframework.security.web.access.ExceptionTranslationFilter#4af45442, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#671c4166]
2020-12-01 23:24:20.068 INFO 3697 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2020-12-01 23:24:20.550 INFO 3697 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2020-12-01 23:24:20.552 INFO 3697 --- [ main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories…
2020-12-01 23:24:21.474 INFO 3697 --- [ main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-12-01 23:24:21.499 INFO 3697 --- [ main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2020-12-01 23:24:21.527 INFO 3697 --- [ main] c.j.team.teamcoder.TeamCoderApplication : Started TeamCoderApplication in 17.806 seconds (JVM running for 19.458)
2020-12-01 23:24:23.438 INFO 3697 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-12-01 23:24:23.443 INFO 3697 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-12-01 23:24:23.460 INFO 3697 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 17 ms
2020-12-02 03:05:22.223 WARN 3697 --- [nio-8081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
# /etc/nginx/nginx.conf
http {
...
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
include /etc/nginx/conf.d/service-url.inc;
location / {
limit_except GET POST PUT DELETE {
deny all;
}
proxy_pass $service_url;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
error_page 405 =200 $uri;
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
#after change upper configuration
[ec2-user#teamcoder-webservice nginx]$ sudo /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Plus, the following code is java spring boot code.
<!--ajax non-used request in "user_info.html"-->
<form th:action="#{'/api/v1/user/pic/' + ${user.id}}" method="post" enctype="multipart/form-data" name="pictureForm">
<table>
<tr><td><label th:for="picture">Profile Image</label></td>
<td><input type="file" id="picture" name="picture" accept="image/png, image/jpeg" /></td></tr>
<tr><td><img th:src="#{${user.picture}}" id="picture_img" alt="put your image"></td>
<td><input type="button" name="Upload" value="Upload" onclick="submit_file_form(document.forms['pictureForm'].picture, 'pictureForm');" class="btn btn-info"/></td></tr>
</table>
<input type="hidden" name="pastPath" id="pastPath" th:value="${user.picture}"/>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
//ajax used request in "user_info.html" -> "userInfo.js"
...
const csrfToken = $('#_csrf').attr('content');
const csrfHeader = $('#_csrf_header').attr('content');
var user_info = {
init : function () {
var _this = this;
$('#btn-update').on('click', function () {
_this.update();
});
$('#btn-delete').on('click', function () {
_this.delete();
});
},
update : function () {
var data = {
name: $('#name').val(),
tags: commaToArray(convertTags($('#tags').val())),
email: $('#email').val(),
birth: $('#birth').val(),
education: $('#education').val().toUpperCase(),
location: $('#location').val()
};
var id = $('#id').val();
$.ajax({
type: 'PUT',
url: '/api/v1/user/'+id,
dataType: 'json',
contentType:'application/json; charset=utf-8',
data: JSON.stringify(data),
beforeSend: function (xhr){
xhr.setRequestHeader(csrfHeader, csrfToken);
}
}).done(function() {
alert('your information is modified');
window.location.href = '/';
}).fail(function (error) {
alert(JSON.stringify(error));
});
},
...
<!-- in html header -->
<meta id="_csrf" th:name="${_csrf.parameterName}" th:content="${_csrf.token}"/>
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
#RequiredArgsConstructor
#RestController
public class UserApiController {
private final UserService userService;
private final RoleService roleService;
#PutMapping("/api/v1/user/{id}")
public Long update(#PathVariable String id,
#RequestBody UserUpdateRequestDto requestDto){
roleService.reloadRolesForAuthenticatedUser(Role.USER.getKey());
return userService.update(Long.valueOf(id), requestDto);
}
}
#RequiredArgsConstructor
#RestController
public class FileController {
private final UserService userService;]
private final S3Service s3Service;
#PostMapping("/api/v1/user/pic/{id}")
public String updateUserPic(#PathVariable String id,
#RequestParam("pastPath") String pastPath,
#RequestPart("picture") MultipartFile multipartFile) throws IOException {
String uploadDir = s3Service.upload(pastPath, multipartFile,"users/");
userService.updatePic(Long.valueOf(id), uploadDir);
return "You successfully uploaded " + uploadDir + "!";
}
}
//SecurityConfig.java
#Configuration
#RequiredArgsConstructor
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/assets/img/**", "/lib/**", "/user-photos/**", "/group-files/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.antMatchers("/", "/logoption","/user/denied", "/search/**", "/privacy/rule","/profile").permitAll()
.antMatchers("/api/v1/user/**", "/api/v1/**").hasRole("GUEST")
.antMatchers("/api/v1/group/**", "/group/**", "/api/v1/participate/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.logout().logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.accessDeniedPage("/user/denied")
.and()
.oauth2Login()
.loginPage("/logoption")
.defaultSuccessUrl("/")
.userInfoEndpoint()// after oauth2 login
.userService(customOAuth2UserService);
}
//for Spring Security
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
}

Try adding these to your SecurityConfig :
http.cors().and()....
And CORS config:
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*"); // TODO: lock down before deploying
config.addAllowedHeader("*");
config.addExposedHeader(HttpHeaders.AUTHORIZATION);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}

Related

Why do I get Request method 'POST' not supported] with Method Not Allowed, status=405 when entering users/update/{id}

When I'm entering users/update/{id} with some id, for example id = 6, I'm getting "Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]".
Basically, what I aimed at was to when I'm going on that URL (users/update/{id}) is:
method updateUser() triggered by going on mentioned URL retrieves user with id = 6 from repository
build a viewModel from it user with id = 6
pass this viewModel to HTML template "registration-formUpdate" as a "user" object in <form class="form-horizontal" method="post" th:href="#{~/registration}" th:object="${user}">
which then passes this object to endpoint "/registration" and to "registrationUsersAdding()" method in RegistrationController class to automatically fill the form with values from the user retrieved from repository
by clicking "add" button on the "registration-formUpdate" template save the updated user to database
However, when I click the "add" buttton, I get the 405 status and "Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]"
Sorry if my explanation isn't all that good, I'm learning Spring and English isn't my first language. I've done this this way 3-4 times on different apps before and it always worked, in this same project I have Events class with different entities, but same logic and it works. I don't know why here it doesn't.
Controller class
#RequiredArgsConstructor
#Controller
public class RegistrationController {
private final UserService userService;
private final UserRepository userRepository;
#PostMapping("/registration")
public String registrationUsersAdding(#ModelAttribute("user") #Valid UserViewModel user2,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
if (user2.getId() == null) {
return "registration-form";
} else {
return "registration-formUpdate";
}
} else {
if (user2.getId() == null) {
userService.add(user2);
} else {
userService.update(user2);
}
}
return "redirect:/login";
}
#Secured("ROLE_ADMIN")
#GetMapping("users/update/{id}")//toooooooo
public String updateUser(#PathVariable(value = "id") Long id, Model model) {
var user = userService.userToViewModel(userRepository.getOne(id));
model.addAttribute("user", user);
return "registration-formUpdate";
}
}
Service class
#Service
#RequiredArgsConstructor
public class UserService implements AddUpdateGetDeleteUser {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
public void add(UserViewModel userViewModel) {
List<Role> roles;
Role role = new Role();
role.setType(RoleType.valueOf(userViewModel.getRoles()));// pomyslec nad tym
roleRepository.save(role);
roles = Collections.singletonList(role);
User user1 = User.builder()
.firstName(userViewModel.getFirstName())
.lastName(userViewModel.getLastName())
.login(userViewModel.getLogin())
.password(passwordEncoder.encode(userViewModel.getPassword()))
.email(userViewModel.getEmail())
.roles(roles)
.build();
userRepository.save(user1);
}
public void update(UserViewModel userDto) {
Optional<User> optionalUser = userRepository.findById(userDto.getId());
if (optionalUser.isPresent()) {
User user = optionalUser.get();
user.setId(userDto.getId());
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setRoles(Collections.singletonList(GetRole(userDto.getRoles())));
user.setLogin(userDto.getLogin());
user.setEmail(userDto.getEmail());
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
userRepository.save(user);
}
}
public UserViewModel userToViewModel(User user) {
return UserViewModel.builder()
.id(user.getId())
.firstName(user.getFirstName())
.lastName(user.getLastName())
.roles(user.getRoles().get(0).toString())
.login(user.getLogin())
.email(user.getEmail())
.password(passwordEncoder.encode(user.getPassword()))
.build();
}
private Role GetRole(String roleType) {
var role = new Role();
role.setType(RoleType.valueOf(roleType));
return role;
}
}
Log
2022-12-30 00:35:43.807 INFO 5804 --- [ main] Z.Z.ZadanieRekrutacyjneApplication : Started ZadanieRekrutacyjneApplication in 10.364 seconds (JVM running for 11.253)
2022-12-30 00:36:28.052 INFO 5804 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-12-30 00:36:28.052 INFO 5804 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-12-30 00:36:28.055 INFO 5804 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2022-12-30 00:36:28.527 WARN 5804 --- [nio-8080-exec-1] o.a.c.util.SessionIdGeneratorBase : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [387] milliseconds.
2022-12-30 00:37:13.180 WARN 5804 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
2022-12-30 00:42:59.615 INFO 5804 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2022-12-30 00:42:59.621 INFO 5804 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-30 00:42:59.624 INFO 5804 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-12-30 00:42:59.646 INFO 5804 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
It's ok now, changed in update-form html template from class="form-horizontal" method="post" th:href="#{~/registration}" th:object="${user}"> to <form action="#" method="POST" th:action="#{~/registration}" th:object="${user}"> and it works

Forbidden, Http.status=403 with jdbcAuthentication()

May be i am doing any silly mistake.
After i configure authentication using jdbcAuthentication() getting the following error (in the bottom of this )after i log in through chrome.
The same works for inMemoryAuthentication()
Works when i have this -
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("pass").roles("REGISTERED_USER").and().withUser("admin")
.password("pass").roles("ADMIN");
}
Here is my code -
#EnableWebSecurity
public class FplUserOperationAuthentication extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select m.username as username, m.password as password, "
+ "m.enabled as enabled from stackoverflow.users_main m where m.username = ?")
.authoritiesByUsernameQuery("select a.username as username, a.role as authority "
+ "from stackoverflow.authorities a where a.username = ?");
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user-profile/admin/**").hasAnyRole("ADMIN")
.antMatchers("/user-profile/users/**").hasAnyRole("REGISTERED_USER","ADMIN")
.antMatchers("/welcome").permitAll().and().formLogin()
.and().csrf().disable();
}
Application.properties -
endpoints.shutdown.enabled=true
spring.application.name=fpl-user-operations
management.endpoints.web.exposure.include = *
spring.datasource.url = jdbc:oracle:thin:#localhost:1521/ORCLPDB
spring.datasource.username = FPLADMIN
spring.datasource.password = oracle1
spring.devtools.add-properties = false
spring.config.import=optional:configserver:http://localhost:8888
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=none
SQL -
alter session set current_schema = stackoverflow;
create table users_main
(
username varchar2(20) primary key,
password varchar2(20),
enabled char(1)
);
create table authorities
(
username varchar2(20),
role varchar2(15),
constraint authorities_c1 foreign key (username) references users_main (username)
);
SQL DATA
TABLE users_main
TABLE Authorities
ERROR WHEN I ACCESS A API, after i try to login with username as "user" and password as "pass". -
ERROR -
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jun 21 12:18:32 IST 2021
There was an unexpected error (type=Forbidden, status=403).
ERROR IN CONSOLE -
2021-06-21 14:49:11.520 DEBUG 11856 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Secured GET /error
2021-06-21 14:49:11.520 DEBUG 11856 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
2021-06-21 14:49:11.521 DEBUG 11856 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
2021-06-21 14:49:11.521 DEBUG 11856 --- [nio-8080-exec-5] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-06-21 14:49:11.537 DEBUG 11856 --- [nio-8080-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
2021-06-21 14:49:11.538 DEBUG 11856 --- [nio-8080-exec-5] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-06-21 14:49:11.538 DEBUG 11856 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 403
2021-06-21 14:49:11.538 DEBUG 11856 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
NOTE - However if i give wrong credentials intentionally, it does say "Bad Credentials"
Don't know the exact reason, if anyone has any idea. Please let me know.
Changes - In DB i appended ROLE in front of all type of authorities/roles
So instead of "REGISTERED_USER", i now have "ROLE_REGISTERED_USER".
Found it here
I did not find appending ROLE explicitly mentioned as a protocol. So i missed that.
My Authorization method -
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user-profile/admin/**").hasAnyRole("ADMIN")
.antMatchers("/user-profile/users/**").hasAnyRole("REGISTERED_USER","ADMIN")
.antMatchers("/welcome").permitAll().and().formLogin()
.and().csrf().disable();
}
You have not included password encoder in the configure method
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
**.passwordEncoder(getPasswordEncoder())**
.usersByUsernameQuery("select m.username as username, m.password as password, "
+ "m.enabled as enabled from stackoverflow.users_main m where m.username = ?")
.authoritiesByUsernameQuery("select a.username as username, a.role as authority "
+ "from stackoverflow.authorities a where a.username = ?");
}

Why does MockMvc in return 404 error on valid path with Spring Security enabled?

I'm trying to test my Spring Boot REST Controller with MockMvc. Following is my test class:
UserControllerImplTest (version 1)
#SpringBootTest
#AutoConfigureMockMvc
class UserControllerImplTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserDTOService userDTOService;
#MockBean
private ImageDTOService imageDTOService;
#Test
public void whenUsers_shouldReturnUsers() throws Exception {
UserDTO user1 = getSampleUser("user1");
UserDTO user2 = getSampleUser("user2");
UserDTO user3 = getSampleUser("user3");
List<UserDTO> users = List.of(user1, user2, user3);
Mockito.when(userDTOService.getAll()).thenReturn(users);
mockMvc.perform(get("/user"))
.andExpect(status().isOk())
.andExpect(content().string(users.toString()));
}
private UserDTO getSampleUser(String username) {
return UserDTO.builder()
.username(username)
.email(username + "#example.com")
.password("password")
.registerTime(LocalDateTime.now())
.isActive(true)
.build();
}
}
Controller to be tested:
UserController:
#Api(tags={"User info"})
#RequestMapping("/user")
public interface UserController {
#ApiOperation(value = "Returns list of users")
#GetMapping("/")
#PreAuthorize("hasRole('ROLE_ADMIN')")
ResponseEntity<List<UserDTO>> users();
#ApiOperation(value = "Returns single user")
#GetMapping("/{username}")
ResponseEntity<UserDTO> userInfo(#PathVariable String username);
#ApiOperation(value = "Returns list of images uploaded by user authenticated with session cookie")
#GetMapping("/images")
#PreAuthorize("isFullyAuthenticated()")
ResponseEntity<List<ImageDTO>> userImages(#AuthenticationPrincipal CustomUserDetails userDetails
);
}
its implementation:
UserControllerImpl
#Service
#RequiredArgsConstructor
public class UserControllerImpl implements UserController {
private final UserDTOService userDTOService;
private final ImageDTOService imageDTOService;
#Override
public ResponseEntity<List<UserDTO>> users() {
return ResponseEntity.status(HttpStatus.OK).body(List.of(UserDTO.builder().username("aaa").build()));
// Optional<List<UserDTO>> users = Optional.ofNullable(userDTOService.getAll());
// if (users.isEmpty()) {
// return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
// }
//
// return ResponseEntity.status(HttpStatus.OK).body(users.get());
}
#Override
public ResponseEntity<UserDTO> userInfo(String username) {
Optional<UserDTO> requestedUser = Optional.ofNullable(userDTOService.findByUsername(username));
if (requestedUser.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.status(HttpStatus.OK).body(requestedUser.get());
}
#Override
public ResponseEntity<List<ImageDTO>> userImages(#AuthenticationPrincipal CustomUserDetails userDetails) {
UserDTO user = userDetails.getUser();
Optional<List<ImageDTO>> images = Optional.ofNullable(imageDTOService.findAllUploadedBy(user));
if (images.isEmpty()) {
return ResponseEntity.status(HttpStatus.OK).body(Collections.emptyList());
}
return ResponseEntity.status(HttpStatus.OK).body(images.get());
}
}
The method I'm invoked in the controller (users()) was modified, just to be sure it'll always return 200.
The test fails because of:
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
I had a strong suspicion that the Filter Bean I defined might be a culprit here.
AuthCookieFilter
#RequiredArgsConstructor
#Component
public class AuthCookieFilter extends GenericFilterBean {
private final SessionDTOService sessionDTOService;
private final AppConfig appConfig;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Optional<String> sessionId = Optional.ofNullable(extractAuthCookie((HttpServletRequest) servletRequest));
if (sessionId.isPresent()) {
Optional<SessionDTO> sessionDTO = Optional.ofNullable(sessionDTOService.findByIdentifier(sessionId.get()));
if (sessionDTO.isPresent()) {
UserDTO userDTO = sessionDTO.get().getUser();
CustomUserDetails customUserDetails = new CustomUserDetails(userDTO);
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(customUserDetails));
}
}
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", appConfig.getCorsHosts());
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Origin, Authorization, Content-Type, Cache-Control");
response.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(servletRequest, response);
}
public static String extractAuthCookie(HttpServletRequest request) {
List<Cookie> cookies = Arrays.asList(Optional.ofNullable(request.getCookies()).orElse(new Cookie[0]));
if (!cookies.isEmpty()) {
Optional<Cookie> authCookie = cookies.stream()
.filter(cookie -> cookie.getName().equals("authentication"))
.findFirst();
if (authCookie.isPresent()) {
return authCookie.get().getValue();
}
}
return null;
}
}
So I decided to configure MockMvc manually, explicitly adding it to the context:
UserControllerImplTest (version 2)
#SpringBootTest
#WebAppConfiguration
class UserControllerImplTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Autowired
private AuthCookieFilter authCookieFilter;
#MockBean
private UserDTOService userDTOService;
#MockBean
private ImageDTOService imageDTOService;
#BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.addFilter(authCookieFilter).build();
}
#Test
public void whenUsers_shouldReturnUsers() throws Exception {
UserDTO user1 = getSampleUser("user1");
UserDTO user2 = getSampleUser("user2");
UserDTO user3 = getSampleUser("user3");
List<UserDTO> users = List.of(user1, user2, user3);
Mockito.when(userDTOService.getAll()).thenReturn(users);
mockMvc.perform(get("/user"))
.andExpect(status().isOk())
.andExpect(content().string(users.toString()));
}
private UserDTO getSampleUser(String username) {
return UserDTO.builder()
.username(username)
.email(username + "#example.com")
.password("password")
.registerTime(LocalDateTime.now())
.isActive(true)
.build();
}
}
But had no success, still getting 404s.
I also considered limiting the test scope to bringing up only the Web layer, by using #WebMvcTest, but my AuthCookieFilter, as you can see above, requires full Spring context to be up.
I noticed, that debugger stops inside doFilter of AuthCookieFilter when I put a breakpoint there, disregarding whether MockMvc is configured automatically or manually. However I cannot reach a breakpoint set inside UserControllerImpl.users().
Web security is enabled in this app and that's were additional filter is being added:
SecurityConfiguration.class
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService userDetailsService;
private final CustomLogoutSuccessHandler customLogoutSuccessHandler;
private final AuthCookieFilter authCookieFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Bean
#Override
protected AuthenticationManager authenticationManager() {
return authentication -> {
throw new AuthenticationServiceException("Cannot authenticate " + authentication);
};
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf().disable()
.logout(configurer -> {
configurer.addLogoutHandler(new HeaderWriterLogoutHandler(
new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL)
));
configurer.logoutSuccessHandler(customLogoutSuccessHandler);
configurer.logoutUrl("/auth/logout");
configurer.deleteCookies("authentication");
})
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.addFilterAfter(authCookieFilter, SecurityContextPersistenceFilter.class)
.authorizeRequests()
.antMatchers("/auth/*").permitAll()
.and()
.formLogin().permitAll()
;
}
#Bean
public PasswordEncoder getPasswordEncoder() {
String defaultEncodingId = "argon2";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(defaultEncodingId, new Argon2PasswordEncoder(16, 32, 8, 1 << 16, 4));
return new DelegatingPasswordEncoder(defaultEncodingId, encoders);
}
}
Here are logs of the application start-up and test execution:
21:40:56.203 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
21:40:56.210 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
21:40:56.228 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
21:40:56.237 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither #ContextConfiguration nor #ContextHierarchy found for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest], using SpringBootContextLoader
21:40:56.240 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: class path resource [com/ewsie/allpic/user/controller/impl/UserControllerImplTest-context.xml] does not exist
21:40:56.240 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: class path resource [com/ewsie/allpic/user/controller/impl/UserControllerImplTestContext.groovy] does not exist
21:40:56.240 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: no resource found for suffixes {-context.xml, Context.groovy}.
21:40:56.241 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: UserControllerImplTest does not declare any static, non-private, non-final, nested classes annotated with #Configuration.
21:40:56.276 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]
21:40:56.320 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/home/max/Projects/allpic/allpic-backend/target/classes/com/ewsie/allpic/AllpicApplication.class]
21:40:56.321 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found #SpringBootConfiguration com.ewsie.allpic.AllpicApplication for test class com.ewsie.allpic.user.controller.impl.UserControllerImplTest
21:40:56.382 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - #TestExecutionListeners is not present for class [com.ewsie.allpic.user.controller.impl.UserControllerImplTest]: using defaults.
21:40:56.382 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener, org.springframework.security.test.context.support.ReactorContextTestExecutionListener]
21:40:56.391 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener#7e22550a, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener#45e37a7e, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener#62452cc9, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener#6941827a, org.springframework.test.context.support.DirtiesContextTestExecutionListener#5a7005d, org.springframework.test.context.transaction.TransactionalTestExecutionListener#5bc9ba1d, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener#1021f6c9, org.springframework.test.context.event.EventPublishingTestExecutionListener#7516e4e5, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener#488eb7f2, org.springframework.security.test.context.support.ReactorContextTestExecutionListener#5e81e5ac, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener#4189d70b, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener#3fa2213, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener#3e7634b9, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener#6f0b0a5e, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener#6035b93b]
21:40:56.393 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext#2a8d39c4 testClass = UserControllerImplTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration#25b2cfcb testClass = UserControllerImplTest, locations = '{}', classes = '{class com.ewsie.allpic.AllpicApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer#72758afa key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#30b6ffe0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#14f232c4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#d324b662, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#f627d13, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#479cbee5], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with #DirtiesContext [false] with mode [null].
21:40:56.409 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=-1}
2020-09-13 21:40:56.599 INFO 52850 --- [ main] c.e.a.u.c.impl.UserControllerImplTest : Starting UserControllerImplTest on Oyashiro-sama with PID 52850 (started by max in /home/max/Projects/allpic/allpic-backend)
2020-09-13 21:40:56.599 INFO 52850 --- [ main] c.e.a.u.c.impl.UserControllerImplTest : No active profile set, falling back to default profiles: default
2020-09-13 21:40:57.143 INFO 52850 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-09-13 21:40:57.197 INFO 52850 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 49ms. Found 5 JPA repository interfaces.
2020-09-13 21:40:57.716 INFO 52850 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration' of type [org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.722 INFO 52850 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'objectPostProcessor' of type [org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.723 INFO 52850 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler#1de08775' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.727 INFO 52850 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration' of type [org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.731 INFO 52850 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-13 21:40:57.872 INFO 52850 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-09-13 21:40:57.909 INFO 52850 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.12.Final
2020-09-13 21:40:57.931 INFO 52850 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-09-13 21:40:57.993 INFO 52850 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-09-13 21:40:58.098 INFO 52850 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-09-13 21:40:58.108 INFO 52850 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
(sql queries here)
2020-09-13 21:40:58.523 INFO 52850 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-09-13 21:40:58.527 INFO 52850 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-09-13 21:40:59.025 WARN 52850 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-09-13 21:40:59.661 INFO 52850 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#647bd553, org.springframework.security.web.context.SecurityContextPersistenceFilter#16d7f503, com.ewsie.allpic.user.security.AuthCookieFilter#a47a011, org.springframework.security.web.header.HeaderWriterFilter#1b9785aa, org.springframework.security.web.authentication.logout.LogoutFilter#2ddb260e, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#4ceac22d, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#1203c259, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#15311af2, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#54602d5a, org.springframework.security.web.session.SessionManagementFilter#347074a9, org.springframework.security.web.access.ExceptionTranslationFilter#2eebb07, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#441a2d52]
2020-09-13 21:40:59.901 INFO 52850 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-09-13 21:41:00.036 INFO 52850 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:default'
2020-09-13 21:41:00.070 INFO 52850 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2020-09-13 21:41:00.111 INFO 52850 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2020-09-13 21:41:00.111 INFO 52850 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2020-09-13 21:41:00.118 INFO 52850 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 7 ms
2020-09-13 21:41:00.428 INFO 52850 --- [ main] c.e.a.u.c.impl.UserControllerImplTest : Started UserControllerImplTest in 4.013 seconds (JVM running for 4.7)
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils (file:/home/max/.m2/repository/org/springframework/spring-core/5.2.5.RELEASE/spring-core-5.2.5.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = [Access-Control-Allow-Origin:"http://localhost:4200", Access-Control-Allow-Methods:"POST, PUT, GET, OPTIONS, DELETE", Access-Control-Max-Age:"3600", Access-Control-Allow-Headers:"Access-Control-Allow-Origin, Authorization, Content-Type, Cache-Control", Access-Control-Allow-Credentials:"true", Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
How can I make the MockMvc work and mock my controller?
The problem was that /user was indeed nowhere to be found, so 404 response was completely justified.
After chaniging:
mockMvc.perform(get("/user"))
to:
mockMvc.perform(get("/user/"))
(note the trailing /)
I was able to recieve the actual response and carry on with the test.
In my case, I imported the controller class manually using #Import and it worked. It seems the controller bean had not been imported by #WebMvcTest. See below code :
#ContextConfiguration(
classes = [TestConfigs::class]
)
#WebMvcTest
#Import(MyController::class)
class MyControllerTests {
#Test
fun testPost() {
//test logic goes here
}
}
Note that how I used #Import : #Import(MyController::class).
If I remove #Import, I would get 404.
Hope it helps

Spring security 403 response on POST\PUT\DELETE

I'm trying to get some Role-based authorization working on spring, but I have trouble with 403 responses on POST\PUT\DELETE requests. I've been looking for solutions elsewhere but provided solutions with disabling csrf do not solve the issue.
This is my HTTP config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").hasAnyRole("ROLE_TRAINER", "ROLE_ADMIN")
.antMatchers(HttpMethod.PATCH, "/user/**").hasAnyRole("ROLE_TRAINER", "ROLE_ADMIN")
.antMatchers(HttpMethod.PUT, "/**").hasAnyRole("ROLE_TRAINER", "ROLE_ADMIN")
.antMatchers(HttpMethod.POST, "/**").hasAnyRole("ROLE_TRAINER", "ROLE_ADMIN")
.antMatchers(HttpMethod.DELETE,"/**").hasAnyRole("ROLE_TRAINER", "ROLE_ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin().permitAll()
.and()
.logout().permitAll();
}
When I debug my code, only GET requests make my program go through the UserDetails objects to provide the Roles collection (as defined below)
public class BrevisFitUser extends User implements UserDetails {
public BrevisFitUser(final User user) {
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName().toUpperCase()))
.collect(Collectors.toList());
}
On a PUT request for instance, this is the log I get:
2019-11-20 13:28:47.560 DEBUG 15456 --- [nio-8080-exec-5] o.a.coyote.http11.Http11InputBuffer : Received [PUT /exercise/strap/3 HTTP/1.1
Content-Type: application/json
Authorization: Basic c2ltb24ua25lejpzaW1vbi5rbmV6
User-Agent: PostmanRuntime/7.19.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 7a829877-6de4-4746-8e27-63677f0160d2
Host: localhost:8080
Accept-Encoding: gzip, deflate
Content-Length: 75
Cookie: JSESSIONID=45F53EE556C13E672E6ECFD5865B5FD6
Connection: keep-alive
{
"unitLength": 29,
"name": "Yellow strap home marked smaller"
}]
2019-11-20 13:28:47.560 DEBUG 15456 --- [nio-8080-exec-5] o.a.t.util.http.Rfc6265CookieProcessor : Cookies: Parsing b[]: JSESSIONID=45F53EE556C13E672E6ECFD5865B5FD6
2019-11-20 13:28:47.561 DEBUG 15456 --- [nio-8080-exec-5] o.a.catalina.connector.CoyoteAdapter : Requested cookie session id is 45F53EE556C13E672E6ECFD5865B5FD6
2019-11-20 13:28:47.561 DEBUG 15456 --- [nio-8080-exec-5] o.a.c.authenticator.AuthenticatorBase : Security checking request PUT /exercise/strap/3
2019-11-20 13:28:47.561 DEBUG 15456 --- [nio-8080-exec-5] org.apache.catalina.realm.RealmBase : No applicable constraints defined
2019-11-20 13:28:47.562 DEBUG 15456 --- [nio-8080-exec-5] o.a.c.authenticator.AuthenticatorBase : Not subject to any constraint
2019-11-20 13:28:47.563 DEBUG 15456 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /exercise/strap/3 at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2019-11-20 13:28:47.563 DEBUG 15456 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /exercise/strap/3 at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2019-11-20 13:28:47.563 DEBUG 15456 --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl#e2265dfb: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#e2265dfb: Principal: com.brevisfit.api.model.user.User[ iduser=1 ]; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 45F53EE556C13E672E6ECFD5865B5FD6; Granted Authorities: ROLE_ADMIN, ROLE_TRAINER'
2019-11-20 13:28:47.563 DEBUG 15456 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /exercise/strap/3 at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2019-11-20 13:28:47.563 DEBUG 15456 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /exercise/strap/3 at position 4 of 15 in additional filter chain; firing Filter: 'CsrfFilter'
2019-11-20 13:28:47.564 DEBUG 15456 --- [nio-8080-exec-5] org.apache.tomcat.util.http.Parameters : Set encoding to UTF-8
2019-11-20 13:28:47.564 DEBUG 15456 --- [nio-8080-exec-5] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/exercise/strap/3
2019-11-20 13:28:47.564 DEBUG 15456 --- [nio-8080-exec-5] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#25ff6f87
2019-11-20 13:28:47.565 DEBUG 15456 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
Based on the issue with the csrf, I have added the following code to the configuration:
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
// Token is being added to the XSRF-TOKEN cookie.
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
And have tried with removal of antMatchers and just require authenticationi:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class); // Register csrf filter.
}
Any idea, why such behaviour?

spring security rejects login csrf

I am using spring-boot with spring-security and am trying to get my login to work with javascript. Everything works fine if I use login.html and a form. But once I use javascript I get an error about a null csrf. I am using all json for my request and response. I believe this means I need the _csrf token for all request POST/PUT headers except login.
Here is my configuration:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
http
.formLogin().failureUrl("/login?error")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
}
Here is my javascript to submit the login request:
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$("#login").click(function(){
username=$("#username").val();
password=$("#password").val();
$.ajax({
type: "POST",
url: "/login",
beforeSend: function(xhr){
xhr.setRequestHeader(header, token);
},
data: "username="+username+"&password="+password,
success: function(html){
alert("logged in");
}
});
return false;
});
And here is the error I get:
s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
o.s.w.s.m.m.a.HttpEntityMethodProcessor : Written [{timestamp=Tue Oct 21 09:56:56 MDT 2014, status=403, error=Forbidden, message=Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'., path=/login}] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#731cc24a]
o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
And the 403 response I receive in my browser is:
"Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'."
Is the something that I am doing wrong that all my requests POST/PUTs get rejected for this?
UPDATE:
Thanks to excellent advice, I added the headers into all my requests and that seems to get me past the csrf issue, only to result in a 302 redirect.
I opened another question [spring boot / spring security is ignoring /login call from javascript for the javascript being ignored from spring-security issue

Categories

Resources