I'm really new to spring and that's why it can be very stupid question, but I got troubled with serving static files. I'm creating a REST api for library app and have some logic when user tries to add a book:
I get principal user from SecurityContextHolder.
I add book and add book to user's list of books
I read the bytes from base64 encoded string and write it to pdf file, stored in /resources/static
And that works. But I don't know how to get this file. I tried to do next:
I made ResourceConfig class that extends WebMvcConfigurer, but it's not worked:
#Configuration
public class ResourceConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations(StaticAbsolutePath.getPath());
}
}
Oh, the StaticAbsolutePath.getPath() is the metod I made to get path to static directory:
public class StaticAbsolutePath {
private static final String path = "A:\\java\\projects\\books\\src\\main\\resources\\static";
public StaticAbsolutePath() {
}
public static String getPath() {
return path;
}
}
I decided that my security config is blocking this path cuz I'm not authorized, so I added this to config class:
http.authorizeRequests().antMatchers("/static/**").permitAll();
But it'a also was useless. When I try to serf to http://localhost:8080/static/1252356147.pdf, it says that "Whitelabel Error Page".
And here is the screen of resources directory:
So if you know what can be the problem, please tell me, I'd really apreciate it!
Here is the full code of SecurityConfig:
#Configuration #EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/api/login");
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/login/**").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/users/").authenticated();
http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/user/current").authenticated();
http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/books/**").authenticated();
http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/books/**").permitAll();
http.authorizeRequests().antMatchers(HttpMethod.PUT, "/api/books/**").authenticated();
http.authorizeRequests().antMatchers("/static/**").permitAll();
http.addFilter(customAuthenticationFilter);
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
UPDATE
I understood that in resources/staic directory should be stored files like HTML and CSS thanks to #Akhil. And I also added the lines of code that he suggested. So my ResourceConfig class now looks like this:
private static final String[] CLASS_PATH_RESOURCE_LOCATIONS = {
"A:\\downloads\\"
};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/pdf/**")
.addResourceLocations(CLASS_PATH_RESOURCE_LOCATIONS)
.setCacheControl(CacheControl.noCache().cachePrivate())
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
And I changed the directory to store user uploaded files:
But it still not working :(
Full project structure:
Firstly, static files mean files that rarely change for example HTML, CSS files, etc. In your case, the user can upload a pdf file anytime that indicates it's not a static file. What I would suggest is to use a different directory to store PDF and give that path in your resourceConfig. However, if you want to store it in your /resources/static directory then you can do something like this.
ResourceConfigs:
#Configuration
#EnableWebMvc
public class ResourceConfigs implements WebMvcConfigurer {
private static final String[] CLASS_PATH_RESOURCE_LOCATIONS = {
"A:\\downloads\\pdfFiles\\"
};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pdf/**")
.addResourceLocations(CLASS_PATH_RESOURCE_LOCATIONS)
.setCacheControl(CacheControl.noCache().cachePrivate())
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
With this configuration, you can have links like this,
http://localhost:8080/pdf/**
You don't need to configure spring security for this
Related
I'm running a Spring (not Boot) web app and it's finding my JSP views properly, but my CSS isn't being found for some reason. To be clear, the page is loading with the necessary elements, but any CSS styling from classes in my .css files is not being applied. The structure for the CSS file I'm trying to use is src/main/resources/css/style.css.
Below is the relevant code for my AppConfig class:
public class AppConfig implements WebMvcConfigurer {
...
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("/css/");
}
...
}
And the JSP header:
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css"/>
I've already checked some other questions asking after this issue and have had no luck. Several of them advised checking the config file for Spring Security to ensure that permission is granted for all requests using the resource file but based on what I can see, it shouldn't be prohibiting the CSS from being loaded:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(encoder());
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
}
For a non-Spring Boot project your css folder should be not in src/main/resources but in src/main/webapp.
You need to tell spring security to ignore css files by overriding this method in WebSecurityConfigurerAdapter:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**","/vendor/**","/fonts/**").anyRequest();
}
The configuration that you posted in your question:
public class AppConfig implements WebMvcConfigurer {
...
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("/css/");
}
...
}
tells Spring MVC to serve /css/style.css from the css directory in your context directory. If it doesn't exist, you get a 404.
If you want to serve the file from the classpath (which is what your question seems to imply), then you should do this instead:
public class AppConfig implements WebMvcConfigurer {
...
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/resources/css/");
}
...
}
See the documentation of ResourceHandlerRegistry for more details.
As now I've always served my images through a static path in the application.properties file:
spring.resources.staticlocations=file:/Applications/MAMP/htdocs/reportMaker/template
spring.mvc.static-path-pattern=/resources/**
Then by doing
http://localhost:8080/resources/logo.png
I'm able to reach the logo.
Now my aim is to switch with a folder path taken from my DB.
I've tried this approach:
#EnableWebMvc
#Configuration
public class StaticResourceConfiguration implements WebMvcConfigurer {
#Autowired
ConfigurationRepository confRepo;
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
myConfiguration conf = confRepo.findByConfKey("downloadPath");
String path = conf.getConfValue();
if(path !=null) {
registry.addResourceHandler("/resources/**").addResourceLocations(path);
}
}
But I can't reach the logo in same way as before.
The path variable is /Applications/MAMP/htdocs/reportMaker/template.
The path variable is /Applications/MAMP/htdocs/reportMaker/template.
According to this documentation https://www.baeldung.com/spring-mvc-static-resources the path should be prefixed by file:/
I've resolved by removing: #EnableWebMvc and adding a / at the end of my path
#Configuration
public class StaticResourceConfiguration implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("file:/Applications/MAMP/htdocs/reportMaker/template/");
}
}
I have set the context path for tomcat as follows:
#Component
public class CustomContainer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setContextPath("/capripol");
factory.setPort(8080);
}
}
Navigating to localhost:8080/capripol works fine and I am prompted with my login screen, however after logging in my forms and controllers do not append to the context path, so instead of navigating to /capripol/MainMenu etc. they navigate to /MainMenu. How do I set the context path such that my form actions and controllers will be appended do it - why is the tomcat factory context path not setting?
Edit: My Application class
#SpringBootApplication
public class CapripolApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(CapripolApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CapripolApplication.class);
}
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "classpath:/images/")
.setCachePeriod(0);
}
}
}
A few ways to do it. You can add it to each controller, usefully if you want to change the context path
#Controller
#RequestMapping(value = "/foo")
public class bar{
#GetMapping(value = "/bar")
public void stuff(){
//doing stuff
}
}
Or you can put it in your application.properties / yml
server.servlet.contextPath=/foo/*
There are technically some other more round about ways to do it, especially if you are using an older version of Spring, but I would think the application properties is what you are looking for.
Is it possible to configure Spring security in a way that it reads configuration details from an external file and configures accordingly ?
(I am not talking about changing config at runtime, I am talking about reading from a file at the time of startup).
An example of my existing Spring security config :
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
As you can see, I am using multiple configurations (have a look at Order() annotation). What I want to be able to do is decide at the time of startup, the number and types of configuration. For example a first client may want to have 2 configs (e.g.LdapConfig and SamlConfig), a second one may want LdapConfig and SqlConfig and a third one may want 4-5 configs. Is it possible to do that?
NOTE: I am not using Spring Boot
EDIT
Summary of why I want in this way :
By customer I mean the company that will be buying my product. And by users I mean the actual end users of the company that bought my product. So I shipped the product to 3 companies. First will configure it to have ldap auth flow and google-oauth2 auth flow. Users of this first company will be seeing a login page with these 2 options. Company 2 now might have a ldap auth flow and saml auth flow and users of that company will be seeing those 2 options. And the company is selecting the available options before startup.
You could load properties, e.g. DB credentials, before creating your WebApplicationContext. Look at the following example:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Tell the EnvironmentManager to load the properties. The path to the config
// file is set by Tomcat's home variable. If you change the container you might
// need to change this, too.
EnvironmentParamManager.initialize(System.getProperty("catalina.home"));
// now create the Spring Context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
rootContext.setServletContext(servletContext);
SpringApplicationContextProvider.configure(rootContext);
// ... other config
}
The EnvironmentParamManager could look like this. I've decided to make it static so that the properties are accessible from everywhere even in non-Spring parts of the application.
public class EnvironmentParamManager {
private static Properties properties = new Properties();
public static void initialize(String pathToConfigFile) {
BufferedInputStream stream;
try {
stream = new BufferedInputStream(new FileInputStream(
pathToConfigFile + "myconfig.props"));
properties.load(stream);
stream.close();
} catch (Throwable e) {
throw new Error("Cannot read environment settings from file " + pathToConfigFile);
}
}
public static String getMongoDBHostname() {
return properties.getProperty("mongodb.username");
}
}
When using JavaConfig, you can access your config properties at the Bean creation phase easily like this
#Configuration
public class CoreConfig {
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
...
ServerAddress address = new
ServerAddress(EnvironmentParamManager.getMongoDBHost(),
EnvironmentParamManager.getMongoDBPort());
...
}
Of course, you are free to connect to any other services like LDAP etc. in just the same way as you load the local properties file before the Spring Context is bootstrapped. Hope that helps.
Selective loading of components can be achived with Springs #Conditional annotation.
The configs would look like this:
#Configuration(value = "some.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class SomeSecurityConfig {
// some code
}
#Configuration(value = "other.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class OtherSecurityConfig {
// other code
}
Then, the LoadSecurityConfigCondition.class decides if the components are loaded:
#Component
public class LoadSecurityConfigCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
boolean enabled = false;
if (metadata.isAnnotated(Configuration.class.getName())) {
final String name = (String) metadata.getAnnotationAttributes(Configuration.class.getName()).get("value");
if (StringUtils.isNotBlank(name)) {
/* Here you may load your config file and
* retrieve the information on wether to load
* the config identified by its name.
*/
enabled = ...;
}
}
return enabled;
}
}
In this example, the config entries can now be created with the #Configuration name, postfixed with .enabled to clarify its purpose:
some.security.config.enabled=true
other.security.config.enabled=false
Have you tried this:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new MemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Profile({"profile1", "profile2"})
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Profile("profile1")
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
So with spring.profiles.active=profile1, both configurations are loaded, with spring.profiles.active=profile2, only the first configuration is loaded. Of course, you can use more than 2 profiles, and you can also activate more than one profile at startup (also comma separated). You just need to divide your configurations and profiles in a way that fits your requirements.
I'm fairly new at the whole Spring Security thing, and I'm trying to set up a website that uses pre-authentication. I have followed some examples and tutorials and I think it's going well. But i have stucked on one thing.
When i navigate through the .jsp pages everything works fine. I can access the "public" pages, and i get "Access Denied" when i try to access the "private" pages (I haven't finished the filter for private pages).
But if I check the logs when navigating I get the same error everytime accessing a page:
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
The thing is, I have a Provider for this, or at least I think so.
SecurityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Bean
public AccessDecisionManager accessDecisionManager()
{
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(new RoleVoter());
return new AffirmativeBased(decisionVoters);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
DlaUserDetailsService userDetailsService = new DlaUserDetailsService();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
auth.authenticationProvider(provider);
}
#Override
public void configure(HttpSecurity http) throws Exception
{
DlaSpringMvcFilter filter = new DlaSpringMvcFilter();
AuthenticationManager authenticationManager = this.authenticationManager();
filter.setAuthenticationManager(authenticationManager);
http.addFilter(filter).authorizeRequests()
.antMatchers("/private/**").hasRole("ADMIN")
.antMatchers("/public/**").permitAll();
http.csrf().disable();
}
}
What have I done wrong here? Am I missing something?