Or more specifically, is it possible?
We currently have our users in memory using XML configuration. We know of InMemoryUserDetailsManager, but unfortunately it's not possible to get all users and users map inside InMemoryUserDetailsManager is private.
Yes you can actually access that using a bit a jugglery with Java reflection where you can access the properties which are not exposed via public API.
I have used below the RelectionUtils from Spring which should be available if you are using Spring ( which you are since it's Spring security).
The key is to get hold of AuthenticationManager via Autowiring and then drill down to the required Map containing the user info.
Just to demonstrate I have tested this on Spring Boot App but there should not be any issue if your are not using it.
#SpringBootApplication
public class SpringBootSecurityInMemoryApplication implements CommandLineRunner {
#Autowired AuthenticationManager authenticationManager;
................................
...............................
public static void main(String[] args) {
SpringApplication.run(SpringBootSecurityInMemoryApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
introspectBean(authenticationManager);
}
public void printUsersMap(Object bean){
Field field = ReflectionUtils.findField(org.springframework.security.authentication.ProviderManager.class, "providers");
ReflectionUtils.makeAccessible(field);
List listOfProviders = (List)ReflectionUtils.getField(field, bean);
DaoAuthenticationProvider dao = (DaoAuthenticationProvider)listOfProviders.get(0);
Field fieldUserDetailService = ReflectionUtils.findField(DaoAuthenticationProvider.class, "userDetailsService");
ReflectionUtils.makeAccessible(fieldUserDetailService);
InMemoryUserDetailsManager userDet = (InMemoryUserDetailsManager)(ReflectionUtils.getField(fieldUserDetailService, dao));
Field usersMapField = ReflectionUtils.findField(InMemoryUserDetailsManager.class, "users");
ReflectionUtils.makeAccessible(usersMapField);
Map map = (Map)ReflectionUtils.getField(usersMapField, userDet);
System.out.println(map);
}
I have 2 users configured - shailendra and admin. You can see the output of program below. You can get the required info from this map.
{shailendra=org.springframework.security.provisioning.MutableUser#245a060f, admin=org.springframework.security.provisioning.MutableUser#6edaa77a}
Related
I'm writing some integration tests for my Spring MVC Controller.
The controllers are secured by Spring Security.
This is the test class I currently have:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = GuiBackendApplication.class
)
#AutoConfigureMockMvc
public class ConfigEditorControllerIntegrationTest {
#Autowired
private MockMvc mockedMvc;
#Test
#WithMockUser(username = "user", password = "password", roles = {"admin"})
public void adminsCanAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isOk());
}
}
This test class ensures that admins can access my endpoint. It works fine.
BUT what if I want to test if ONLY users with the admin role can access my endpoint?
I could write a test that uses #WithMockUsers with all the roles I currently have except the admin role. But that would me awful to maintain. I want my test to ensure that only users with the admin role can access my endpoint, regardless of any new roles.
I checked the Spring Reference Docs and didn't find anything about that. Is there a way to achieve that?
Something like this
#Test
#WithMockUser(username = "user", password = "password", roles = {"IS NOT admin"})
public void nonAdminsCannotAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isUnauthorized());
}
Spring Security does not know what roles does your system define. So you have to tell it and test it one by one if you want to have 100% test coverage for all the available roles.
You can do it easily and in a maintenance way by using JUnit 5 's #ParameterizedTest and configuring MockMvc with the UserRequestPostProcessor with different roles.
Something like :
public class ConfigEditorControllerIntegrationTest {
#ParameterizedTest
#MethodSource
public void nonAdminsCannotAccessRuntimeConfig(String role) throws Exception {
mockedMvc.perform(get("/my/custom/api")
.with(user("someUser").roles(role)))
.andExpect(status().isUnauthorized());
}
static List<String> nonAdminsCannotAccessRuntimeConfig() {
return Roles.exclude("admin");
}
}
And create a class to maintain all the available roles :
public class Roles {
public static List<String> all() {
return List.of("admin", "hr", "developer" , "accountant" , .... , "devops");
}
public static List<String> exclude(String excludeRole) {
List<String> result = new ArrayList<>(all());
result.remove(excludeRole);
return result;
}
}
I'm trying to get a repository into a class annotated with #Service using the #Autowired annotation in a Spring-boot class. However, the repository turns up as a null.
Here's the relevant code:
#Service
public class ImportLicenses {
#Autowired
private LicenseRepository licenseRepository;
public static void main (String[] args) {
ImportLicenses importLicenses = new ImportLicenses();
importLicenses.listFiles("/Users/admin/Licenses/licenses");
}
private void processLicense (Path path) {
try {
count++;
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
FileTime fileTime = attr.lastModifiedTime();
Permission permission = new Permission(readLineByLineJava8(path.toFile().getAbsolutePath()));
LicensePojo licensePojo = new LicensePojo(permission, path);
Optional<License> licenseOptional = licenseRepository.findById(licensePojo.getId());
at which point it gets an NPE since licenceReposity is null.
I am able to access the licenseRepository in a controller class with this constructor
public LicenseController(LicenseRepository licenseRepository,
UserRepository userRepository) {
this.licenseRepository = licenseRepository;
this.userRepository = userRepository;
}
However, since I'm calling the constructor directly in the static main method, this doesn't seem like it's available. What's the best way to get the repository into this class?
Edit: Thanks for the responses. To clarify the question, I'm trying to pull this class into the structure of the existing Spring Boot application, instead of creating a separate one.
Option 1: Create a button or menu selection on the UI, and create a new controller class to run the import. This would be the simplest, but I don't want to necessarily have that on the UI.
Option 2: Code the import class create another Spring Application
#SpringBootApplication
public class ImportLicenses implements ApplicationRunner {
private final Logger logger = LoggerFactory.getLogger(LicenseGenApplication.class);
#SpringBootApplication
public static void main() {
main();
}
public static void main(String[] args) {
SpringApplication.run(ImportLiceneses.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
listFiles("/Users/admin//licenses");
}
public void listFiles(String path) {
try {
Files.walk(Paths.get(path))
.filter(ImportLicenses::test)
.forEach(p -> processLicense(p));
} catch (IOException e) {
e.printStackTrace();
}
}
....
}
Option 3 - Create a non-executable jar file from the existing application for use in the new application to avoid duplicating code.
Option 1 is the quickest, I'm not sure if option 2 work, Option 3 I'll take a look at to see if it's do-able.
Your application is a usual Java application. It is not a Spring Boot application.
What should you do? Make a Spring Boot application of it. For instance, create a demo app at https://start.spring.io/, compare your main class to the main class in the demo application, then adjust your main class correspondingly. Also compare your Maven or Gradle config to the config of the demo app and adjust correspondingly.
At least, your application should have an annotation #SpringBootApplication. Also, it should be launched via SpringApplication.run(...). This is a minimum. Depending on what you need, you may want to use #EnableAutoConfiguration and other configuration options.
I am trying configuring multiple couchbase data source using springboot-data-couchbase.
This is a way I tried to attach two couchbase sources with 2 repositories.
#Configuration
#EnableCouchbaseRepositories("com.xyz.abc")
public class AbcDatasource extends AbstractCouchbaseConfiguration {
#Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList("ip_address_of_couchbase");
}
//bucket_name
#Override
protected String getBucketName() {
return "bucket_name";
}
//password
#Override
protected String getBucketPassword() {
return "user_password";
}
#Override
#Bean(destroyMethod = "disconnect", name = "COUCHBASE_CLUSTER_2")
public Cluster couchbaseCluster() throws Exception {
return CouchbaseCluster.create(couchbaseEnvironment(), "ip_address_of_couchbase");
}
#Bean( name = "BUCKET2")
public Bucket bucket2() throws Exception {
return this.couchbaseCluster().openBucket("bucket2", "somepassword");
}
#Bean( name = "BUCKET2_TEMPLATE")
public CouchbaseTemplate newTemplateForBucket2() throws Exception {
CouchbaseTemplate template = new CouchbaseTemplate(
couchbaseClusterInfo(), //reuse the default bean
bucket2(), //the bucket is non-default
mappingCouchbaseConverter(), translationService()
);
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
#Override
public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) {
baseMapping
.mapEntity(SomeDAOUsedInSomeRepository.class, newTemplateForBucket2());
}
}
similarly:
#Configuration
#EnableCouchbaseRepositories("com.xyz.mln")
public class MlnDatasource extends AbstractCouchbaseConfiguration {...}
Now the problem is there is no straight forward way to specify namespace based datasource by attaching different beans to these configurations like in springdata-jpa as springdata-jpa support this feature do using entity-manager-factory-ref and transaction-manager-ref.
Due to which only one configuration is being picked whoever comes first.
Any suggestion is greatly appreciated.
Related question: Use Spring Data Couchbase to connect to different Couchbase clusters
#anshul you are almost there.
Make one of the Data Source as #primary which will be used as by default bucket.
Wherever you want to use the other bucket .Just use specific bean in your service class with the qualifier below is the example:
#Qualifier(value = "BUCKET1_TEMPLATE")
#Autowired
CouchbaseTemplate couchbaseTemplate;
Now you can use this template to perform all couch related operations on the desired bucket.
I am not able to read a property from properties file through Spring Boot. I have a REST service which is working through the browser and Postman both and returning me a valid 200 response with data.
However, I am not able to read a property through this Spring Boot client using #Value annotation and getting following exception.
Exception:
helloWorldUrl = null
Exception in thread "main" java.lang.IllegalArgumentException: URI must not be null
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.springframework.web.util.UriComponentsBuilder.fromUriString(UriComponentsBuilder.java:189)
at org.springframework.web.util.DefaultUriTemplateHandler.initUriComponentsBuilder(DefaultUriTemplateHandler.java:114)
at org.springframework.web.util.DefaultUriTemplateHandler.expandInternal(DefaultUriTemplateHandler.java:103)
at org.springframework.web.util.AbstractUriTemplateHandler.expand(AbstractUriTemplateHandler.java:106)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:612)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
at com.example.HelloWorldClient.main(HelloWorldClient.java:19)
HelloWorldClient.java
public class HelloWorldClient {
#Value("${rest.uri}")
private static String helloWorldUrl;
public static void main(String[] args) {
System.out.println("helloWorldUrl = " + helloWorldUrl);
String message = new RestTemplate().getForObject(helloWorldUrl, String.class);
System.out.println("message = " + message);
}
}
application.properties
rest.uri=http://localhost:8080/hello
There are several problems in your code.
From the samples you posted, it seems that Spring is not even started. The main class should run the context in your main method.
#SpringBootApplication
public class HelloWorldApp {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApp.class, args);
}
}
It isn't possible to inject a value into a static field. You should start with changing it into a regular class field.
The class must be managed by Spring container in order to make value injection available. If you use default component scan you can simply annotate the newly created client class with the #Component annotation.
#Component
public class HelloWorldClient {
// ...
}
If you don't want to annotate the class you can create a bean in one of your configuration classes or your main Spring Boot class.
#SpringBootApplication
public class HelloWorldApp {
// ...
#Bean
public HelloWorldClient helloWorldClient() {
return new HelloWorldClient();
}
}
However, if you are the owner of the class, the first option is preferable. No matter which way you choose, the goal is to make the Spring context aware of class existence so the injection process can happen.
I have configured Spring Mongodb project to load data in database named "warehouse". Here is how my config class looks like
#Configuration
public class SpringMongoConfig extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "warehouse";
}
public #Bean Mongo mongo() throws Exception {
return new Mongo("localhost");
}
public #Bean MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), getDatabaseName());
}
}
But Spring is always using the default database "test" to store and retrieve the collections. I have tried different approaches to point it to "warehouse" db. But it doesnt seem to work. What am doing wrong? Any leads are appreciated.
Assuming you have a standard mongo install (e.g., the database is at a default such as /data/db or C:\data\db), your configuration class looks correct. How are you using it? Can you try:
SpringMongoConfig config = new SpringMongoConfig();
MongoTemplate template = config.mongoTemplate();
template.createCollection("someCollection");
From a shell, if you then log into mongo and enter show dbs, do you not see a warehouse"?