I have a web application running in Tomcat and using Spring MVC to define controllers and mappings. I have the following class:
#Controller("api.test")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String test(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
// body
}
}
I would like to make this controller and the ".../test" path available according to a property defined somewhere (e.g. file). If the property is, lets say, false, I would like the app to behave as if that path doesn't exist and if it is true, to behave normally. How can I do this? Thanks.
If you are using Spring 3.1+, make the controller available only in the test profile:
#Profile("test")
class TestController {
...
}
then enable that profile by e.g. passing the following system property at Tomcat boot:
-Dspring.profiles.active=test
To disable the controller simply omit the given profile.
Another way of doing it , may be simpler way of doing it, is to use #ConditionalOnProperty annotation with your RestController/Controller.
#RestController("api.test")
#ConditionalOnProperty(name = "testcontroller.enabled", havingValue = "true")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
// body
}
}
Here testcontroller.enabled property in your yml properties say ,if not set to true , the TestController Bean is never created.
Tip: I suggest you to use RestController instead of Controller as its has #ResponseBody added by default. You can use #ConditionalOnExpression to arrive at the same solution but is little slower due to SpEL evaluation.
Related
There are various option provided in Spring framework like using #Conditionxxx annotations and Spring profiles, I am trying to use #ConditionOnProperty but it doesn't work.
Use below code for controller
#ConditionalOnProperty(
value="env",
havingValue = "test",
matchIfMissing = false)
#RestController
#RequestMapping("/poc/")
public class TestController {
#GetMapping("/test")
String getTest() {
return "success";
}
}
In application.properties file, add below line
env=test
if you change env value in property file to dev, your controller won't get registered.
I tried with the ConditionalOnProperties annotation for a particular endpoint within a class which has multiple endpoints. However, the condition doesn't seem to toggle it, but turn on always. It works well on the class level, but not on the endpoint level. Is it a bug?
#RequestMapping(path = "/test", consumes = {"application/x-www-form-urlencoded"})
#ResponseBody
#Timed()
#ConditionalOnProperty(name = "test.enabled")
public String test(#RequestParam(EXCEPTION_LOG_MESSAGE) String errorLog) {
As far as I understand the annotation, it should be used for beans. Either for a method, which returns a #Bean or for a class, which is a #Component, #Sevice or –as in your case– a #Controller.
The method you are annotating does not define a bean but is just a method of a bean, which gets defined anyway.
To achieve you goal, you could for example
put the specific endpoint to an extra Controller and annotated that one
or use the #Value annotation to get the property and just add an if to you method which makes it return something like 404 in case the property is not set:
Example for the latter idea:
#Value("${test.enabled}")
private boolean testEnabled;
public ResponseEntity test() {
if (!testEnabled) {
return ResponseEntity.notFound().build();
}
// ...
}
There are probably more options.
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've got an application which serves some web content via Spring MVC and also some JSON stuff under the same URI.
#Controller
public class SomeController {
#RequestMapping(value = {"/someUri"}, method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public String getView() {
return "index.html";
}
#RequestMapping(path = "/someUri", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String getJson() {
return "{ \"some\": \"json\" }";
}
And now I want to secure only the REST API which produces the MediaType.APPLICATION_JSON_VALUE with Spring Security.
When I add the #PreAuthorize or #Secured annotation on each method it works fine. But I want to configure it globally, like in the WebSecurityConfiguration#configure(HttpSecurity http) method. Is it possible to secure in any way globally an endpoint which produces a specific media type?
You could use MediaTypeRequestMatcher:
Allows matching HttpServletRequest based upon the MediaType's resolved from a ContentNegotiationStrategy. By default, the matching process will perform the following:
The ContentNegotiationStrategy will resolve the MediaType's for the current request
Each matchingMediaTypes that was passed into the constructor will be compared against the MediaType instances resolved from the ContentNegotiationStrategy.
If one of the matchingMediaTypes is compatible with one of the resolved MediaType returned from the ContentNegotiationStrategy, then it returns true
For example, consider the following example
GET /
Accept: application/json
ContentNegotiationStrategy negotiationStrategy = new HeaderContentNegotiationStrategy()
MediaTypeRequestMatcher matcher = new MediaTypeRequestMatcher(negotiationStrategy, MediaType.APPLICATION_JSON);
assert matcher.matches(request) == true // returns true
AFAIK Spring security does not have to do anything with the media type produced by the url. The security constraints are applied to URL patterns. When you talk about #PreAuthorized and #Secured, I assume you are looking for a global authorization mechanism. Yes, you can do something like that
#Configuration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN");
}
...
}
But it is a good idea to move all of your rest apis that require authorization to some sort of sub domain like /secure/** so that you can apply the security to a single pattern directly. Otherwise, you need to register all patterns one by one.
I am trying to call a method that is annotated with #RequestMapping(signIn) through a class level (from method: authentication) like so:
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request)
{
UserController user = new UserController();
return user.signIn(request, null);
}
and my controller looks like:
#Autowired
private UserManager userManager;
#RequestMapping(value = /signin, method = RequestMethod.POST)
public #ResponseBody Response signIn(HttpServletRequest request) {
JsonObject json = Misc.parseJson(request);
String lang = Misc.getLang(request);
user.setEmail(Misc.getEmail(json));
user.setPassword(Misc.getEncryptedPassword(json));
return ResponseUtils.success(userManager.auth(user, lang));
}
user manager is annotated with #component:
#Component
public class UserManager {
public User auth(User user, String lang) {
....
return user;
}
}
Problem is when I call the method "signIn" and just new-up a UserController instance through "/authenticate" mapping, the UserManager becomes NULL. So now I'm assuming that autowiring doesn't work when it's done this way.
Is there any other way to call the signIn method? I would hate to copy paste an already existing code to another class just to get this to work...
Autowiering only works in spring managed bean. If you create a class with new keyword, it is not a spring managed bean and autowiering would not work.
You can try to autowire the class which contains the method which is annotated or better put the code in a service class which can be used by both methods.
It's not problem with #Autowired .There are two type of Annotation
firstly method base annotation and field level annotation. You just used field level annotation.Check your import class with "org.springframework.beans.factory.annotation.Autowired" or it can be problem with initiation of "UserManager"
I don't know why you not moving logic into separate Service classs, but try this:
UserController.java
public UserController(UserManager userManager) {
this.userManager = userManager;
}
and then inside controller where authentication resource method is located:
#Autowired UserManager userManager;
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request) {
UserController user = new UserController(userManager);
return user.signIn(request);
}
So in the end I just separated the logic instead. Though one solution that I tried and I could have used was to just add another mapping to the signIn method instead of adding a new method in the other class since the logic was similar. Still I opted for a separate logic instead since there were a lot of unnecessary code in the signIn method for my purpose.