Is it possible to configure spring security in a way that it reads configuration details from external file and configures accordingly.
(I am not taking about changing config at runtime. I am talking about reading from a file at the time of startup)
An example of my existing Sporing security config is:
@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 number and types of configuration. Example first client may want to have 2 configs e.g.LdapConfig
and SamlConfig
. Other may want LdapConfig
and SqlConfig
and third may want 4-5 configs. Is it possible to do that?
NOTE: I am not using Spring Boot
EDIT
Summary of why I wnat it 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.
5 Answers
Answers 1
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.
Answers 2
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.
Answers 3
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 eample, 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
Answers 4
In order to manage multiple authentication providers you can use AuthenticationManagerBuilder, in this class you will add all the authentication providers you want to use.
@EnableWebSecurity public class MultipleAuthProvidersSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired CustomAuthenticationProvider customAuthProvider; @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customAuthProvider); auth.inMemoryAuthentication() .withUser("memuser") .password("pass") .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic() .and() .authorizeRequests() .antMatchers("/api/**") .authenticated(); } }
Answers 5
Spring Security gives you all of the tools almost out-of-the-box when securing against Java Server Pages: redirection to the login page, redirection to the right controller after user has authenticated, JSP taglibs, etc.
The fact is, there are a lot of modern web applications that rely on accessing back-end web services while implementing a rich and responsive user experience on the front-end, such as AngularJS-based web applications. And things can get a little trickier when authentication is done using web services: no redirection after login, the returned HTTP status codes are not necessarily the right ones… the web services’ client inherits a lot of responsibilities that were previously handled by the back-end.
0 comments:
Post a Comment