Spring REST controller file upload - Request method 'POST' not supported error - spring

I'm in the process of creating a prototype for a web app, where one of the functions should allow someone to upload an excel file with some extra info. The file alongside the info is stored in an object, which is serialized and stored.
I have created the upload method, and while trying to test the function it throws the "Request method 'POST' not supported" whitelabel error page.
I have suspicions that it might be the pom.xml at fault, but I'm not entirely sure.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>project</groupId>
<artifactId>answers</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>MVC 1.0 Blank Project (from https://github.com/making/mvc-1.0-blank)</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.ozark</groupId>
<artifactId>ozark</artifactId>
<version>1.0.0-m02</version>
</dependency>
<!-- Swing exercise -->
<dependency>
<groupId>com.miglayout</groupId>
<artifactId>miglayout</artifactId>
<version>3.5.5</version>
</dependency>
<!-- Web exercise -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Here is the Controller class. We will move to a Thymeleaf configuration in a day or two, this html is just for testing purposes currently.
#MultipartConfig
#RestController
#RequestMapping(value = "/Teacher", produces = "text/html;charset=UTF-8")
public class Teacher {
#GetMapping("")
#ResponseBody
TestController testcont;
public String homePage(#RequestParam(value = "file", required = false) String name, HttpServletRequest request,
HttpServletResponse response){
StringBuilder sb = new StringBuilder();
sb.append("<p> <a href='/Teacher/NewTest'>New Test upload</a></p>\n"
+ "<p><a href='/SelectTest'>Select Test File</a> <button type='button'>Send Test</button></p>"
+ "\n \n \n"
+ "<p><a>Current Test for students:</a>\n <a href='/getCurrentTest'></a></p>"
);
return sb.toString();
}
#GetMapping("/NewTest")
#ResponseBody
public String newTestUpload(HttpServletRequest request, HttpServletResponse response){
StringBuilder sb = new StringBuilder();
if(!request.getParameterNames().hasMoreElements()){
sb.append("<p><form action='' method='post' enctype='multipart/form-data'>"
+ "<label>Enter file</label><input type='file' name='file'>"
+ "<button type='submit'>Upload</button></p>"
+ "<p><form action='/testName'>Test Name: <input type='text' name='name' value=''></p>"
+ "<p><form action='/addInfo'>Comment: <input type='text' comment='comment' value=''></p>"
+ "<p>Answer 1: <input type='text' Answer='answer1' value=''></p>"
+ "<p>Answer 2: <input type='text' Answer='answer2' value=''></p>"
+ "</form>"
+ "<a href='/Teacher'>Back</a>\n"
);
return sb.toString();
}
else if(request.getParameter("file") != null && request.getParameter("name") != ""
&& request.getParameter("comment") != "" && request.getParameter("answer1") != null
&& request.getParameter("answer2") != null){
try{
// Upload happens here
Part filePart = request.getPart("file");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
InputStream fileContent = filePart.getInputStream();
File testExcel = new File(fileName);
testExcel.createNewFile();
Files.copy(fileContent, Paths.get(testExcel.getName()), StandardCopyOption.REPLACE_EXISTING);
double ans1 = Double.parseDouble(request.getParameter("answer1"));
double ans2 = Double.parseDouble(request.getParameter("answer2"));
Test test = new Test(testExcel, request.getParameter("name"),
request.getParameter("comment"), ans1, ans2);
testcont.addTest(test);
sb.append("New test uploaded!<br/>\n<a href='/'>Back</a>\n");
return sb.toString();
} catch (Exception e){
sb.append("<h1>Couldnt insert test</h1>");
response.setStatus(HttpServletResponse.SC_OK);
return sb.toString();
}
}
else{
sb.append("failed<br/>\n<a href='/'>Back</a>\n");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return sb.toString();
}
}
}

From the controller code you've posted, both of your endpoints are GET.
/Teacher :- #GetMapping
/Teacher/NewTest :- #GetMapping
You are trying to POST to an endpoint which does not support POST method, which is why you see error message, specifying 'POST' method not supported.

Both methods in your controller has #GetMapping annotation, that means that they support GET requests. You need to replace one with #PostMapping(to support POST requests)

Correct your method to:
#PostMapping
#RequestMapping("/NewTest")
public String newTestUpload(HttpServletRequest request, HttpServletResponse response) {
or
#PostMapping(path = "/NewTest")
public String newTestUpload(HttpServletRequest request, HttpServletResponse response) {

Related

#Validated + javax annotations not working for controller method parameters

I have a Restcontroller in a Spring project. And I have to validate its method's parameter.
Mainly Spring #Validated annotation on class + javax validation annotations are proposed to be a good practice working examples.
But it failed: Spring just ignores this validation: ConstraintViolationException supposed to be thrown is probably smoking somewhere outside and lets other exceptions do the dirty work following the code.
#RestController
#RequiredArgsConstructor
#RequestMapping("/reducedPageNumbers")
#Validated
#Tag(
name = "Page number reducer controller",
description = "This controller is responsible for the reduction of a given range of page numbers for printer"
)
public class PageReducerController {
private final PageReducerService service;
#Operation(
method = "GET",
summary = "Finding a user by ID",
responses = {
#ApiResponse(responseCode = "200", description = "Successful Request"),
#ApiResponse(responseCode = "400", description = "Bad Request. " +
"All page numbers must be integers, separated by comas", content = {
#Content(
mediaType = "application/json",
array = #ArraySchema(schema = #Schema(implementation = ErrorContainer.class)))
}),
#ApiResponse(responseCode = "500", description = "Unexpected Internal Server Error", content =
#Content)
},
description = "This method transforms a list of page numbers in one String line, separated by ',' " +
"into ascending reduced format of a String line for printer. " +
"E.g.: \"1,3,32,5,11,7,6,19,2,21,4,8,22,23\" -> \"1-8,11,19,21-23,32\". " +
"And returns a ReducerResponse object with both initial String input line 'original' " +
"and reduced String line 'reduced'"
)
#GetMapping
public ResponseEntity<ReducerResponse> show(#NotNull #NotBlank String rawPageNumbers) {
ReducerResponse response = service.reduce(rawPageNumbers);
return new ResponseEntity<>(response, HttpStatus.OK);
}
What can be a problem here?
Tried to validate a method parameter through #Validated + javax validation annotations. Expected: ConstraintViolationException. Actually resulted: validation totally ignored.
I also made sure I use spring validation starter, hibernate-validator 6.0.0 and configured MethodValidationPostProcessor as follows:
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor getMethodValidationPostProcessor(){
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(this.validator());
return processor;
}
#Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
My Pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<groupId>by.smirnov</groupId>
<artifactId>pagenumberreducer</artifactId>
<version>1.0.1</version>
<name>pagenumberreducer</name>
<description>Page number reducer test project</description>
<properties>
<java.version>11</java.version>
<springdoc.version>1.6.14</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.0.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
It started to work properly on Boot 3 with Jakarta 3.

Wire Mock Stub Response body is not read by Spring Boot

I am creating a Spring Boot based Micro Service. This service will call a external service. I want to create Stub for that service to do my integration testing.
I have following configuration. But for some reason my while running my test class Stub is not properly created due to which my test is failing.
Test class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"server.context-path=/app", "app.baseUrl=http://restapi-2.herokuapp.com"})
#AutoConfigureWireMock
#AutoConfigureMockMvc
class Restapi1ApplicationTests {
#Autowired
private ApiService apiService;
#Autowired
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MockMvc mockMvc;
#Test
void contextLoads() {
}
#Test
void getMessage() throws Exception {
MockRestServiceServer mockRestServiceServer = WireMockRestServiceServer.with(this.restTemplate)
.baseUrl("http://restapi-2.herokuapp.com")
.stubs("classpath:/stubs/companyresponse.json").build();
CompanyDetail companyDetail = new CompanyDetail();
companyDetail.setCompanyName("Test");
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/messages"))
.andExpect(status().isOk())
//.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.jsonPath("$.companyName").value("Test"));
//.andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(companyDetail)));
}
}
#RestController
public class Api_1_Controller {
private final ApiService apiService;
public Api_1_Controller(ApiService apiService) {
this.apiService = apiService;
}
#GetMapping(path = "/api/v1/messages")
public CompanyDetail getMessage(){
CompanyDetail companyDetail = apiService.getMessageFromApi();
return companyDetail;
}
}
#Service
public class ApiService {
private RestTemplate restTemplate;
public ApiService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public CompanyDetail getMessageFromApi(){
CompanyDetail companyDetail = null;
try{
companyDetail = restTemplate.getForEntity("http://restapi-2.herokuapp.com/companies",CompanyDetail.class).getBody();
}catch(Exception exception){
exception.printStackTrace();
throw exception;
}
return companyDetail;
}
}
#JsonInclude(value = JsonInclude.Include.NON_NULL)
public class CompanyDetail {
private String companyName;
//Getter and Setters
}
companyresponse.json is in below path
test/resources/stubs
{
"request": {
"urlPath": "/companies",
"method": "GET"
},
"response": {
"status": 200,
"jsonBody": {"companyName" : "Test"},
"headers": {
"Content-Type": "application/json"
}
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heroku</groupId>
<artifactId>restapinew-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>restapinew-1</name>
<description>restapi-1 project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<imageName>restapinew-1</imageName>
</configuration>
</plugin>
</plugins>
</build>
</project>
When Running this test case getting Response body as null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: No value at JSON path "$.companyName"
at org.springframework.test.util.JsonPathExpectationsHelper.evaluateJsonPath(JsonPathExpectationsHelper.java:304)
at org.springframework.test.util.JsonPathExpectationsHelper.assertValue(JsonPathExpectationsHelper.java:99)
Instead of JsonBody use body attribute, enclose your response (json response) in double quotes it may resolve the issue.

thymeleaf extras security doesn't work with spring Security

SO as a beginner i have tried to make an ecommmerce website using spring boot 2.2.11 , spring security , thymeleaf and also json web token , My problem is when a user authentificate the template doesn't change even i put isAnonyms and IsAuthentificated tags of thymeleaf in my template.
I have two question here :
1-/ how to tell all controller that the user is already logged ?
2-/ how to pass the jwt token from the backend to frontend so that the user can make specific request ?
Here is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.30</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version></version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The part Of my Index.html that contains the thymeleaf tags:
<div class="forms ml-auto">
<a th:href="#{/login}" class="btn" sec:authorize="isAnonymous()"><span
class="fa fa-user-circle-o"></span> Sign In</a>
<a th:href="#{/signup}" class="btn" sec:authorize="isAnonymous()"><span
class="fa fa-pencil-square-o"></span> Sign Up</a>
<a th:href="#{/account}" class="btn" sec:authorize="isAuthenticated()"><span
class="fa fa-pencil-square-o"></span> Account</a>
<a th:href="#{/cart}" class="btn"> Cart <span> 0 </span> <i class="fa fa-shopping-cart"></i> </a>
<a th:href="#{/logout}" class="btn" sec:authorize="isAuthenticated()"><span
class="fa fa-user-circle-o"></span> Logout</a>
</div>
My Controller For Login :
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Model model) {
model.addAttribute("userDto",new UserDto());
return "signin";
}
#RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(#ModelAttribute("userDto") #Valid UserDto userDto, BindingResult result , RedirectAttributes ra){
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userDto.getEmail(),userDto.getPassword()));
final UserDetails userDetails = userDetailsService.loadUserByUsername(userDto.getEmail());
if (!userDetails.getUsername().equalsIgnoreCase(userDto.getEmail()) ){
result.rejectValue("email",null,"Wrong Email");
}
if (!bCryptPasswordEncoder.matches(userDto.getPassword(),userDetails.getPassword())){
result.rejectValue("password","null","Wrong Password");
}
if (result.hasErrors()){
ra.addFlashAttribute("userDto",userDto);
return "signin";
}
final String jwt = jwtUtil.generateToken(userDetails);
System.out.println(jwt);
return "index";
}
My Spring Security Configuration :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/resources/**", "/static/**", "/public/**").permitAll()
.antMatchers("/", "/signin/", "/signup","/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**")
.hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().loginPage("/signin").defaultSuccessUrl("/")
.usernameParameter("email").passwordParameter("password")
.permitAll()
.defaultSuccessUrl("/",true)
.and().logout().logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/home/logout"));
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
Before login :
img_before_login
Image After Login :
redirect to index
Login Successfully and switching to other page :
switch page
ps: I will be thankful for any solution or any advice .
You can get if the user is authenticated by specifying a Principal as method argument in the #Controller. If the value is null, then the request is not authenticated. Otherwise, request is authenticated.
#GetMapping("/foo")
String foo(Principal principal) {
boolean isAuthenticated = principal != null;
...
}
Often you would provide a JWT when authentication success is achieved. Here is an example application.
The first step is to provide a way to authenticate the user. In this instance, we validate a username/password with basic authentication.
#Configuration
public class RestConfig extends WebSecurityConfigurerAdapter {
#Value("${jwt.public.key}")
RSAPublicKey key;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// #formatter:on
}
#Bean
UserDetailsService users() {
// #formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.authorities("app")
.build()
);
// #formatter:on
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
}
Then after basic authentication is succeeds it reaches the controller which produces the successful JWT in the response:
#RestController
public class TokenController {
#Value("${jwt.private.key}")
RSAPrivateKey key;
#PostMapping("/token")
public String token(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
// #formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// #formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
}
SignedJWT sign(SignedJWT jwt) {
try {
jwt.sign(new RSASSASigner(this.key));
return jwt;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
NOTE: You didn't specifically ask, but the likely reason the Thymeleaf tags don't appear to be working is that you are in a stateless application, so the authentication is lost immediately after log in since the session is not created.

Spring Boot Security with Jdbc Annotation

WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/icons/**", "/js/**", "/images/**").permitAll();
http.authorizeRequests().antMatchers("/bootstrap/**", "/icons/**", "/datatables/**", "/jquery/**",
"/font-awesome/**", "/select2/**").permitAll();
http.authorizeRequests().antMatchers("/", "/")
.permitAll()
.anyRequest()
.authenticated()
.and().formLogin()
.loginPage("/userForm")
.usernameParameter("userName").passwordParameter("password")
.defaultSuccessUrl("/login")
.failureUrl("/userForm")
.permitAll().and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/logout").permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, active_status from bgtool_test_users where username = ? and active_status = 'Y'")
.authoritiesByUsernameQuery(
"select username, role from bgtool_test_users where username = ?")
.passwordEncoder(passwordEncoder())
;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserController.java
#Controller
public class UserController {
private final Logger logger = LoggerFactory.getLogger(UserController.class);
#Autowired
private GameFacade gameFacade;
#RequestMapping("/userList")
public String list(Model model) {
List<User> users = gameFacade.findAllUsers();
model.addAttribute("users", users);
logger.debug("Users: {}", users);
return "userList";
}
#RequestMapping(value = "/userForm", method = RequestMethod.GET)
public String userForm(Model model) {
User entry = new User();
model.addAttribute("userLogin", entry);
logger.debug("Login Form");
return "loginForm";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#Valid #ModelAttribute("userLogin") User entry, BindingResult result, Model model) {
System.out.println("setting status N");
if (result.hasErrors()) {
logger.debug("Login Form validation error");
return "loginForm";
} else {
entry = gameFacade.findUserByName(entry.getUserName(), entry.getPassword());
if (entry == null) {
result.rejectValue("password", "error.userLogin", "Username or Password incorrect !!");
return "loginForm";
}
logger.debug("Login Successful", entry);
return "home";
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>frau</groupId>
<artifactId>bgtweb</artifactId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<derby.version>10.12.1.1</derby.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>${derby.version}</version>
<scope>runtime</scope>
</dependency>
<!-- SPRING SECURITY -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jheinzel.maven</groupId>
<artifactId>derby-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<derbyHome>${project.basedir}/data</derbyHome>
<port>1527</port>
<database>EMDb</database>
</configuration>
</plugin>
</plugins>
</build>
</project>
When i try to login, i am redirected back to loginform page. The controller method for mapping "/login" is not getting called as i am not getting my logger messages of same method in the console.
I have checked the SQL queries.they are correct.I am unable to find what is missing.
Any help is appreciated. Thanks in Advance
Your login page url and default success url is the same:
.loginPage("/userForm").usernameParameter("userName").passwordParameter("password")
.defaultSuccessUrl("/userForm")
Do you understand logical chain of Spring Security? You declare pages for each authentication step, configure authentication provider that check you username and and password. it's it. So there are two possible places for error - your mapping (pages and controller) and your DB (jdbcAuthentication()).
You event don't need a controller - only pages and and security config. try to simplify your example and remove controller and debug jdbc authentication
This example show correct way of configuration

spring boot 1.3.5 form:errors not showing for jsp

I am upgrading a spring-mvc web app from spring 4.X to be a spring boot war.
The page serves, the form is posted, the validation is executed (and records an error) but the jsp does show any errors in form:errors
THe same jsp works fine outside of spring-boot.
To be sure I'm setting my spring boot jsp app correctly I've simply added a form post to the existing "spring-boot-sample-web-jsp" (see https://github.com/spring-projects/spring-boot/tree/1.3.x/spring-boot-samples )
Here is the model object
package sample.jsp;
import java.io.Serializable;
public class EmailHolderPageModel implements Serializable {
private String emailAddress;
public EmailHolderPageModel() {
super();
}
public EmailHolderPageModel(String emailAddress) {
super();
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
Here is the server side:
#Autowired
private EmailSaveValidator emailSaveValidator;
#RequestMapping("/saveEmail.html")
public ModelAndView processEmail(#ModelAttribute("myModel") EmailHolderPageModel pageModel, BindingResult result){
ModelAndView modelAndView = null;
emailSaveValidator.validate(pageModel, result);
if(result.hasErrors()){
modelAndView = new ModelAndView("enterEmail");
EmailHolderPageModel pm = new EmailHolderPageModel("");
modelAndView.addObject("myModel", pm);
System.err.println("!!!Failed Validation!!!");
} else {
modelAndView = new ModelAndView("thankyou");
ThankyouPageModel thankYoupageModel = new ThankyouPageModel();
modelAndView.addObject("thankyouModel", thankYoupageModel);
}
return modelAndView;
}
Here is the validator
#Component
public class EmailSaveValidator implements Validator {
public boolean supports(Class candidate) {
return EmailHolderPageModel.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "emailRequired", "required field");
}
}
Here is the jsp (truncated a little because stackoverflow is getting confused)
<%# page session="false"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
<head>
<title>test entering email</title>
</head>
<body>
<form:form commandName="myModel" method="POST" action="saveEmail.html" >
<form:errors path="emailAddress" htmlEscape="false" />
<div id="formIntro">
<spring:message text="enter email address" />
<p><strong><label>
<spring:message text="email address:" /> </label><form:input path="emailAddress" size="35" maxlength="200"/>
</label>
</strong></p>
</div>
<input type="submit" value="Submit" />
</form:form>
</body>
</html>
The pom file is (unmodified from spring-boot-sample-web-jsp)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.3.6.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-web-jsp</artifactId>
<packaging>war</packaging>
<name>Spring Boot Web JSP Sample</name>
<description>Spring Boot Web JSP Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
</plugins>
</build>
</project>
==============
And the solution is do not create a new model object on error (though works fine when not a spring boot app):
#RequestMapping("/saveEmail.html")
public ModelAndView processEmail(#ModelAttribute("myModel") EmailHolderPageModel pageModel, BindingResult result){
ModelAndView modelAndView = null;
emailSaveValidator.validate(pageModel, result);
if(result.hasErrors()){
modelAndView = new ModelAndView("enterEmail");
// !! SOLUTION !!
// DO NOT CREATE A NEW MODEL OBJECT
// !! SOLUTION !!
// EmailHolderPageModel pm = new EmailHolderPageModel("");
modelAndView.addObject("myModel", pageModel);
System.err.println("!!!Failed Validation!!!");
} else {
modelAndView = new ModelAndView("thankyou");
ThankyouPageModel thankYoupageModel = new ThankyouPageModel();
modelAndView.addObject("thankyouModel", thankYoupageModel);
}
return modelAndView;
}
You commandName should be exacty the name of your model class: eg.
public class EmailHolderPageModel{}...
And in your controller any function:
public ModelAndView anythink(#Valid EmailHolderPageModel email...)
So, in you jsp form should be:
<form:form commandName="emailHolderPageModel" />...

Resources