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.
Related
I have built an application by using Spring Boot and Thymeleaf. My application works as supposed in my localhost, but when I package it as a .war and deploy it in a test tomcat server, it prompts the login page and then either redirects me to the error page or brings me back to the login page.
I have tried multiple things, and I think that there is an issue with the way I am handling the formLogin() inside my SecurityConfig.java class. More specifically, since Tomcat adds the base-url (e.g. from localhost:8080 to serverUrl:8080/reservation) when uploading my app, the loginProcessingUrl class probably fails to identify the "/process-login" class located on the login.html page.
Please find below my SecurityConfig.java class
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private AccessDeniedHandler accessDeniedHandler;
#Autowired
public SecurityConfig(UserService userService, CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler, AccessDeniedHandler accessDeniedHandler) {
this.userService = userService;
this.customAuthenticationSuccessHandler = customAuthenticationSuccessHandler;
this.accessDeniedHandler = accessDeniedHandler;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login-form-page","/resources/**").permitAll()
.antMatchers("/", "/new-reservation", "/your-reservations","/all-reservations",
"/change-password").hasAnyRole("ADMIN","EMPLOYEE","MANAGER")
.antMatchers("/users","/user-reservations","/arrival-date","/duplicate-reservations","/all-reservations","/registration**")
.hasAnyRole("ADMIN").and()
.formLogin()
.loginPage("/login-form-page")
.loginProcessingUrl("/process-login")
.successHandler(customAuthenticationSuccessHandler)
.permitAll()
.and()
.logout()
.logoutUrl("/login-form-page")
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/resources/**", "/static/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
And here is a small sample of the login.html page.
<div class="form-container sign-in-container">
<form id="loginForm" name="regForm" th:action="#{/process-login}"
method="POST">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<h1>Sign In</h1>
<!-- Login Error -->
<div th:if="${param.error}"
class="alert alert-danger col-xs-10">Wrong email and/or password</div>
<!-- Logout notify -->
<div th:if="${param.logout}"
class="alert alert-success 1 col-xs-10">You
have been logged out.</div>
All the .html pages are located in
-resources
-templates
Lastly, the only error I see in the logs is the following
DEBUG o.s.w.s.r.ResourceHttpRequestHandler - Resource not found
For anyone interested, I managed to solve the above issue. It seems like it was not a misconfiguration in the loginProcessingUrl() class. Instead, the issue was the way a remote server handles the JSESSIONID and csrf.
More specifically, what I had to do is
Added the following block of code in my SecurityConfig.java
cookieServerCsrfTokenRepository.setCookieHttpOnly(false);
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
Next, added the block of code below
#Bean
public ServletContextInitializer servletContextInitializer(#Value("${secure.cookie}") boolean secure) {
return new ServletContextInitializer() {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.getSessionCookieConfig().setSecure(secure);
}
};
}
The secure.cookie value needs to be set (in the application.properties to true if you intend to utilise the HTTPs protocol.
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
I have an application providing several REST endpoints and web pages as well.
/products -- REST endpoint
/cutomers -- REST endpoint
/ui/catalog -- Web
/ui/admin -- Web
I want to set up the security so all web starting with /ui/** are redirected to a login page and all the others (REST) are challenged with 401 and WWW-Authenticate.
With the following settings the login page is not permitted and 401 with the header is sent:
#Configuration
#Order(20)
#RequiredArgsConstructor
class RestConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.requestMatcher(AnyRequestMatcher.INSTANCE)
.authorizeRequests().anyRequest().fullyAuthenticated();
httpSecurity.
requiresChannel().
requestMatchers(AnyRequestMatcher.INSTANCE).
requiresSecure();
}
}
#Configuration
#Order(10)
#RequiredArgsConstructor
class WebUIConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.antMatcher("/ui/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll();
}
}
Why this doesn't work? I would expect to be redirected to the login page (it works) and the login page to be 200 (it doesn't work).
Editing the WebUIConfigurationAdapter as follows solves the problem:
httpSecurity.requestMatchers()
.antMatchers("/ui/**", "/login", "/logout")
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll();
The URL /login is set by WebUIConfigurationAdapter so it is then excluded by RestConfigurationAdapter.
Thanks #PraveenKumarLalasangi for his comment.
I have a Spring Boot project using Spring Security and OAuth2.0 libraries.
I confirmed when I accessed http://localhost:8080/login, the default login view is displayed and I could log in.
But if I implement Config class shown below:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/login**" ).permitAll()
.anyRequest()
.authenticated();
}
}
The browser returns "404 not found error".
I thought I could be authenticated directly accessing to a Github's API, so implemented this, but it Wouldn't work:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(){
return "redirect:/oauth2/authorization/github";
}
How should I cope with that? I know I probably have some misunderstandings because of a lack of my knowledge,
but I only want to know how to allow access to only a login page when a user is not authenticated.
Thanks.
finally solved!
I need to append following parts to the end of the methods flow.
.and()
.oauth2Login()
.loginPage("/login");
so the final code is:
SecurityConfig.java
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated()
//appended
.and()
.oauth2Login()
.loginPage("/login");
}
}
Controller.java
#GetMapping(value = "/login")
public String getit(Model model){
return "login";
}
login.html
<body>
<a th:href="#{/oauth2/authorization/github}">Login</a>
</body>
It worked!
Referenced URL:https://www.codeflow.site/ja/article/spring-security-5-oauth2-login
I am trying to authenticate and authorize using role base in spring using hibernate in java configuration. When I run the server then it gives me error sarying no mapping found for the URI in servlet.
public class ShoppingInitializerWeb implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(ShoppingServletConfig.class);
ctx.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/*");
}
}
ShoppingServletConfig.java
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = "com.project.shopping")
public class ShoppingServletConfig extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/ui/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
WebConfigStatic.java
#EnableWebMvc
#Configuration
#ComponentScan(basePackages="com.project.shopping")
public class WebConfigStatic extends WebMvcConfigurerAdapter{
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**").addResourceLocations("/ui/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/ui/css/");
registry.addResourceHandler("/*.html/**").addResourceLocations("/ui/view/");
}
}
This is my controller:
#Controller
#RequestMapping("/")
public class UserController {
#RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "welcome";
}
And I have got welcome.jsp in webapp/WEB-INF/ui/view
Here is the error in console:
WARNING: No mapping found for HTTP request with URI [/Shoping/home] in DispatcherServlet with name 'dispatcher'
I looked up all the related errors and tried solving but couldn't get it solved.
I have faced a same project in my Spring project and I tried every possible way but no solution didn't work for that project however, finally I got a solution.
After adding maven dependencies into project deployment assembly, my project worked perfectly. So, you can try with below procedures, it should work if your code is perfect.
Right click on Project then select Properties > Deployment Assembly >
then click add button > Java Build path entries > Select Maven
dependencies > click Finish button.
then update maven project, and then mvn clean install ... then run.
I hope, it would work.