When I click submit on my HTML form it is not hitting the /greeting endpoint
#org.springframework.stereotype.Controller
#EnableAutoConfiguration
public class Controller {
#Autowired
assessment.PdfGeneratorUtil pdfGenaratorUtil;
#GetMapping ("/")
String home() {
return "static/assessment.html";
}
#PostMapping("/greeting")
public String greetingSubmit() {
Map<String,String> data = new HashMap<String,String>();
data.put("name","James");
Thymeleaf config
#Configuration
public class TheymeLeafConfiguration {
#Bean
public ClassLoaderTemplateResolver emailTemplateResolver(){
ClassLoaderTemplateResolver emailTemplateResolver=new ClassLoaderTemplateResolver();
emailTemplateResolver.setPrefix("templates/");
emailTemplateResolver.setTemplateMode("HTML5");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode("XHTML");
emailTemplateResolver.setCharacterEncoding("UTF-8");
emailTemplateResolver.setOrder(1);
return emailTemplateResolver;
}
}
And a snippet of the html:
<form action="#" th:action="#{/greeting}" method="post">
Which when I submit the form doesn't hit the breakpoint on the POST controller (the same line as the Map) and I get a 405 in the browser:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
The URL in the browser after submit is:
http://localhost:8080/assessment.html#
Project structure:
UPDATE
It is working with this code, in so far as it hits the Get controller method, and also the Post controller method on submission. I am not quite sure what changed.
Note I am not using #ModelAttribute at this point in time since I was testing that the controller methods are getting called at the correct times.
#GetMapping("/greeting")
String greetingForm() {
return "assessment";
}
#PostMapping("/greeting")
public String greetingSubmit() {
System.out.println(" HELLLOO HELLOOOO ");
Map<String,String> data = new HashMap<String,String>();
data.put("name","James");
try {
pdfGenaratorUtil.createPdf("greeting",data);
} catch (Exception e) {
e.printStackTrace();
}
return "finished";
}
With the minimal details that you have given in your code, I will try to answer your question.
The first problem that I see in your code is you are not passing any value to the method with postmapping, which I see at odd as post are used to create resource in rest.
So if you want to map the values received from input to some model data you need to do it using #ModelAttribute annotation.
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting) {
return "greeting";
}
See Spring boot form handling
Check whether your template webpages are found. I dont see any page mapping to show the result of this submission.
So you need to return some page name where your browser will forward the response of your form submission. So if you dont add that, spring find the method, executes the code, but dont know where to redirect. In this case as well it shows the 415 HttpStatus code.
So add something like below:
return "greeting";
So this above code will look for /templates/greeting.html as per your resolver configuration.
I assume you are also using spring security in your project (assumption from screenshot. [class SecurityConfig.])
If you use spring security, the CSRF filter is enabled by default in spring-boot. So either you need to disable it or add csrf param in your login form post request. (recommended). For the remaining forms, CSRF token will be automatically added to forms with hidden input.
To Disable CSRF:
In your spring securityConfig disable the csrf protection on HttpSecurity object something like below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginProcessingUrl("/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password").permitAll()
.and()
.csrf().disable()
}
Or To add csrf in your login form as below:
input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
Related
TLDR: My method requires 2 redirects/forwards to work (1 for authentication and 1 to serve the jsp page). How can I resolve both redirects/forwards (or make it a non-requirement) so as to not run into the error, java.lang.IllegalStateException: Cannot forward after response has been committed.
For more context:
I have a java servlet with a method that looks something like the following:
#GET
#Path("/test")
#Authenticate
public Viewable test(#Context HttpServletRequest request, #Context HttpServletResponse response) {
Map<String, Object> model = createModel();
return new Viewable("/somePath/jspFile", model);
}
The #Authenticate annotation intercepts the call to do some Open ID Connect type authentication which results in the user being forwarded to a different server for all authentication needs. If the user is authenticated, they are redirected back to my application.
However, when hitting the url for this method, I am getting java.lang.IllegalStateException: Cannot forward after response has been committed. I don't know too much about using this Viewable class, but based on the fact that I don't run into that error when returning String/void/whatever else, I assume returning a new Viewable needs to do some forwarding that results in the user seeing the jsp page.
I've read the main SO post about this error, but I am unsure how to apply the fixes to my current problem. For example, I don't know how I would apply something like the following fix:
protected void doPost() {
if (someCondition) {
sendRedirect();
} else {
forward();
}
}
The fix assumes that I can I can either redirect OR forward, but my current method needs a redirect for authentication AND a forward/redirect to serve the jsp page. Maybe there's an obvious fix I'm missing that doesn't require a complete rehaul of the current code?
Edit: It would be nice if I could check if the user was authenticated first, but I assume using this annotation at all automatically entails an initial redirect
Edit: It looks like the user is redirected for the initial login authentication, but does not need to be redirected again after being authenticated once due to SSO
Ok based on some preliminary testing, it seems like the following solution has worked for me:
Check if the user has already been authenticated
Return a Response rather than a Viewable.
Since the user only needs to be redirected the first time for authentication, I can return an empty/meaningless response as a placeholder. And then once the user has been authenticated and is returned to my app, I can return a Viewable wrapped in a Response object.
So the code would look something like the following:
#GET
#Path("/test")
#Authenticate
public Response test(#Context HttpServletRequest request, #Context HttpServletResponse
response) {
Map<String, Object> model = createModel();
if (userIsAuthenticated()) {
return Response.status(401).build();
} else {
return Response.ok(new Viewable("/somePath/jspFile", model)).build();
}
}
I'm developing a web app using Spring Boot v2.1.9.RELEASE and Thymeleaf. The app mixes server-side rendering (Spring MVC regular controllers) with rest controllers called through AJAX. The page works like a charm as long as GET and POST are the only HTTP methods I use. As soon as I add a - totally unrelated, yet unused - DELETE REST controller, CSS and JS resources stop being loaded and errors like the following are shown in browser console:
The resource from “http://localhost:8080/css/demo.css” was blocked due to MIME type (“application/json”) mismatch (X-Content-Type-Options: nosniff)
If I remove the new controller, the page starts working again.
When I inspect the request with Network Monitor, I observe that:
X-Content-Type-Options: nosniff header is always present in the response, which is consistent with Spring Security documentation.
if the DELETE endpoint is not there, a GET /css/demo.css request returns with a HTTP 200 code as expected. Both Accept (request header) and Content-Type (response header) are marked as text/css.
when I add the endpoint, the above request returns a HTTP 405. Accept header is text/css but Content-Type becomes application/json. Also, a new response header is added: Allow: DELETE.
My guess is that when Spring scans for controllers and a DELETE method is found, some extra configuration is automatically added, but I couldn't find any reference to confirm that. I would like to know why is this happening and how can I avoid this behaviour.
Since I'm developing local, my Spring Security config is pretty straightforward:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManagerConfigurer authConfigurer() {
return new InMemoryAuthenticationManagerConfigurer();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
authConfigurer().configure(auth);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The REST controller I'm adding does almost nothing:
#RestController
public class MotivosController {
private final String CONTROLLER_PATH = "/rest/motivos/{id}";
#RequestMapping(name=CONTROLLER_PATH, method=RequestMethod.DELETE)
public void deleteMotivo(#PathVariable(name="id") String id) {
logger.debug("Eliminando el motivo " + id);
}
// Other members I consider unrelated to the problem go here...
}
CSS and JS files are being loaded in a template.html file placed under src/main/resources/templates folder, as follows:
<link type="text/css" href="/css/demo.css" rel="stylesheet" />
Answer was easier than I thought and I was totally wrong with my assumptions. I confused #RequestMapping's name attribute with value attribute, and because of that any URL matches that method. Changing name for value made it work.
I have a controller in my project that handles all exceptions defined like this:
#ControllerAdvice
public class GlobalExceptionHandlingController {
#ResponseBody
#ExceptionHandler(value = AccessDeniedException.class)
public ResponseEntity accessDeniedException() {
Logger.getLogger("#").log(Level.SEVERE, "Exception caught!");
return new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN);
}
}
I'm focusing on one specific exception here and that is AccessDeniedException that is thrown by Spring Security on unauthorized requests. This is working properly for "normal" aka non-ajax requests. I can click on a link or enter URL directly in the location bar and I will see this message if request is unauthorized.
However on AJAX request (using Angular for it) I'm getting standard 403 error page as a response but what's interesting is that I can see that AccessDeniedException is caught by this controller!
I did some research and it seems that I need to have custom AccessDeniedHandler so I made this:
Added this lines in my Spring Security configuration:
.and()
.exceptionHandling().accessDeniedPage("/error/403/");
and I made special controller just to handle this:
#Controller
public class AjaxErrorController {
#ResponseBody
#RequestMapping(value = "/error/403/", method = RequestMethod.GET)
public ResponseEntity accessDeniedException() {
return new ResponseEntity("Access is denied (AJAX)", HttpStatus.FORBIDDEN);
}
}
Now this is working fine but the exception is still caught in the first controller but return value of that method is getting ignored. Why?
Is this how it's supposed to be done? I have a feeling that I am missing something here.
I'm using Spring 4.2.5 with Spring Security 4.0.4.
Although I don't know all the details, my theory is that it can be a content type issue.
Often when doing AJAX requests, the response is expected to be in JSON, so the browser will add an Accept: application/json header to the request.
Your response entity on the other hand:
new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN)
is a text response, the default Content-Type of this with a typical Spring setup is text/plain.
When Spring detects that it can't deliver a response with type the client wants, it fallbacks to the default error page.
How can I get _csrf object (?!) in spring controller? I've configured Spring Security and can get ${_csrf} request attribute in jsp files.
I've tried:
CsrfToken _csrf = (CsrfToken) session.getAttribute("CsrfToken");
CsrfToken _csrf = (CsrfToken) session.getAttribute("_csrf");
the result is null;
Thanks in advance!
In debug I saw a session attribute with a key "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN". I viewed the HttpSessionCsrfTokenRepository class. It has a method for loading token from incoming HttpServletRequest object.
Finally this worked for me:
CsrfToken token = new HttpSessionCsrfTokenRepository().loadToken(request);
I will be grateful if someone explains me how this works.
To access the CSRF token in a Spring controller you can simply do this:
#Controller
public class FooController {
#RequestMapping("/foo")
public void foo(CsrfToken token) {
// Do whatever with token
}
}
Spring will automatically detect that you want the token, based on the type of the parameter, and inject it into your method.
This works since at least Spring Security 5.0 and if you are using Spring Boot or have the #EnableWebSecurity annotation in your configuration.
Documentation
Try:
CsrfToken token = (CsrfToken) session.getAttribute(CsrfToken.class.getName());
I think in your earlier attempts, you were mixing up the CSRF parameter name with the session attribute name, and also trying CsrfToken.class.getName() which may or may not have been used in earlier versions. So simply, you had the right idea but the wrong key.
If you look at the source code for HttpSessionCsrfTokenRepository, you'll see it defines the following defaults:
private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
private String headerName = DEFAULT_CSRF_HEADER_NAME;
private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
The first one is the parameter name for when the token comes as a POST parameter, the second is the header name for when it comes in the request header, and the third is the key for storing it in the session. The method loadToken doesn't actually get the token from the request object - it gets the session object from the request and then looks up the token, which it earlier stored with the key defined by sessionAttributeName.
This also works if you want to get it directly from the session
CsrfToken token = (CsrfToken) session.getAttribute("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN");
You can have HttpServletRequest instance inside Controller's resource method as a parameter. Using this request object you can get the csrf token easily.
#Controller
#RequestMapping("/api/v1/test")
public class TestController {
#GetMapping
public String test(HttpServletRequest request) {
CsrfToken csrfToken =
(CsrfToken)httpServletRequest.getAttribute(CsrfToken.class.getName());
if(csrfToken != null)
return csrfToken.getToken();
return "Token Not Found";
}
}
Csrf Token value used to create using java.util.UUID class, as following:-
UUID.randomUUID().toString();
Check org.springframework.security.web.csrf.CookieCsrfTokenRepository and org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository classes which are implementation of CsrfTokenRepository, inside spring-security-web-X.X.X.RELEASE.jar.
If you want to have the CSRF token in cookies with response at client side (say browser) then:-
#Configuration
#EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.....
.forLogin();
}
}
Enable the csrf and use CookieCsrfTokenRepository csrf token repository.
Fetch the token from cookie with cookie name - "XSRF-TOKEN"
Use this token in another requests [except GET, HEAD, TRACE, OPTIONS] header with header key as X-XSRF-TOKEN
I'm having the weirdest problem I have ever seen before.
The application I am working on uses spring security 3.1.3 to provide authentication support. There is a custom login form for which I have implemented a custom authenticationmanager / successhandler and failurehandler.
For some reason on internet explorer I always get the error message "Please fill in all mandatory fields". This is caused by appending /login?error=1 to the end of my url which can only be accessed through the following code (the redirectAndAddError method):
public class TideUserNamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public TideUserNamePasswordAuthenticationFilter() {
super();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
String password = request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
redirectAndAddError(response);
return null;
}
return super.attemptAuthentication(request, response);
}
private void redirectAndAddError(HttpServletResponse response) {
try {
response.sendRedirect("/tide/login?error=1");
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
}
So what I tried was using Fiddler2, a web debugging proxy to view if one of the two parameters are actually empty. The strange thing is that when this program is running the error does not occur anymore and I can log on successfully.
Had anyone had a similar problem before? I think it's not related to my code as running the tool suddenly "solves" the problem.
This problem only occurs in internet explorer which makes it even more strange.
Edit
I have used another tool to watch the requests and this is what happens in IE:
First a POST request is sent to the uri /authenticate, I have set this myself like this:
<beans:property name="filterProcessesUrl" value="/authenticate"/>
The response of that request has http status code 302, moved temporarily and returns that the new location is at /login?error=1 (my form with the mandatory fields required error).
After that a GET request occurs to /login?error=1 with status code 401: Unauthorized. The intercept-url is set up like this:
<intercept-url pattern="/login**" access="permitAll"/>
The next request is a GET request to /login?error=1 again, this time the status code is showing: ERROR_INTERNET_CONNECTION_RESET, which looks like it could be a problem.
In Google Chrome the following request is made:
POST to /authenticate, result is a 302: moved temporarily to the dashboard page (which I display after logging on)
Someone on my team finally figured out what the problem was after finding this issue in the chromium bugtracker:
https://code.google.com/p/chromium/issues/detail?id=62687
The problem has been resolved by adding this in our login controller
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String doLogin() throws ServletException, IOException {
return "forward:/authenticate";
}
and changing the url that the form posts to to this one instead of the authentication url that spring security provides (we are redirecting to it manually now)