Spring Security - #EnableGlobalWebSecurity deprecated to #EnableWebSecurity - CustomMethodSecurityExpressionRoot not loading properly afterwards - java

After upgrading from Spring Boot 2.3.0.RELEASE -> 2.7.8 and upgrading Spring Security from 5.7.6 -> 5.8.1, I only fixed the following deprecation - #EnableGlobalMethodSecurity(prePostEnabled = true) to #EnableMethodSecurity
After making just that one change and re-running my Spock unit tests, I am continually getting the following root exception:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method internalUser(java.lang.String) cannot be found on type org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
For some odd reason, the Spring Security Framework is no longer using my own CustomMethodSecurityExpressionRoot and seems to be defaulting back to its own org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
Here is my CustomMethodSecurityExpressionRoot.java:
package com.wellframe.excalibur.auth.accesscontrol;
import com.wellframe.excalibur.auth.AuthenticationHolder;
import com.wellframe.excalibur.auth.UserType;
import com.wellframe.excalibur.services.GambitService;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
/\*\*
* Custom methods defined here will be accessible by spring security method annotations like #PreAuthorize.
\*/
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements
MethodSecurityExpressionOperations {
private final GambitService gambitService;
public CustomMethodSecurityExpressionRoot(Authentication authentication, ApplicationContext context) {
super(authentication);
gambitService = context.getBean(GambitService.class);
}
public boolean gambitAuthorize(AccessControlPolicy... policies) {
return gambitService.authorizeRequest((AuthenticationHolder) super.getAuthentication(), List.of(policies));
}
public AccessControlPolicy internalUser(String methodName, Object... args) {
return new AccessControlPolicy(UserType.INTERNAL_USER, methodName, args);
}
public AccessControlPolicy careManager(String methodName, Object... args) {
return new AccessControlPolicy(UserType.DASH_USER, methodName, args);
}
public AccessControlPolicy programUser(String methodName, Object... args) {
return new AccessControlPolicy(UserType.MOBILE_USER, methodName, args);
}
// Copied from Spring Security's access/expression/method/MethodSecurityExpressionRoot.java
private Object filterObject;
private Object returnObject;
private Object target;
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
void setThis(Object target) {
this.target = target;
}
#Override
public Object getThis() {
return target;
}
}
Here is my CustomCustomMethodSecurityExpressionHandler.java:
package com.wellframe.excalibur.auth.accesscontrol;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
/\*\*
* Used to provide custom security expressions in the #PreAuthorize annotation.
\*/
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private ApplicationContext context;
public CustomMethodSecurityExpressionHandler() {
String stopHere = "stopHere";
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, this.context);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
super.setApplicationContext(applicationContext);
this.context = applicationContext;
}
}
and finally, here is my MethodSecurityConfig.java:
#EnableMethodSecurity
//#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
#Autowired
ApplicationContext context;
#Bean
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
expressionHandler.setApplicationContext(context);
return expressionHandler;
}
}
The only change that I made was fix the deprecation. #EnableGlobalMethodSecurity(prePostEnabled = true) to #EnableMethodSecurity.
After making that change, and stepping through the code, I realized that the Spring Security framework is no longer calling my #Override method in my CustomMethodSecurityExpressionHandler.java:
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, this.context);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}

Related

Custom Security Expression

I'm studying tutorial how to create custom security expression and I created threes classes but I got error, I tried google everything, may be I am not updated or some. Can you explain what's going on?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Failed to evaluate expression 'isComprador()'] with root cause
Method call: Method isComprador() cannot be found on type org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
MethodSecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new CustomMethodSecurityExpressionHandler();
}
}
CustomMethodSecurityExpressionHandler:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
CustomMethodSecurityExpressionRoot:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
void setThis(Object target) {
this.target = target;
}
#Override
public Object getThis() {
return target;
}
//
public boolean isComprador() {
final Usuario usuario = ((UserDetailsImpl) this.getPrincipal()).getUsuario();
return usuario.getPerfil() == Perfil.COMPRADOR;
}
public boolean isVendedor() {
final Usuario usuario = ((UserDetailsImpl) this.getPrincipal()).getUsuario();
return usuario.getPerfil() == Perfil.VENDEDOR;
}
}
Thanks!
Att,
Carlos Oliveira
I'd really recommend using a custom bean rather than trying to integrate into the expression root. This is much easier to configure, decouples your code from Spring Security you just create a simple pojo, and allows your code to be more focused.
To use this approach start by creating a Spring Bean:
#Component
public class Authz {
public boolean isComprador() {
// Authentication is the currently logged in user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && "comprador".equals(authentication.getName());
}
}
Then you can refer to methods in the Bean using #beanName.methodName. In our case, the Bean name is authz and our method is isComprador so the following would work:
#Service
public class MessageService {
// we pass in the name argument into our custom expression Authz.isComprador
#PreAuthorize("#authz.isComprador()")
String greetForName(String name) {
return "Hello " + name;
}
}
Finally we just enable method security like normal:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
}
You can then write a few unit tests to prove that it works:
#SpringBootTest
class DemoApplicationTests {
#Autowired
MessageService service;
#Test
#WithMockUser // run the test as a user with the default username of user
void secureWhenForbidden() {
assertThatCode(() -> service.greetForName("Rob")).isInstanceOf(AccessDeniedException.class);
}
#Test
#WithMockUser("comprador") // run the test as a user with the username of comprador
void secureWhenGranted() {
assertThatCode(() -> service.greetForName("Rob")).doesNotThrowAnyException();;
}
}
You can find a complete sample at https://github.com/rwinch/spring-security-sample/tree/method-security-bean-expression

How would I do a Configuration class with a generics-typed bean? (Example included)

I want to provide a base configuration class that will handle creating beans with a generic type that gets defined when you extend the class as seen below. But it never calls the #Bean methods.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.client.RestTemplate;
import java.lang.reflect.Constructor;
#RunWith( SpringJUnit4ClassRunner.class )
#ContextConfiguration( classes = { TestGenericBean.MyClientCreator.class } )
public class TestGenericBean
{
/* This throws an error saying no bean provided */
#Autowired
private TestClient client;
public static class ClientConfig<T>
{
private Class<T> classCreator;
public ClientConfig(Class<T> classCreator)
{
this.classCreator = classCreator;
}
/* This is never called */
#Bean
public T createClient(RestTemplate restTemplate) throws Exception
{
Constructor<T> constructor = classCreator.getConstructor(
RestTemplate.class
);
return constructor.newInstance( restTemplate );
}
/* This is never called */
#Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
#Configuration
public static class MyClientCreator extends ClientConfig<TestClient>
{
public MyClientCreator()
{
super( TestClient.class );
}
}
public static class TestClient
{
public RestTemplate restTemplate;
public TestClient(RestTemplate restTemplate)
{
this.restTemplate = restTemplate;
}
}
#Test
public void testBean()
{
System.out.print( client.restTemplate );
}
}
Not sure why, but it requires a bean of type MappingJackson2HttpMessageConverter. With that, it works.
#RunWith( SpringJUnit4ClassRunner.class )
#ContextConfiguration( classes = { TestGenericBean.MyClientCreator.class } )
public class TestGenericBean
{
#Autowired
private TestClient client;
public static class ClientConfig<T>
{
private Class<T> classCreator;
public ClientConfig(Class<T> classCreator)
{
this.classCreator = classCreator;
}
/**** THIS IS REQUIRED - WHY? ****/
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter()
{
return new MappingJackson2HttpMessageConverter();
}
#Bean
public T createClient(AsyncRestTemplate asyncRestTemplate) throws Exception
{
Constructor<T> constructor = classCreator.getConstructor(
AsyncRestTemplate.class
);
return constructor.newInstance( asyncRestTemplate );
}
#Bean
public AsyncRestTemplate asyncRestTemplate()
{
return new AsyncRestTemplate();
}
}
#Configuration
public static class MyClientCreator extends ClientConfig<TestClient>
{
public MyClientCreator()
{
super( TestClient.class );
}
}
public static class TestClient
{
public AsyncRestTemplate asyncRestTemplate;
public TestClient(AsyncRestTemplate asyncRestTemplate)
{
this.asyncRestTemplate = asyncRestTemplate;
}
}
#Test
public void testBean()
{
System.out.print( client.asyncRestTemplate );
}
}
I haven't used Spring / Spring boot a lot lately but i have a lot of experience with annotation preprocessors. The way spring boot is using them cannot allow any generic method to be used as a bean provider for #autowire. This is because it cannot safely resolve the java class < T > and therefore map it with the field. I would suggest you try using a wrapper class such as Getter< T > or List< T > but can't guarantee that any generic implementation will work.

Spring Security seems to be ignoring a certain service

I am trying to implement Method Security using #PreAuthorize.
Spring Version: 4.2.3.Release
Spring Security: 4.0.3.Release
I have implemented a CustomPermissionEvaluator. I have noticed that it seems to be working fine except for 1 service where the hasPmerission is not called.
I know this because I get the a logging message from hasPermission / or in the erroneous case do not get the log:
public boolean hasPermission(Authentication authentication, Object o, Object o1) {
logger.info("Call to hasPermission with "+o+" and "+o1);
...
}
My Spring configuration is as follows:
#Configuration
#ComponentScan
public class RootConfiguration {
}
MVC Config
#EnableWebMvc
#Configuration
#ComponentScan({"OntoRais.*"})
#PropertySource("classpath:application.properties")
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean(name="multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setDefaultEncoding("utf-8");
commonsMultipartResolver.setMaxUploadSize(50000000);
return commonsMultipartResolver;
}
}
Method Security Config:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Autowired
private CustomPermissionEvaluator permissionEvaluator;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler
= new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(permissionEvaluator);
return handler;
}
public CustomPermissionEvaluator getPermissionEvaluator() {
return permissionEvaluator;
}
public void setPermissionEvaluator(CustomPermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
}
Initializer:
#Configuration
#EnableSpringConfigured
public class MessageWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(org.springframework.web.context.request.RequestContextListener.class);
super.onStartup(servletContext);
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { MvcConfiguration.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new HiddenHttpMethodFilter(),
new OpenEntityManagerInViewFilter(),
new DelegatingFilterProxy("springSecurityFilterChain")
};
}
}
Security Config:
#Configuration
#EnableWebSecurity
#ComponentScan
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
OntoRAISUserDetailsService ontoRAISUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
formLogin().
and().
logout().
and().
authorizeRequests().
antMatchers("/login").permitAll().
anyRequest().authenticated().
and().csrf().disable();
}
#Autowired
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(ontoRAISUserDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(ontoRAISUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public OntoRAISUserDetailsService getOntoRAISUserDetailsService() {
return ontoRAISUserDetailsService;
}
public void setOntoRAISUserDetailsService(OntoRAISUserDetailsService ontoRAISUserDetailsService) {
this.ontoRAISUserDetailsService = ontoRAISUserDetailsService;
}
The Service in question:
#Service
public class StakeholderService {
#Autowired
private OntopManager om;
private static final Logger logger = LoggerFactory.getLogger("OntoRais");
public OntopManager getOm() {
return om;
}
public void setOm(OntopManager om) {
this.om = om;
}
#PreAuthorize("hasPermission(#stakeholderType, 'Create_StakeholderType')")
public void createStakeholderType(StakeholderType stakeholderType) {
try {
logger.info("Create stakeholder type in service layer");
List<OBDADataSource> sources = om.getObdaModel().getSources();
OBDAMappingAxiom mapping = om.getObdaModel().getMapping(new URI("genertatedURI"), MappingList.StakheholderType());
HashMap<String, String> values = new HashMap<>();
values.put("stakeholderName", stakeholderType.getLabel());
String query = ClassSQLHelper.generateSQLCreateSatement(mapping.getSourceQuery(), values);
SQLHelper.executeSQL(query, sources.get(0));
} catch (URISyntaxException e) {
logger.error(e.getMessage());
}
}
And the controller from which i call the service layer:
#Api(description = "Operations related to Stakeholders")
#RestController
public class StakeholderController {
#Autowired
private OntopManager om;
#Autowired
StakeholderService stakeholderService;
#Autowired
ProjectService projectService;
private static final Logger logger = LoggerFactory.getLogger("OntoRais");
...
/**
* Add a new Stakeholder Type
*
* #param stakeholdertype The new Stakeholder to be added.
* #return
*/
#ApiOperation(value = "Add new stakeholder type",
notes = "",
response = ResponseResource.class,
responseContainer = "Object")
#JsonView(Views.Details.class)
#RequestMapping(value = "/api/stakeholder/types", method = RequestMethod.POST)
public ResponseEntity<List<StakeholderType>> addStakeholderType(#RequestBody StakeholderType stakeholdertype) {
logger.info("Add Stakeholder type in controller");
getStakeholderService().createStakeholderType(stakeholdertype);
return getStakeholderTypes();
}
When calling api/stakeholder/types" with method = POST
This is my debug output:
Add Stakeholder type in controller
Create stakeholder type in service layer
INSERT INTO prefix_http_www_ontorais_de_stakeholdertype(id,stakeholderName) VALUES(DEFAULT,'TESTEWRTERETE');
As you can see the log from hasPermission is not present -> not called.
I can see that the method is called from my other method sercurity annotations in other service objects.
A similar Service which correctly invokes hasPermission as expected just for comparison:
#Service
public class OrganisationService {
private static final Logger logger = LoggerFactory.getLogger("OntoRais");
#Autowired
private OntopManager om;
#Autowired
private ProjectService projectService;
...
#PreAuthorize("hasAuthority('Add_Organisation')")
public void addOrganisation(Organisation organisation) {
List<OBDADataSource> sources = om.getObdaModel().getSources();
OBDAMappingAxiom mapping = null;
try {
mapping = om.getObdaModel().getMapping(new URI("genertatedURI"), MappingList.OrganisationMapping());
} catch (URISyntaxException e) {
e.printStackTrace();
}
HashMap<String, String> valueMap = new HashMap<>();
valueMap.put("organisationName", organisation.getName());
valueMap.put("organisationDescription", organisation.getDescription());
String query = ClassSQLHelper.generateSQLCreateSatement(mapping.getSourceQuery(), valueMap);
SQLHelper.executeSQL(query, sources.get(0));
}
Any hints on what I am doing wrong/missing/am blind for are very welcome thanks.
Benedict
Ok I found the problem, and a solution.
The Problem was that my CustomPermissionEvaluator depended on a method within the StakeholderService. Even though that method was not secured this resulted in the Spring not being able to proxy the object, therefore preventing any security checks.
Even though it is a bad idea to use a service layer object in the PermissionEvaluator, perhaps someone could elaborate on the exact implications, as i am definately not an expert in spring sercurity

Force Jersey to read mocks from JerseyTest

I want to test a Resourse with JerseyTest. I have created the following test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:testApplicationContext.xml")
public class ResourceTest extends JerseyTest
{
#Configuration
public static class Config
{
#Bean
public AObject aObject()
{
return mock(AObject.class);
}
}
#Autowired
public AObject _aObject;
#Test
public void testResource()
{
// configouring mock _aObject
Response response = target("path");
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}
#Override
protected Application configure()
{
return new ResourceConfig(Resource.class).property("contextConfigLocation", "classpath:testApplicationContext.xml");
}
}
My Resource also has an AObject reference with #Autowired annotation.
My problem is that my JerseyTest and the Resource (that is configured by the test) have different instances for the Mock object. In the console I see that the testApplicationContext.xml is loaded twice, once for the test and one for the Resource.
How can I force jersey to use the same mock?
After debugging the jersey-spring3 (version 2.9.1) library it seems that the problem lies in the SpringComponentProvider.createSpringContext
private ApplicationContext createSpringContext() {
ApplicationHandler applicationHandler = locator.getService(ApplicationHandler.class);
ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration().getProperty(PARAM_SPRING_CONTEXT);
if (springContext == null) {
String contextConfigLocation = (String) applicationHandler.getConfiguration().getProperty(PARAM_CONTEXT_CONFIG_LOCATION);
springContext = createXmlSpringConfiguration(contextConfigLocation);
}
return springContext;
}
It checks if a property named "contextConfig" exists in the application properties and if not it initializes the spring application context.
Even if you initialized a spring application context in your tests, jersey will create another context and use that one instead. So we have to somehow pass the ApplicationContext from our tests in the Jersey Application class. The solution is the following:
#ContextConfiguration(locations = "classpath:jersey-spring-applicationContext.xml")
public abstract class JerseySpringTest
{
private JerseyTest _jerseyTest;
public final WebTarget target(final String path)
{
return _jerseyTest.target(path);
}
#Before
public void setup() throws Exception
{
_jerseyTest.setUp();
}
#After
public void tearDown() throws Exception
{
_jerseyTest.tearDown();
}
#Autowired
public void setApplicationContext(final ApplicationContext context)
{
_jerseyTest = new JerseyTest()
{
#Override
protected Application configure()
{
ResourceConfig application = JerseySpringTest.this.configure();
application.property("contextConfig", context);
return application;
}
};
}
protected abstract ResourceConfig configure();
}
The above class will take the application context from our tests and pass it to the configured ResourceConfig, so that the SpringComponentProvider will return the same application context to jersey. We also use the jersey-spring-applicationContext.xml in order to include jersey specific spring configuration.
We cannot inherit from JerseyTest because it initializes the Application in the constructor before the test application context is initialized.
You can now use this base class to create your tests for example
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:testContext.xml")
public class SomeTest extends JerseySpringTest
{
#Autowired
private AObject _aObject;
#Test
public void test()
{
// configure mock _aObject when(_aObject.method()).thenReturn() etc...
Response response = target("api/method").request(MediaType.APPLICATION_JSON).get();
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
}
#Override
protected ResourceConfig configure()
{
return new ResourceConfig(MyResource.class);
}
}
In testContext.xml add the following definition in order to inject a mock AObject.
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.yourcompany.AObject" />
</bean>
I couldn't get the answer https://stackoverflow.com/a/24512682/156477 from #Grigoris working, although his explanation for why it is happening is correct.
In the end I went for the approach below which exposes a special setter to insert the mock object. Not as 'clean' as the approach above, but worth the tradeoff of exposing the apiProvider I wanted to mock so I could write some tests..
public MyAPITest extends JerseyTest {
// Declare instance of the API I want to test - this will be instantiated in configure()
MyAPI myAPI;
#Override
protected ResourceConfig configure()
{
MockitoAnnotations.initMocks(this);
myAPI = new MyAPI();
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(MyAPI).property("contextConfig", new ClassPathXmlApplicationContext("classpath:spring.testHarnessContext.xml"));
return resourceConfig;
}
#Mock
private MyAPIProvider mockAPIProvider;
#Before
public void before() {
myAPI.setMockProvider(mockAPIProvider);
}
#Test
public void test() {
// I can now define the mock behaviours and call the API and validate the outcomes
when(mockAPIProvider....)
target().path("....)
}
}
If anyone is interested in the solution https://stackoverflow.com/a/40591082/4894900 from Kevin for Jersey v1:
public MyAPITest extends JerseyTest {
#InjectMocks
MyAPI myAPI;
#Mock
MyApiService myApiService;
#Override
protected AppDescriptorconfigure()
{
MockitoAnnotations.initMocks(this);
ResourceConfig rc = new DefaultResourceConfig();
rc.getSingletons().add(myAPI);
return new LowLevelAppDescriptor.Builder(rc).contextPath("context").build();
}
#Test
public void test() {
// I can now define the mock behaviours
when(myApiService...)
WebResource webResource = resource().path("mypath");
ClientResponse result = webResource.get(ClientResponse.class);
}
}
Further improvising the accepted solution by removing xml dependency. More details available here.
JerseySpringTest abstracting JerseyTest:
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
/** Run JerseyTest with custom spring context with mocks Mimics Spring #WebMvcTest with #MockBean */
public abstract class JerseySpringTest extends JerseyTest {
#Override
protected ResourceConfig configure() {
MockitoAnnotations.openMocks(this);
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
set(TestProperties.CONTAINER_PORT, "0");
final ResourceConfig resourceConfig =
new ResourceConfig()
.property("contextConfig", createSpringContext(getBeanMap()))
.property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, "WARNING")
.register(getResourceClass());
return serverConfig(resourceConfig);
}
/**
* Gives the test class opportunity to further customize the configuration. Like registering a
* MultiPartFeature if required.
*
* #param config
* #return
*/
protected ResourceConfig serverConfig(final ResourceConfig config) {
return config;
}
/**
* Supplies all the bean objects required to be loaded in the application context for the Resource class
* under test
*
* #return
*/
protected List<Object> getBeans() {
return Collections.emptyList();
}
/**
* Supplies all the bean objects with name qualifier required to be loaded in the application context for the Resource class
* under test
*
* #return
*/
protected Map<String, Object> getQualifiedBeans() {
return Collections.emptyMap();
}
private Map<String, Object> getBeanMap() {
final Map<String, Object> result = new HashMap<>();
CollectionUtils.emptyIfNull(getBeans())
.forEach(obj -> result.put(StringUtils.uncapitalize(obj.getClass().getSimpleName()), obj));
result.putAll(MapUtils.emptyIfNull(getQualifiedBeans()));
return result;
}
/**
* Resource class under test
*
* #return
*/
protected abstract Class<?> getResourceClass();
/**
* Creates & returns a Spring GenericApplicationContext from the given beans with qualified names
*
* #param beans
* #return
*/
public static ApplicationContext createSpringContext(Map<String, Object> beans) {
final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
MapUtils.emptyIfNull(beans).forEach((k, obj) -> beanFactory.registerSingleton(k, obj));
final GenericApplicationContext context = new GenericApplicationContext(beanFactory);
context.refresh();
return context;
}
}
Sample Resource With Test:
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import lombok.RequiredArgsConstructor;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Path("/rest")
#Component
#RequiredArgsConstructor
class RestResource {
private final ServiceXYZ serviceXYZ;
private final ServiceABC serviceABC;
#Qualifier("greeter")
private final String greeterName;
#GET
#Path("/serviceXYZ/greet/{name}")
public Response greetByServiceXYZ(#PathParam("name") final String name) {
return Response.ok(serviceXYZ.greet(name) + ", Regards: " + greeterName).build();
}
#GET
#Path("/serviceABC/greet/{name}")
public Response greetByServiceABC(#PathParam("name") final String name) {
return Response.ok(serviceABC.greet(name)+ ", Regards: " + greeterName).build();
}
}
#Service
class ServiceXYZ {
public final String greet(final String name) {
return "Welcome " + name + " to Hello World!";
}
}
#Service
class ServiceABC {
public final String greet(final String name) {
return "Welcome " + name + " to Hello Universe!";
}
}
class ResourceTest extends JerseySpringTest {
#InjectMocks private RestResource subject;
#Mock private ServiceXYZ serviceXYZ;
#Mock private ServiceABC serviceABC;
// only required to override for custom server config, say if the Resource accepts file input
#Override
protected ResourceConfig serverConfig(final ResourceConfig config) {
return config.register(MultiPartFeature.class);
}
#Override
protected Map<String, Object> getQualifiedBeans() {
return Map.of("greeter", "Amith Kumar");
}
#Override
protected List<Object> getBeans() {
return List.of(subject, serviceXYZ, serviceABC);
}
#Override
protected Class<?> getResourceClass() {
return RestResource.class;
}
// only required to override for custom client config, say if the Resource accepts file input
#Override
protected void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}
#Test
void testServiceXYZGreets() {
// ARRANGE
when(serviceXYZ.greet("foo")).thenReturn("Hello foo");
// ACT
Response output = target("/rest/serviceXYZ/greet/foo").request().get();
// ASSERT
assertAll(
() -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
() -> assertEquals("Hello foo, Regards: Amith Kumar", output.readEntity(String.class)));
}
#Test
void testServiceABCGreets() {
// ARRANGE
when(serviceXYZ.greet("boo")).thenReturn("Hola boo");
// ACT
Response output = target("/rest/serviceABC/greet/boo").request().get();
// ASSERT
assertAll(
() -> assertEquals(Status.OK.getStatusCode(), output.getStatus()),
() -> assertEquals("Hola boo, Regards: Amith Kumar", output.readEntity(String.class)));
}
}

Spring 3 #Component and static factory method

If I'm writing a static factory method to create objects, how do I use the '#Component' annotation for that factory class and indicate (with some annotation) the static factory method which should be called to create beans of that class? Following is the pseudo-code of what I mean:
#Component
class MyStaticFactory
{
#<some-annotation>
public static MyObject getObject()
{
// code to create/return the instance
}
}
I am afraid you can't do this currently. However it is pretty simple with Java Configuration:
#Configuration
public class Conf {
#Bean
public MyObject myObject() {
return MyStaticFactory.getObject()
}
}
In this case MyStaticFactory does not require any Spring annotations. And of course you can use good ol' XML instead.
You need to use the spring interface FactoryBean.
Interface to be implemented by objects used within a BeanFactory which
are themselves factories. If a bean implements this interface, it is
used as a factory for an object to expose, not directly as a bean
instance that will be exposed itself.
Implement the interface and declare a bean for it. For example :
#Component
class MyStaticFactoryFactoryBean implements FactoryBean<MyStaticFactory>
{
public MyStaticFactory getObject()
MyStaticFactory.getObject();
}
public Class<?> getObjectType() {
return MyStaticFactory.class;
}
public boolean isSingleton() {
return true;
}
}
Through #Component and component scanning, this class will be discovered. Spring will detect that it is a FactoryBean and expose the object you return from getObject as a bean (singleton if you specify it).
Alternatively, you can provide a #Bean or <bean> declaration for this FactoryBean class.
Bean:
public class MyObject {
private String a;
public MyObject(String a) {
this.a = a;
}
#Override
public String toString() {
return a;
}
}
FactoryBean:
#Component
public class MyStaticFactory implements FactoryBean<MyObject> {
#Override
public MyObject getObject() throws Exception {
return new MyObject("StaticFactory");
}
#Override
public Class<?> getObjectType() {
return MyObject.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
Use:
#Component
public class SomeClass{
#Autowired
MyObject myObject;
}
Spring boot:static factory method:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
enum ParsersConst {
bofa, jpm, wellsforgo
}
interface Parser {
String readFromFile(String file);
}
class JPM implements Parser {
#Override
public String readFromFile(String file) {
System.out.println("From JPM Parser");
return "JPM";
}
}
class Bofa implements Parser {
#Override
public String readFromFile(String file) {
System.out.println("From Bofa Parser");
return "BOFA";
}
}
class WellsForgo implements Parser {
#Override
public String readFromFile(String file) {
System.out.println("From Wellsforgo Parser");
return "WellsForgo";
}
}
class ParserCreator {
public static Parser createParser(ParsersConst parsConst) {
if (ParsersConst.bofa.equals(parsConst)) {
return new Bofa();
} else if (ParsersConst.jpm.equals(parsConst)) {
return new JPM();
} else if (ParsersConst.wellsforgo.equals(parsConst)) {
return new WellsForgo();
}
throw new IllegalArgumentException("Unknown Parser");
}
}
#Configuration
class ParserConfig{
#Bean
public Parser bofa() {
return ParserCreator.createParser(ParsersConst.bofa);
}
#Bean
public Parser wellsforgo() {
return ParserCreator.createParser(ParsersConst.wellsforgo);
}
#Bean
public Parser jpm() {
return ParserCreator.createParser(ParsersConst.jpm);
}
}
#Component
public class StaticFacotryDemo implements CommandLineRunner{
#Autowired
private ApplicationContext context;
#Override
public void run(String... args) throws Exception {
Parser parser = (Parser) context.getBean(ParsersConst.jpm.toString());
System.out.println(parser);
System.out.println(parser.readFromFile("jan_stmt.pdf"));
}
}

Categories

Resources