'Dynamicallly retrieve Spring Boot CORS Configuration from database for specific method in a Controller
I am trying to set the CORS configuration at Controller level using
@CrossOrigin on Controller and Handler Method
public class AccountController {
@CrossOrigin("retreive data from DB")
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
I have tried using below but its set only when the spring boot starts and takes changes only when the service is restarted next time ...
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// CorsConfiguration config = jHipsterProperties.getCors();
CorsConfiguration config=CorsService.fetchCorsConfigFromDb;
if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
log.debug("Registering CORS filter");
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/management/**", config);
source.registerCorsConfiguration("/v2/api-docs", config);
}
return new CorsFilter(source);
}
fetchCorsConfigFromDbwill fetch data from DB. Any changes from DB will be reflected only when Spring Boot App is restarted...
Solution 1:[1]
To implement this functionality you can use a basic filter in which you can write your custom database logic to add CORS header to your request based on some database attribute value.
You can refer to below example to implement this functionality using spring-data-jpa.
Add DB connection attributes to application.properties file
application.properties
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=postgres
spring.datasource.password=root
Create a entity with below attribute to save URL in DB
Cors.java
@Entity
@Getter
@Setter
public class Cors {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String url;
private boolean isAllowed;
}
And in the repository added a findByUrl method to get value from DB based on URL
CorsRepository.java
public interface CorsRepository extends JpaRepository<Cors,Long> {
Optional<Cors> findByUrl(String url);
}
Below is my filter to intercept request and make DB call and if isAllowed is true then i add cors headers to make a successful request
CorsFilter.java
@Component
public class CorsFilter implements Filter {
@Autowired
CorsRepository corsRepository;
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String url = request.getRequestURI().toString();
System.out.println(url);
Optional<Cors> cors = corsRepository.findByUrl(url);
if(cors.isPresent() && cors.get().isAllowed()){
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
}
chain.doFilter(req, res);
}
@Override
public void destroy() { }
}
You can create a sample controller like this:
CorsTesterController.java
@RestController
public class CorsTesterController {
@GetMapping("/api/v1/test")
String getResponse(){
return "test response";
}
}
and insert values to DB to allow/disallow a url to test this example code.
testdb=# select * from cors;
id | is_allowed | url
----+------------+-----------------
1 | f | /api/v1/block
2 | t | /api/v1/allowed
Solution 2:[2]
For CorsService.fetchCorsConfigFromDb;
it should load from cache
then you can update your cache at run-time
you should implement CorsService.fetchCorsConfig();
- this method should look for any cors configuration in cache first ,if there any configurations found load it directly from cache else you will retrive cors from db and update your cache
- create update cors at runtime method
CorsService.updateCorsConfig();, this should update your cors in db and then update cache .
Solution 3:[3]
Usually reading properties from database - not secured variant and if you lost connection to database you service can startup not correct.
Anyway you can do it, but would like to draw attention to default support by Spring a lot of remote properties sources
For loading custom properties you may usespring.config.import=optional:file:./cors.yml
or from java args-Dspring.config.import=optional:file:./cors.yml
This method support reading from file, from url, config server
Spring documentation about this
For reading CORS configuration from properties file you may use library (I'm developer of this)
<dependency>
<groupId>io.github.iruzhnikov</groupId>
<artifactId>spring-webmvc-cors-properties-autoconfigure</artifactId>
<version>VERSION</version>
</dependency>
and properties config
spring:
web:
cors:
enabled: true
mappings: #spring.web.cors.mappings.<any_name>.<property>: <value>
anyName: #just any name, just for grouping properties under the same path pattern (not used in internal logic)
paths: #ant style path pattern, ATTENTION! not ordered, /** pattern override all other pattern
- /path/to/api
- /path/to/api/**
#allowed-origins: "*"
allowed-methods: GET #Enable override all defaults! If disabled: a lot more from all the controller methods included from the path pattern matches
#allowed-headers: "*"
#exposed-headers: ('*' - not-supported)
#allow-credentials: true
allowed-origin-patterns: .*
#max-age: PT30M
Solution 4:[4]
Your array needs to be at least twice as big as the number of items in your list. You may find this approach useful:
from multiprocessing import Process, Array
def calculation(numbers, result):
for index, number in enumerate(numbers):
result[index] = number*number
def calculation2(numbers, result):
for index, number in enumerate(numbers, len(numbers)):
result[index] = number*number*number
if __name__ == '__main__':
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = Array('i', len(nums)*2)
p = []
for t in [calculation, calculation2]:
p.append(Process(target=t, args=(nums, result)))
p[-1].start()
for p_ in p:
p_.join()
print(result[:])
Output:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Salah Atwa |
| Solution 3 | |
| Solution 4 | Albert Winestein |
