There is class, who extends HandlerInterceptorAdapter, with method:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
...
request.getSession().setAttribute("user", user);
}
This attribute is visible to all views, except error views :(
My ExceptionConfiguration:
#Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "error/error");
r.setExceptionMappings(mappings);
r.setDefaultErrorView("error/defaultError");
return r;
}
Piece of my "error/error" page:
<h3 th:text="${user.name}" />
<p><h3 th:text="${exception.message}" /></p>
<h3>Please contact support.</h3>
And my exception is:
org.springframework.web.util.NestedServletException: Request processing failed
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "user.name"
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Field or property 'name' cannot be found on null
How can I make attribute 'user' visible to error pages too?
Thank you in advance.
Related
#Controller
public static class forLostController {
#GetMapping("/losing")
public String lostingForm(Model model) {
model.addAttribute("losing", new lostUser());
return "losing";
}
#PostMapping("/losing")
public String losingSubmit(#ModelAttribute lostUser losing) throws BrokerException {
//it works!!
System.out.println(losing.getName());
System.out.println(losing.getHeight());
//something wrong while showing the page "resultForLost.html"
return "resultForLost";
}
}
resultForLost.html:
<span class="visible-xs"><p th:text="'姓名: ' + ${losing.name}" /></span>
In the console of the Idea:
2020-10-13 21:30:54.485 ERROR 7941 --- [nio-9998-exec-3] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-9998-exec-3] Exception processing template "resultForLost": Exception evaluating SpringEL expression: "losing.name" (template: "resultForLost" - line 30, col 52)
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "losing.name" (template: "resultForLost" - line 30, col 52)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
...
2020-10-13 21:30:54.486 ERROR 7941 --- [nio-9998-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "losing.name" (template: "resultForLost" - line 30, col 52)] with root cause
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
Why it is null for losing? The input of the function losingSubmit() is #ModelAttribute lostUser losing, so it will be sent to the "resultForLost.html" page, right?
What should I do?
I successfully run it without error.
#PostMapping("/finding")
public String losingSubmit(#ModelAttribute("losing") lostUser losing) throws BrokerException {
//code
}
it's ok
You need to add losing attribute to the model in losingSubmit method.
#PostMapping("/losing")
public String losingSubmit(#ModelAttribute lostUser losing, Model model) throws BrokerException {
//it works!!
System.out.println(losing.getName());
System.out.println(losing.getHeight());
//add this line
model.addAttribute("losing", losing);
//something wrong while showing the page "resultForLost.html"
return "resultForLost";
}
I'm trying to upload .pdf file with jQuery AJAX to Spring MVC 5 with Spring Security 5 back-end running on Tomcat and faced multiple issues depending on Spring configuration
NOTE:
File upload should be available without authentication
Front-end
Markup:
<div id="upload-modal" class="modal">
<div class="modal-content">
<h4>Upload</h4>
<form action="#" enctype="multipart/form-data">
<div class="file-field input-field">
<div class="btn">
<span>View...</span>
<input type="file" name="file" accept="application/pdf">
</div>
<div class="file-path-wrapper">
<label>
<input class="file-path validate" type="text">
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
Cancel
Upload
</div>
</div>
csrf header for all the requests:
$(document).ready(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
Uploading with jQuery AJAX:
$("#upload-bttn").click(function () {
var $uploadModal = $("#upload-modal");
const fileName = $uploadModal.find(".file-path").val();
const extension = fileName.substr(fileName.lastIndexOf(".") + 1);
if (extension === "pdf") {
$.ajax({
url: "/upload",
type: "POST",
data: new FormData($uploadModal.find("form").get(0)),
processData: false,
contentType: false,
success: function () {
console.log("success")
},
error: function () {
console.log("error")
}
});
} else {
M.toast({html: 'Selected file is not .pdf'});
}
});
Back-end
General configuration looks like below. It is modified depending on the cases
Security Initialization:
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(SecurityContext.class);
}
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
Application initialization:
public class ApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
servletContext.getSessionCookieConfig().setHttpOnly(true);
servletContext.getSessionCookieConfig().setSecure(true);
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(WebAppContext.class);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
Case 1 - CommonsMultipartResolver bean definition
CommonsMultipartResolver bean definition:
#Bean
public CommonsMultipartResolver multipartResolver(
#Value("${max.upload.size}") Integer maxNumber,
#Value("${max.size}") Integer maxSize) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
resolver.setMaxUploadSizePerFile(maxSize);
resolver.setMaxInMemorySize(maxSize);
resolver.setDefaultEncoding("UTF-8");
try {
resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
} catch (IOException e) {
e.printStackTrace();
}
return resolver;
}
I remember there was strange Spring behavior when MultipartResolver bean should be named "multipartResolver" explicitly. I tried both #Bean and #Bean("multipartResolver") with configuration above and had same result (despite bean above is named "multipartResolver" as per method name)
Result:
Error 500 - Unable to process parts as no multi-part configuration has been provided
Case 2 - MultipartConfigElement in Servlet registry
removed CommonsMultipartResolver bean
added StandardServletMultipartResolver bean
added MultipartConfigElement to ApplicationInitializer
StandardServletMultipartResolver bean definition:
#Bean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
Updated ApplicationInitializer:
#Override
public void onStartup(ServletContext servletContext) {
...
servlet.setMultipartConfig(new MultipartConfigElement(
System.getProperty("java.io.tmpdir")
));
}
As per Spring documentation:
Ensure that the MultipartFilter is specified before the Spring Security filter. Specifying the MultipartFilter after the Spring Security filter means that there is no authorization for invoking the MultipartFilter which means anyone can place temporary files on your server. However, only authorized users will be able to submit a File that is processed by your application
As I need to allow not authenticated users to upload the files I tried both before and after in SecurityInitializer as below with the same result
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
or
#Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
Result:
Error 403
Questions
What do I miss in the configuration?
Thoughts
CommonsMultipartResolver would be preferable as allows to drive it with Spring properties
Something wrong with Spring Security context setup
There is allowCasualMultipartParsing="true" option (did not test) which I wouldn't like to stick to as its Tomcat specific
Updates
With disabled Spring Security everything works properly
http.authorizeRequests().antMatchers("/**").permitAll(); remains as the only security context configuration so don't think its Security context configuration issue
Set multipart resolver bean name explicitly in MultipartFilter in
beforeSpringSecurityFilterChain(ServletContext servletContext) and still no luck
Adding of _csrf token to the request header did not work for both cases
Realized that I miss additional WebAppContext class in SecurityInitializer constructor. Now error 500 disappeared but 403 appeared for case 1. Logging says that I have invalid csrf token despite I added it to the header like above
Tried to submit the form with csrf token including hidden input <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> yet the result is the same - error 403 with invalid token statement
After two days of struggling:
Constructor should contain both security and application context configuration classes
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(SecurityContext.class, WebAppContext.class);
}
}
Application context (WebAppContext) should contain MultipartResolver bean definition
#Bean
public CommonsMultipartResolver multipartResolver(
#Value("${max.upload.size}") Integer maxNumber,
#Value("${max.size}") Integer maxSize) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1024 * maxSize * maxNumber);
resolver.setMaxUploadSizePerFile(maxSize);
resolver.setMaxInMemorySize(maxSize);
resolver.setDefaultEncoding("UTF-8");
try {
resolver.setUploadTempDir(new FileSystemResource(System.getProperty("java.io.tmpdir")));
} catch (IOException e) {
e.printStackTrace();
}
return resolver;
}
In my case after application initialization csrf token inside Spring CsrfTokenRepository was empty for some reason so when Spring been comparing token from client request header with null in CsrfFilter Spring was returning error 403. I configured csrf in security context in the following way:
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
...
}
Now csrf token is passed in cookies with first server response to the browser and the repository generates and caches a token to compare against the one coming from the client so comparison passes successfully
Here CookieCsrfTokenRepository may also be declared as CookieCsrfTokenRepository.withHttpOnlyFalse() if you would like to grab the token from cookie and set it into csrf header, but I have chosen to go with meta tags approach above
need your help here please.
I have a login page. After I enter username/password, I want to see Dashboard page. But I am getting 404 page not found. Can anyone please tell what is going on here.
When I hit http://localhost:8080 -> It goes to http://localhost:8080/login - Which is expected.
After I enter username/password, it goes to http://localhost:8080 - Expected: to go to Dashboard page i.e. http://localhost:8080/dashboard
#Component
public class SimpleAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
...
redirectStrategy.sendRedirect(request, response, "dashboard");
}
DashboardController.java
#Controller
#RequestMapping("/dashboard")
public class DashboardController {
#RequestMapping("/")
String init(){
System.out.println("Dashboard - init()");
return "dashboard_init";
}
}
app.component.html
Hello... {{name}}
Hello... {{title}}
<h1>
Welcome {{title}}!
</h1>
<p>Id: <span>{{greeting.id}}</span></p>
<p>Message: <span>{{greeting.content}}!</span></p>
app-routing.module.ts
import {DashboardComponent} from "./dashboard/dashboard.component";
const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
// { path: 'dashboard', redirectTo: '/dashboard', pathMatch: 'full' },
{
path: 'dashboard_init',
component: DashboardComponent,
data: { title: 'Dashboard' }
}
];
dashboard.component.ts
#Component({
selector: 'dashboard-component',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
})
export class DashboardComponent implements OnInit {
private currentAssociate: Associate;
constructor(private http: Http,
private router: Router) {
}
ngOnInit(): void {
// initialize services and data
this.http
.get('/dashboard')
.toPromise()
.then(response => {
let data = response.json();
if (data.currentAssociate) this.currentAssociate = data.currentAssociate as Associate;
})
.catch(error => {
// this.alertService.error(error);
});
}
}
dashboard.component.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8" />
<title>Dashboard</title>
</head>
<div>
<B>Dashboard...</B>
</div>
</html>
Error: (When the url is http://localhost:8080/dashboard/)
Dashboard - init()
[2m2018-03-26 10:07:20.421[0;39m [31mERROR[0;39m [35m13184[0;39m [2m---[0;39m [2m[nio-8080-exec-2][0;39m [36morg.thymeleaf.TemplateEngine [0;39m [2m:[0;39m [THYMELEAF][http-nio-8080-exec-2] Exception processing template "dashboard_init": Error resolving template "dashboard_init", template might not exist or might not be accessible by any of the configured Template Resolvers
[2m2018-03-26 10:07:20.422[0;39m [31mERROR[0;39m [35m13184[0;39m [2m---[0;39m [2m[nio-8080-exec-2][0;39m [36mo.a.c.c.C.[.[.[/].[dispatcherServlet] [0;39m [2m:[0;39m Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: Error resolving template "dashboard_init", template might not exist or might not be accessible by any of the configured Template Resolvers] with root cause
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "dashboard_init", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:246)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1060)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1011)
something wrong overall..
in Controller replace top /dashboard with /
#Controller
#RequestMapping("/")
public class DashboardController {
#RequestMapping("/")
String init(){
System.out.println("Dashboard - init()");
return "dashboard_init";
}
}
Also as fas as I remember return "dashboard_init" is expecting dashboard_init.html template to be returned
Probably you want redirect or something to /dashboard_init, do like
#RequestMapping(value = "/", method = RequestMethod.GET)
public void index(final HttpServletResponse response) {
response.setStatus(HttpStatus.OK.value());
response.sendRedirect( "/wherever-you-want");
}
This series of tutorials will help you to integrate Spring Security with Angular, using different approaches. Starting with Basic Auth
Spring Security and Angular
I have an error when I want to acces my JSP page.
My bean:
public class BeChildren implements Serializable
{
...
private String isFilledChildren;
....
/**
* #param isFilledChildrenthe isFilledChildrento set
*/
public void setIsFilledChildren( String isFilledChildren)
{
this.isFilledChildren= isFilledChildren;
}
public String getIsFilledChildren( )
{
if ( getNom( ) != null )
{
return "true";
} else
{
return "false";
}
}
...
}
Error:
28/07/17-09:13:10,670 ERROR org.apache.struts.taglib.tiles.InsertTag - ServletException in '/pages/sub/dir/detail/body.jsp': javax.servlet.jsp.JspException: Invalid argument looking up property: "bean.enfant.isFilledChildren" of bean: "sub/dir/detail"
org.apache.jasper.JasperException: javax.servlet.ServletException: javax.servlet.jsp.JspException: Invalid argument looking up property: "bean.enfant.isFilledChildren" of bean: "sub/dir/detail"
javax.servlet.jsp.JspException: Invalid argument looking up property: "bean.children.isFilledChildren" of bean: "sub/dir/detail"
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.String
My JSP:
https://pastebin.com/QmgtXBqA
...
<html:form action="/page/sub/dir/detail.do">
<html:hidden name="sub/dir/detail" property="modeCreation" styleId="modeCreation"/>
<html:hidden name="sub/dir/detail" property="bean.enfant.isFilledChildren"/>
....
<logic:equal name="sub/dir/detail" property="bean.enfant.isFilledChildren" value="true">
.....
</logic:equal>
...
<script language="javascript" type="text/javascript">
var f = document.forms[0];
function init(){
var isFilledChildren = document.forms[0].elements["bean.enfant.isFilledChildren"];
....
if (isFilledChildren!=null && "true"==isFilledChildren.value){
...
}
}
....
What is wrong ?
The error is ecause you pass boolean value to isFilledChildren property as parameter
<logic:equal name="sub/dir/detail" property="bean.enfant.isFilledChildren" value="true">
In your bean the property accepts String value
The property that is of String type should use the string value to avoid ClassCastException.
<logic:equal name="sub/dir/detail" property="bean.enfant.isFilledChildren" value="'true'">
Changing the type for isFilledChildren property to String would resolve this.
If it does not, try with <logic:match /> and <logic:notMatch /> (if it's feasible in this use case). See below for logic:match sample code"
<logic:match name="UserForm" property="favouriteFood" value="Pizza">
I am creating a spring security application using Spring 4.0.2.RELEASE and Spring Security 3.2.3.RELEASE using entirely java configuration, no xml. The configuration for security seems to be working correctly and is generating the login page correctly and authenticating. However I get 404 errors for all of my pages.
I have controllers and jsp pages set up for each page. When I run the application, I see log messages showing that the controllers were mapped
Mapped "{[/ || /welcome] ... onto ... WelcomeController.welcome()
However, when I try to hit one of those URLs, I get the login page, then on sucessfull login get a 404 and I see nothing in the log.
Below you will find my controller, my 2 configuration classes, and my 2 initializes.
WelcomeController.java
#Controller
public class WelcomeController {
#RequestMapping(value = {"/", "/welcome"})
public ModelAndView welcome() {
System.out.println("welcome invoked");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("welcome");
return modelAndView;
}
}
Below You will find my configuration files
WebConfig.java
#EnableWebMvc
#Configuration
#ComponentScan({ "com.myproject.pagegen.controller" })
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver
= new InternalResourceViewResolver();
resolver .setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").hasRole("USER")
.antMatchers("/welcome").hasRole("USER")
.anyRequest().anonymous()
.and().formLogin();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
WebAppInitializer.java
public class WebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { WebConfig.class, SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
SecurityWebAppInitializer.java
public class SecurityWebAppInitializer
extends AbstractSecurityWebApplicationInitializer { }
UPDATE:
I did find something interesting. If I changed my servletMapping to /* instead of /, I would then get a log message showing that the controller was invoked, but it would have no mapping for the jsp. It seems like it is trying to map the jsp url to the controllers.
welcome invoked
org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/ROOT/WEB-INF/jsp/welcome.jsp] in DispatcherServlet with name 'dispatcher'
I had a similar issue when running a project using Eclipse and Tomcat. Try running the application using Spring Tool Suite and use the VMware vFabric tc server.
I also got it to work in Eclipse and Tomcat by manually updating the version of Tomcat installed. Try the latest version 7.0.54 https://tomcat.apache.org/download-70.cgi
It seems to me that all troubles in your authentication mechanism and not in controller mapping for URL-s.
It is a good tutorial about Security Java config LINK
When you try go to your pages without authentifications you are redirected to 'login page'. If I've understand right - your authentification process is fail all the time. In your configuration you can't go to '/welcome' and '/' pages without being authorized.
Try to add and change next things:
protected void configure(HttpSecurity http) throws Exception {
// Here you can define your custom redirections
.loginPage("[some url]")
.failureUrl("[some url]")
.loginProcessingUrl("[some url]")
.defaultSuccessUrl("[some url]")
// Here the credentials, sended to the authentification mechanizm (If you use clasic 'UsernamePasswordAuthenticationFilter')
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
// Permitions for access pages
http.authorizeRequests()
.antMatchers("/welcome", "/").permitAll()
// Access restriction without using roles
.antMatchers([some other URL-s]]).authenticated()
// or using ROLES
.antMatchers([some other URL-s]]).hasRole("USER")
// Here you can define your custom redirections for logout
.and()
.logout()
.logoutUrl("/logOut")
.logoutSuccessUrl("/welcome");
}
If you use clasic 'UsernamePasswordAuthenticationFilter' you must define it as bean (If you use another fllter, of course you must define it):
#Bean
public UsernamePasswordAuthenticationFilter filter() {
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
filter.setAuthenticationManager(providerManager());
return filter;
}
And then in JSP page use form like this ('j_username' and 'j_password' parameters required in default authentication with 'UsernamePasswordAuthenticationFilter')
<form name="signIn" action="[your SignIn URL]" method="POST">
<input type="text" name="j_username"></label>
<input type="password" name="j_password"></label>
<input type="submit" value="Log In">
</form>
You must try the remote debugging to find what fail in your authentication process.