When using #Secured on a REST-Controller, implementing an interface, the Controller is not found in a #WebMvcTest. Either removing the #Secured annotation or removing the implements on the class will make it run in the test.
#Controller
#RequestMapping(path="/failing")
public class FailingTestController implements MyPasswordApi {
#RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE, path = "/test")
#Secured("ROLE_USER")
public ResponseEntity<GetEntity> getMethod()
and
#Controller
#RequestMapping(path = "/running")
public class RunningTestController {
#RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE, path = "/test")
#Secured("ROLE_USER")
public ResponseEntity<GetEntity> getMethod() {
are both used in different jUnit-5 Tests. The "RunningTest" will succeed (i.e. the GET-Request will have status 200), whereas the "FailingTest" will end up with a status 404. Using the injected RequestMapppingHanderMapping one can see, that the controller with the inheritance is not bound.
In fact, in the application, both controllers are found.
My question is, how to test an controller implementing security and an interface.
A testcase is found on github: https://github.com/sanddorn/Spring-Boot-Security-Rest-Showcase
The real answer could only be found in the github-repo (see question). The root problem was to annotate the application class with #SpringBootApplication and #EnableGlobalMethodSecurity like
#SpringBootApplication
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class TestApplication {
public static void main(String []argv) {
SpringApplication.run(TestApplication.class);
}
}
Using a different configuration class like
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class TestConfiguration {
}
and removing all extra annotation to the application-class solves the problem.
You are using wrong annotation, replace Controller to RestController
#RestController
#RequestMapping(path = "/running")
public class RunningTestController {
}
Related
Recently i found spring documentation page that says:
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used.
But it doesn't seem to be the case in my application. I wanted to write a small benchmark to compare the performance of both types of proxying.
There are two similar classes. Both have one method annotated with the #Transactional annotation. One class implements the interface and the other does not:
#Service
public class Cglib {
#Transactional
public void method() {}
}
public interface Dynamic {
void method();
}
#Service
public class DynamicImpl implements Dynamic {
#Override
#Transactional
public void method() {}
}
And based on the documentation for the first class, a CGLIB proxy should be created, and for the second, a JDK dynamic proxy.
But in my case CGLIB was used for both classes:
#Component
#RequiredArgsConstructor
public class Runner implements ApplicationRunner {
private final Cglib cglib;
private final Dynamic dynamic;
#Override
public void run(ApplicationArguments args) {
System.out.println(cglib.getClass());
System.out.println(dynamic.getClass());
}
}
class com.example.demo.proxy.cglib.Cglib$$EnhancerBySpringCGLIB$$767ff22
class com.example.demo.proxy.dynamic.DynamicImpl$$EnhancerBySpringCGLIB$$20a564d6
There are no additional configurations in the application. Only #SpringBootApplication class generated via spring initializr
Am I doing something wrong? The code was run on Spring Boot 2.7.2 and JDK 17.
That is due to spring-boot autoconfiguation:
#Configuration(proxyBeanMethods = false)
#ConditionalOnBean(TransactionManager.class)
#ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {
#Configuration(proxyBeanMethods = false)
#EnableTransactionManagement(proxyTargetClass = false)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
public static class JdkDynamicAutoProxyConfiguration {
}
#Configuration(proxyBeanMethods = false)
#EnableTransactionManagement(proxyTargetClass = true)
#ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
it turns #EnableTransactionManagement(proxyTargetClass = true) (Indicate whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false)) when the property spring.aop.proxy-target-class is not set (matchIfMissing = true)
I'm trying to create a MockMvc test of a Spring Boot controller. I specifically do not want the entire application context to be spun up, so I am restricting the context to the controller in question. However, the test fails with a 500 with the following log output:
2020-03-03 13:04:06.904 WARN 8207 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class main.endpoints.ResponseDto]
It appears that the Spring Boot context does not know how to find Jackson.
Here is the controller
#RestController
class MyController {
#GetMapping("/endpoint")
fun endpoint(): ResponseDto {
return ResponseDto(data = "Some data")
}
}
data class ResponseDto(val data: String)
The test is as follows:
#SpringBootTest(
classes = [MyController::class],
webEnvironment = SpringBootTest.WebEnvironment.MOCK
)
#AutoConfigureMockMvc
internal class MyControllerTest(#Autowired private val mockMvc: MockMvc) {
#Test
fun `should work`() {
mockMvc.perform(MockMvcRequestBuilders.get("/endpoint").accept(MediaType.APPLICATION_JSON))
.andExpect(
content().json(
"""
{
"data": "Some data"
}
"""
)
)
}
}
The build.gradle file includes the following dependencies:
def jacksonVersion = "2.10.2"
testImplementation("com.fasterxml.jackson.core:jackson-core:2.10.2")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.10.2")
testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.10.2")
Any ideas on how to get this to work?
The solution is to annotate the class with #WebMvcTest rather than #SpringBootTest. This configures enough context that the test can interact via MockMvc with the controller.
Unfortunately, enabling #WebMvcTest has another side effect: all beans specified by #Bean-annotated methods in the configuration are also instantiated. This is a problem when those methods cannot be executed in a test environment (e.g. because they access certain environment variables).
To solve this, I added the annotation #ActiveProfiles("test") to the test and #Profile("!test") to each such annotated method. This suppresses the invocation of those methods and the test works.
I'm not sure, but I think you need to specify what format the output will be. So something like
#GetMapping(value = ["/endpoint"], produces = [MediaType.APPLICATION_JSON])
So that spring knows to convert it to json and not say XML or something.
#EnableWebMvc might solve your issue.
According to Java Doc:
Adding this annotation to an #Configuration class imports the Spring MVC configuration from WebMvcConfigurationSupport,
e.g.:
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = MyConfiguration.class)
public class MyConfiguration {
}
To customize the imported configuration, implement the interface WebMvcConfigurer and override individual methods, e.g.:
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = MyConfiguration.class)
public class MyConfiguration implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addConverter(new MyConverter());
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyHttpMessageConverter());
}
}
I'm trying to build a simple handler method that will prevent users to browse item's that belong to different user. The method is below:
#PostAuthorize("principal.username == #model['username']")
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public String single(#PathVariable Long id, Model model) {
Item item = itemService.findById(id);
model.addAttribute("item", item);
model.addAttribute("username", item.getUser().getUsername());
return "item";
}
so the main idea is to compare principal.username with the username stored in the model. I'm using Spring 5.0.5, security 5.0.4. Java Config without boot. My config holds (amnogst other things)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
and
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
final DefaultMethodSecurityExpressionHandler expressionHandler
= new DefaultMethodSecurityExpressionHandler();
return expressionHandler;
}
}
Despite of this, I'm able to login and than via direct url access items of different user. Any hint is welcome. Thanks
Add this method security config to your project. This configuration act as globally and of-course your proxyTargetClass = true so that, spring can generate proxy for your controller class also.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
/*
We can enable annotation-based security using the #EnableGlobalMethodSecurity annotation
on any #Configuration instance.
*/
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
Hope this will solve your problem.
OK, so in case others experience a same problem. The problem is that I was using a #Pre/#PostAuthorize annotation on a Controller. Controllers are typically not behind an interface, and by the default, the aspect that configure the behaviour didn't kick in.
The solution for me was to #EnableAspectJAutoProxy on a WebConfig class that scan packages in search for controllers:
#Configuration
#EnableWebMvc
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"rs.saga.web"})
public class WebConfig implements WebMvcConfigurer {
...
}
Problem
Exploring an option of enabling/disabling a #RequestMapping endpoint on demand without restarting JVM. Implement a kill switch on a endpoint at runtime.
Attempt
I've tried the following but #RefreshScope does not seem to work with #ConditionOnProperty
#RestController
#RefreshScope
#ConditionalOnProperty(name = "stackoverflow.endpoints", havingValue = "enabled")
public class MyController {
#Value("${stackoverflow.endpoints}")
String value;
#RequestMapping(path = "/sample", method = RequestMethod.GET)
public ResponseEntity<String> process() {
return ResponseEntity.ok(value);
}
}
Updated property using
POST /env?stackoverflow.endpoints=anyvalue
And reloaded context using
POST /refresh
At this point the controller returns the updated value.
Question
Are there any other ways to achieve the desired functionality?
Conditions in Boot are only applied at the Configuration class level or at the bean definition level.
This might help you.
public class MyWebConfiguration extends WebMvcConfigurationSupport {
#Bean
#ConditionalOnProperty(prefix="stackoverflow.endpoints",name = "enabled")
public MyController myController() {
return new MyController ();
}
}
As Spenser Gibb said here, this behavior is not supported.
I defined a series of annotation which could help me do some QPS measurement. I put them on controller method in spring cloud. Unfortunately, it does not work...
It does nothing if I put
#Metered(tags = {"test1", "test2"})
#Counted(tags = {"count1"})
on controller method.
Do I miss anything ?
#RestController
#Configuration
#ComponentScan
public class TestController {
private Logger logger = LoggerFactory.getLogger(TestController.class);
#RequestMapping("exinfo")
#Metered(tags = {"test1", "test2"})
#Counted(tags = {"count1"})
public String getInfo(){
return "success";
}