I have the following configuration placed in /src/main/java/com/dog/bootstrap:
#EnableWebSecurity
#Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("hello");
auth.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
}
and I am loading it as follows:
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.scan("com.dog.bootstrap");
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(dispatcherContext));
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
Set<String> mappingConflicts = dispatcher.addMapping("/");
if (!mappingConflicts.isEmpty()) {
throw new IllegalStateException("'dispatcher' could not be mapped to '/' due " +
"to an existing mapping.");
}
}
My controller:
#Controller
public class DogController {
#RequestMapping(value = {"/dog"}, method = RequestMethod.GET)
#ResponseBody
public String getSource(#PathVariable("domain") String domain) throws Exception {
return "dogs";
}
}
When I startup my app, I do see hello being printed, so configure(AuthenticationManagerBuilder auth) is being called. However, none of my endpoints are requiring me to enter a login page. When I go to localhost:8080/dog it outputs dogs without asking me to authenticate myself.
You're not actually including the filter chain, as described in the last step of this guide. Try adding this default initializer, which maps to /*:
#Component public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
Related
I implemented a OncePerRequestFilter, where in the doFilterInternal() I would like to use an utilization class, that used JdbcTemplate and user data from a properties file. I realized that it couldn't reach the data from the properties file (database connection and variables) and has null value all the time. As I found on the internet it's, because of the different context.
I could successfully setup a new jdbc datasource locally, but I wouldn't like to duplicate the code, so I would like to inject simply the sources the same way as I did everywhere else like in RestControllers (#Value, #Autowired).
Any idea, how could I inject these in my utilization class that will be used in the servlet filter or directly in my filter?
Thank you!
UPDATE - code snippets:
In the RestController, the injection of JdbcTemplate works properly, but in the filter I cannot inject it, always throws nullPointerException.
#SpringBootApplication
public class AsdApplication {
public static void main(String[] args) {
SpringApplication.run(AsdApplication.class, args);
}
public static class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegateFilterProxy = new DelegatingFilterProxy();
delegateFilterProxy.setTargetBeanName("MyFilter");
return new Filter[] { delegateFilterProxy };
}
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return null;
}
}
}
#RestController
public class RestCtrl {
#Autowired
private JdbcTemplate jdbcTemplate;
#GetMapping("/test")
public ResponseEntity<String> getTest() {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in ctrl: " + result);
return new ResponseEntity<>("asd ad asd asd asd", HttpStatus.OK);
}
}
#Component(value = "MyFilter")
public class MyFilter extends OncePerRequestFilter {
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in filter: " + result);
User currentUser = new User("username", "password", new ArrayList<>());
UsernamePasswordAuthenticationToken authenticatedUser = new UsernamePasswordAuthenticationToken(
currentUser, null, currentUser.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
filterChain.doFilter(request, response);
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
spring.datasource.url=jdbc:<sqlserver>
spring.datasource.username=<user>
spring.datasource.password=<pass>
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
As you are actually using Spring Boot and want to make it part of the Spring Security filter chain (which is something different!) what you need to do is
Create an #Bean method to create the filter and make it a bean
Create an #Bean method and add a FilterRegistration bean to prevent the bean from being registered as a filter by Spring Boot
Configure Spring Security.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistationBean() {
FilterRegistationBean frb = new FilterRegistrationBean(myFilter());
frb.setEnabled(false);
return frb;
}
Finally remove the #Component from your MyFilter as you don't need it and it would create an additional instance. All prior changes (like the ApplicationInitializer etc. you can remove.
NOTE: As you are using Spring Security and somehow use this for authentication, instead of extending OncePerRequestFilter I suggest you extend the Spring Security AbstractAuthenticationProcessingFilter which integrates better with Spring Security (like fireing events for authentication, logging etc.).
I see you are creating a new instance of MyFilter instead of using the one managed by Spring with #Component(value = "MyFilter")
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
Hence you will hit a NPE since jdbcTemplate is null. You can inject the instance managed be Spring instead of creating a new one.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("MyFilter")
private MyFilter myFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
You should use this:
Through this class you can get different Spring Boot Beans in a non Bean class.
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
Then after creating it, get your bean this way:
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
// Here you get your dependency
ARequiredClass dependency = appCtx.getBean(ARequiredClass.class);
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 am new to spring security and I was following this example on configuring spring security : https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/. So I saw that they use this method to let the spring know for the configuration.
public class SpringWebMvcInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { HelloWebSecurityConfiguration.class };
}
...
}
But I have app initialization like this:
public class AppInit implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
// TODO Auto-generated method stub
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfiguration.class);
ServletRegistration.Dynamic registration =
servletContext.addServlet("dispatcher", new DispatcherServlet(context));
registration.setLoadOnStartup(1);
registration.addMapping("/services/rest/*");
}
}
And I want to include my spring security configuration there, as without it I get message in browser: Your login attempt was not successful, try again.
Reason: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Extend from AbstractAnnotationConfigDispatcherServletInitializer is a way to make spring to load the security config, but I don't use it. A more convinient way to accomplish this can be like this(decalare the dependency of spring security in pom.xml first):
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER")
.and().withUser("admin").password("admin").roles("USER","ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/").hasRole("USER")
.antMatchers("/index").hasRole("USER")
.antMatchers("/message/*").hasRole("USER")
.anyRequest().permitAll()
.and().formLogin().loginPage("/login").defaultSuccessUrl("/index").failureUrl("/login?error").permitAll()
.and().rememberMe().tokenValiditySeconds(60*60*7).key("message")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll();
// define your action here.
}
}
Spring will load this config automatically on startup for you, this is enough for spring security to work. As you see, you should define the rules in configure(HttpSecurity http) to tell spring security what to do when a request is coming.
You can just register your security config in your AppInit class by changing the line
context.register(AppConfiguration.class);
to
context.register({HelloWebSecurityConfiguration.class, AppConfiguration.class});
I'm running a SpringBoot application with a the root class being annotated with #SpringBootApplication, #EnableAutoConfiguration.
I've created a UserRepositoryInterface interface, which extends the CrudRepository interface with my User JPA object. There is no implementation for this interface and there was no need for one yet. Neither are there any configuration files for anything in this Application. Except for the JPA DB connection, but that works.
public interface UsersRepositoryInterface extends CrudRepository<User, Long> {
// Query to search for users via email
List<User> findByEmail(#Param("email") String email);
}
And I've succesfully Autowired it into some REST endpoints. The problem appears, when I try to Autowire it into my security classes. I'm trying for authentication with JWT and it works. Now I want to call the database during the log-in process and I'm having an issue. Here are the classes:
First the WebSecurityConfiguererAdapter class, where I add the paths to the filter. Note the line with "new JWTLoginFilter", which is the class where I try to Autowire:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsServ;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
//Allow options pre-flight request
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// Allow POST request to /login
.antMatchers(HttpMethod.POST, "/login").permitAll()
// Others must be authenticated
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Change logging in from username+password to email+password
auth.userDetailsService(userDetailsServ);
}
}
And the JWTLoginFilter class. I ommited some irrelevant code:
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UsersRepositoryInterface userRepo;
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(
HttpServletRequest req, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
//Check if userRepo is injected
if(userRepo == null) {
System.out.println("Null");
}
AccountCredentials creds = new ObjectMapper()
.readValue(req.getInputStream(), AccountCredentials.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
Collections.emptyList()
)
);
}
}
The println in JWTLoginFilter will always return Null, when called upon.
Am I missing something?
SOLVED IT:
Works now.
Annotated the JWTLoginFilter with
#Component("someName")
And injected it in the WebSecurityConfig with
#Resource(name="someName")
private JWTLoginFilter myFilter;
Hardcoded the URL in the JWTLoginFilter constructor, but I still had to Autowire the AuthenticationManager from the WebSecurityConfig into the JWTLoginFilter.
First had to make the AuthenticationManager a Bean. Used the answer here: How To Inject AuthenticationManager using Java Configuration in a Custom Filter
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Then injected it with answer here: Spring Security authenticationmanager must be specified - for custom filter
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
While removing the
setAuthenticationManager(authManager);
in the constructor in JWTLoginFilter
Well, what do you expect? You are creating the JWTLoginFilter via the new keyword. Spring does not do any wiring here at all. You should make this filter a #Bean or #Component or whatever else to make it a spring bean and inject it into WebSecurityConfig somehow.
I Hope your problem has been resolved already. But I am adding a sample code snippet for reference for those who are facing the same issue.
When our filter contains Autowired dependencies, and instantiating our filter in spring configuration using new() would not autowire its dependencies as this won't be a string managed bean. Hence we need to autowire our filter in spring application configuration class , which in turn autowire its internal dependencies.
PFB the sample code for reference.
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
#Autowired
private MyFilter myFilter;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
#Bean
public FilterRegistrationBean myFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(myFilter);
registration.addUrlPatterns("*");
return registration;
}
}
Filter:
#Component
public class MyFilter extends BaseFilter {
#Autowired
private EmployeeRepository employeeRepository;
//your code goes here
if(employeeRepository != null) {
System.out.println("employeeRepository is not null");
}
}
I'm trying to build a Restful webapp using Spring MVC 4.
But I'm got this error "Ambiguous handler methods mapped for HTTP".
In my controller I have this endpoints:
GET /people/
GET /people/{id}
POST /people/
DELETE /peope/{id}
PUT /people/{id}
I think this error occurs because I have something wrong in my configuration.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.myapp.rest.controller" })
public class MVCConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/site/**").addResourceLocations("/WEB-INF/site/**");
}
}
And this
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
"DispatcherServlet", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/*");
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.myapp.config");
return context;
}
}
Thank you all