Dynamically securing Spring Rest endpoint - spring

Actually my code look like that:
#PreAuthorize("hasAuthority('admin')")
#RequestMapping(value = "/xxxx", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<> method(#RequestBody RequestClass request) {
}
As you can see the allowed authorities are hard-coded in java code.
Is there a way to override the behaviour of PreAuthorize or to load the proper endpoint configuration at startup from an external source(database or configuration file)?

I suppose you might give something like this a try (might need some tweaking) but it's ugly and I would not do it myself...
Setup your method to role mappings in your configuration. For example:
permissions.method1: admin
permissions.method2: admin
permissions.method3: user
Then use an #ConfigurationProperties class to load in your map into a Map.
#ConfigurationProperties("")
public class SecurityMappingProperties {
private final Map<String, String> permissions = new HashMap<>();
public Map<String, String> getPermissions() {
return permissions;
}
}
Then setup a service to handle the lookup.
#Service
public class MethodPermissionService {
#Autowired
private SecurityMappingProperties mappingProperties;
//lookup the mapped role and see if you user has it..
public Boolean lookupPermissionForMethod(String method){
return doesUserHaveRole(mappingProperties.get(method));
}
private Boolean doesUserHaveRole(String role){
//implement whatever logic you want to look up the requesting user's role...
}
}
Then in your controllers, invoke the methodPermissionService and pass in the method name, like so...
#PreAuthorize("#methodPermissionService('method1')")
This, of course, would require you to have every secured method in all of your controllers to have an #Preauthorize with the matching method name as the argument to the methodPermissionService('xxx').
Since we are already in this rabbit hole, if you really wanted to, you could also just have a single place to declare all of them in some sort of MethodRoleHolder class where you can make them static Strings like the following:
public static final String METHOD1_SECURITY = "#methodPermissionService('method1')";
public static final String METHOD2_SECURITY = "#methodPermissionService('method2')";
then use them in your controllers...
#PreAuthorize(MethodRoleHolder.METHOD1_SECURITY)
Upfront caveat: I haven't actually tried this myself exactly as I laid out here but I have implemented a security scheme similar to this, just without the dynamic role mapping look up part.

Related

Modify request header going into a controller and response header leaving a Spring Boot RestController

This feels like it should be a simple thing, but I'm still pretty new to SpringBoot, and the whole Servlet ecosystem, so it's not readily apparent. I would love an interface similar to HandlerInterceptor that allows me to modify the request and response object once I'm done in a controller. Even better would be to decorate mapping annotation, so I can specify which controllers need the operation.
The problem I'm solving right now, though I anticipate expanding this in the future, is that I have an encrypted header coming into my application that I would like to decrypt for use in the controller and then encrypt again on the way out.
EDIT: For clarity.
I have a rest controller, something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String ecryptedHeaderValue) {
DecryptionObject decryptedHeader = new DecryptionObject(pageHeaderValue);
SomePojo result = getResult();
return decryptedHeader.decorateResponseWithEncryptedHeader(result);
}
}
I would love to not have the DecryptionObject on every mapping, but rather, before I even get to the mapping, I decrypt the header via some filter or hook and then re-encrypt the header on the way out. Then my code would look something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource", decryptHeader="EncryptedHeader")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String decryptedHeaderValue) {
SomePojo result = getResult();
return result;
}
}
I found that the HandlerInterceptor doesn't work because I cannot modify the request or response in the interceptor. Hope that clarifies the issue.
You can still use HandlerInterceptor. Create your class implementing HandlerInterceptor, and then register it using another class which extends WebMvcConfigurer.
#EnableWebMvc
#Configuration
#ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new [...]); //Apply to all controllers
registry.addInterceptor(new [...]).addPathPatterns("path1","path2"); //Apply to specific paths to restrict to some controllers.
}
}
You also could do it using a Filter - create your Filter class and register it by declaring a #Bean of type FilterRegistrationBean - this also allows you to restrict to some paths.
UPDATE: You could do this with request attributes which can be set by interceptors (request.setAttribute("decryptedHeaderValue",<decrypted>). Or if you're specific about using headers, a filter would be more suitable for your purpose. Create a new wrapped request type that wraps the incoming request and does whatever you want, and pass this wrapper to the next filter in chain.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
[...]
HttpServletRequestWrapper decryptedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
public String getHeader(String name) {
if (name.equals("DecryptedHeader")) {
String encrypted = super.getHeader("EncryptedHeader");
String decrypted = decrypt(encrypted);
return decrypted;
}
return super.getHeader(name); //Default behavior
}
}
chain.doFilter(decryptedRequest, response); //Pass on the custom request down
}
Then any class down the line (other filters, controllers etc) can just call request.getHeader("DecryptedHeader") to retrieve the decrypted header. This is just one of many similar approaches. You can restrict the paths for which this filter executes when registering it.
For response, there is a similar class HttpServletResponseWrapper which you can use for customization.
We can do this via addingAttribute in the interceptor
httpServletRequest.setAttribute(,);

discussion about #RequestParam and #RequestBody

I have a class:
public class user{
private String id;
private MultiPartFile file;
**Getters And Setters**
}
And in the Controller:
#PostMapping(value="/upload)
public void upload(User user){
}
In the front end I post data with form-data.I can get the user object.
But when I add #RequestBody and #RequestParam,it can't works.
in my opinion,#RequestParam is used to binding parameter to simple class . when I use #RequestBody ,spring will find HttpMessageConverter to convert http request body to class.But I'm not sure about that.Does anyone can explain to me?
So, I believe we are talking about org.springframework.web.multipart.MultipartFile, which is to be used together with #RequestParam variable. The mechanism is somewhat special in this case.
I had a similar problem, and what I ended up using was org.springframework.web.multipart.commons.CommonsMultipartResolver. From frontend I've constructed multipart request with two parts, in your scenario it could be user (containing just JSON data) and file (containing the file itself), e.g.:
#PostMapping(value="/upload")
public void upload(#RequestParam("user") User user, #RequestParam("file") MultipartFile file){
...
}
But then, you need to configure custom serialization of the User part, which can be done using org.springframework.web.multipart.commons.CommonsMultipartResolver. You can configure it using bean config like this:
#Configuration
public class MappingConfig {
#Order(Integer.MIN_VALUE)
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
#Bean
public Converter<String, User> stringToUser() {
return new Converter<String, User>() {
#Override
public User convert(String jsonString) {
return new Gson().fromJson(jsonString, User.class);
}
};
}
...
}
Also, as you can see I am using Gson manually, I couldn't find a better way how to do it. Also, it doesn't play with Java 8 lambdas, so it cannot be shortened (because of explicit types are needed for it to work).
I hope that this will at least points you to a right path.

OData (Olingo) "inhibit" endpoint

My question is about what is best way to inhibit an endpoint that is automatically provided by Olingo?
I am playing with a simple app based on Spring boot and using Apache Olingo.On short, this is my servlet registration:
#Configuration
public class CxfServletUtil{
#Bean
public ServletRegistrationBean getODataServletRegistrationBean() {
ServletRegistrationBean odataServletRegistrationBean = new ServletRegistrationBean(new CXFNonSpringJaxrsServlet(), "/user.svc/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("javax.ws.rs.Application", "org.apache.olingo.odata2.core.rest.app.ODataApplication");
initParameters.put("org.apache.olingo.odata2.service.factory", "com.olingotest.core.CustomODataJPAServiceFactory");
odataServletRegistrationBean.setInitParameters(initParameters);
return odataServletRegistrationBean;
} ...
where my ODataJPAServiceFactory is
#Component
public class CustomODataJPAServiceFactory extends ODataJPAServiceFactory implements ApplicationContextAware {
private static ApplicationContext context;
private static final String PERSISTENCE_UNIT_NAME = "myPersistenceUnit";
private static final String ENTITY_MANAGER_FACTORY_ID = "entityManagerFactory";
#Override
public ODataJPAContext initializeODataJPAContext()
throws ODataJPARuntimeException {
ODataJPAContext oDataJPAContext = this.getODataJPAContext();
try {
EntityManagerFactory emf = (EntityManagerFactory) context.getBean(ENTITY_MANAGER_FACTORY_ID);
oDataJPAContext.setEntityManagerFactory(emf);
oDataJPAContext.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
return oDataJPAContext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
...
My entity is quite simple ...
#Entity
public class User {
#Id
private String id;
#Basic
private String firstName;
#Basic
private String lastName;
....
Olingo is doing its job perfectly and it helps me with the generation of all the endpoints around CRUD operations for my entity.
My question is : how can I "inhibit" some of them? Let's say for example that I don't want to enable the delete my entity.
I could try to use a Filter - but this seems a bit harsh. Are there any other, better ways to solve my problem?
Thanks for the help.
As you have said, you could use a filter, but then you are really coupled with the URI schema used by Olingo. Also, things will become complicated when you have multiple, related entity sets (because you could navigate from one to the other, making the URIs more complex).
There are two things that you can do, depending on what you want to achieve:
If you want to have a fined grained control on what operations are allowed or not, you can create a wrapper for the ODataSingleProcesor and throw ODataExceptions where you want to disallow an operation. You can either always throw exceptions (i.e. completely disabling an operation type) or you can use the URI info parameters to obtain the target entity set and decide if you should throw an exception or call the standard single processor. I have used this approach to create a read-only OData service here (basically, I just created a ODAtaSingleProcessor which delegates some calls to the standard one + overridden a method in the service factory to wrap the standard single processor in my wrapper).
If you want to completely un-expose / ignore a given entity or some properties, then you can use a JPA-EDM mapping model end exclude the desired components. You can find an example of such a mapping here: github. The mapping model is just an XML file which maps the JPA entities / properties to EDM entity type / properties. In order for olingo to pick it up, you can pass the name of the file to the setJPAEdmMappingModel method of the ODataJPAContext in your initialize method.

Spring Boot equivalent to XML multi-database configuration

I would like to port two projects to Spring Boot 1.1.6. The are each part of a larger project. They both need to make SQL connections to 1 of 7 production databases per web request based region. One of them persists configuration setting to a Mongo database. They are both functional at the moment but the SQL configuration is XML based and the Mongo is application.properties based. I'd like to move to either xml or annotation before release to simplify maintenance.
This is my first try at this forum, I may need some guidance in that arena as well. I put the multi-database tag on there. Most of those deal with two connections open at a time. Only one here and only the URL changes. Schema and the rest are the same.
In XML Fashion ...
#Controller
public class CommonController {
private CommonService CommonService_i;
#RequestMapping(value = "/rest/Practice/{enterprise_id}", method = RequestMethod.GET)
public #ResponseBody List<Map<String, Object>> getPracticeList(#PathVariable("enterprise_id") String enterprise_id){
CommonService_i = new CommonService(enterprise_id);
return CommonService_i.getPracticeList();
}
#Service
public class CommonService {
private ApplicationContext ctx = null;
private JdbcTemplate template = null;
private DataSource datasource = null;
private SimpleJdbcCall jdbcCall = null;
public CommonService(String enterprise_id) {
ctx = new ClassPathXmlApplicationContext("database-beans.xml");
datasource = ctx.getBean(enterprise_id, DataSource.class);
template = new JdbcTemplate(datasource);
}
Each time a request is made, a new instance of the required service is created with the appropriate database connection.
In the spring boot world, I've come across one article that extended TomcatDataSourceConfiguration.
http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html That at least allowed me to create a java configuration class however, I cannot come up with a way to change the prefix for the ConfigurationProperties per request like I am doing with the XML above. I can set up multiple configuration classes but the #Qualifier("00002") in the DAO has to be a static value. //The value for annotation attribute Qualifier.value must be a constant expression
#Configuration
#ConfigurationProperties(prefix = "Region1")
public class DbConfigR1 extends TomcatDataSourceConfiguration {
#Bean(name = "dsRegion1")
public DataSource dataSource() {
return super.dataSource();
}
#Bean(name = "00001")
public JdbcTemplate jdbcTemplate(DataSource dsRegion1) {
return new JdbcTemplate(dsRegion1);
}
}
On the Mongo side, I am able to define variables in the configurationProperties class and, if there is a matching entry in the appropriate application.properties file, it overwrites it with the value in the file. If not, it uses the value in the code. That does not work for the JDBC side. If you define a variable in your config classes, that value is what is used. (yeah.. I know it says mondoUrl)
#ConfigurationProperties(prefix = "spring.mongo")
public class MongoConnectionProperties {
private String mondoURL = "localhost";
public String getMondoURL() {
return mondoURL;
}
public void setMondoURL(String mondoURL) {
this.mondoURL = mondoURL;
}
There was a question anwsered today that got me a little closer. Spring Boot application.properties value not populating The answer showed me how to at least get #Value to function. With that, I can set up a dbConfigProperties class that grabs the #Value. The only issue is that the value grabbed by #Value is only available in when the program first starts. I'm not certain how to use that other than seeing it in the console log when the program starts. What I do know now is that, at some point, in the #Autowired of the dbConfigProperties class, it does return the appropriate value. By the time I want to use it though, it is returning ${spring.datasource.url} instead of the value.
Ok... someone please tell me that #Value is not my only choice. I put the following code in my controller. I'm able to reliably retrieve one value, Yay. I suppose I could hard code each possible property name from my properties file in an argument for this function and populate a class. I'm clearly doing something wrong.
private String url;
//private String propname = "${spring.datasource.url}"; //can't use this
#Value("${spring.datasource.url}")
public void setUrl( String val) {
this.url = val;
System.out.println("==== value ==== " + url);
}
This was awesome... finally some progress. I believe I am giving up on changing ConfigurationProperties and using #Value for that matter. With this guy's answer, I can access the beans created at startup. Y'all were probably wondering why I didn't in the first place... still learning. I'm bumping him up. That saved my bacon. https://stackoverflow.com/a/24595685/4028704
The plan now is to create a JdbcTemplate producing bean for each of the regions like this:
#Configuration
#ConfigurationProperties(prefix = "Region1")
public class DbConfigR1 extends TomcatDataSourceConfiguration {
#Bean(name = "dsRegion1")
public DataSource dataSource() {
return super.dataSource();
}
#Bean(name = "00001")
public JdbcTemplate jdbcTemplate(DataSource dsRegion1) {
return new JdbcTemplate(dsRegion1);
}
}
When I call my service, I'll use something like this:
public AccessBeans(ServletRequest request, String enterprise_id) {
ctx = RequestContextUtils.getWebApplicationContext(request);
template = ctx.getBean(enterprise_id, JdbcTemplate.class);
}
Still open to better ways or insight into foreseeable issues, etc but this way seems to be about equivalent to my current XML based ways. Thoughts?

Is there an equivalent of a "beforeHandler" for spring 4?

I have a controller with a requestmapping..
#Controller
public class TestController {
private static final String template = "Hello there, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/hello")
public #ResponseBody String hello() {
return "Hello";
}
}
How can I make it such that everytime a user goes to a RequestMapping, or whichever url, some other method is called to println to the console the URL the user is at before it actually enters the method "hello"?
I would like to use this method to ensure users have proper credentials in the future. I see there are #PreAuthorize annotation, but there doesnt seem to be a method associated with it that I can write my own logic, i.e. do a simple println to the console with the current URL the user is at.
You have a number of options.
With Spring, you can implement and register a HandlerInterceptor and implement its preHandle method to log the request URL, which you can reconstruct with the various HttpServletRequest methods.
With pure servlet-api, you can implement and register your own Filter which logs the request URL and then continues the chain, with doFilter(..).
You can also use AOP, advise all your #RequestMapping annotated methods with a #Before advice that logs the URL.

Resources