I have a rest service written with spring boot. I want to get all endpoints after start up. How can i achieve that?
Purpose of this, i want to save all endpoints to a db after start up (if they are not already exist) and use these for authorization. These entries will be inject into roles and roles will be used to create tokens.
You can get RequestMappingHandlerMapping at the start of the application context.
#Component
public class EndpointsListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods()
.forEach(/*Write your code here */);
}
}
Alternately you can also Spring boot actuator(You can also use actutator even though you are not using Spring boot) which expose another endpoint(mappings endpoint) which lists all endpoints in json. You can hit this endpoint and parse the json to get the list of endpoints.
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html#production-ready-endpoints
You need 3 steps to exposure all endpoints:
enable Spring Boot Actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
enable endpoints
In Spring Boot 2, Actuator comes with most endpoints disabled, the only 2 available by default are :
/health
/info
If you want to enable all of the endpoints, just set:
management.endpoints.web.exposure.include=*
For more details, refer to:
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html
go!
http://host/actuator/mappings
btw, In Spring Boot 2, Actuator simplifies its security model by merging it with the application one.
For more details, refer to this article:
https://www.baeldung.com/spring-boot-actuators
As an addition to the above comments, since Spring 4.2 you may use the #EventListener annotation like this:
#Component
public class EndpointsListener {
private static final Logger LOGGER = LoggerFactory.getLogger(EndpointsListener.class);
#EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
applicationContext.getBean(RequestMappingHandlerMapping.class)
.getHandlerMethods().forEach((key, value) -> LOGGER.info("{} {}", key, value));
}
}
If you want to find out more about how to use the Spring Events and to create custom events, please check out this article: Spring Events
In the application.properties, we need
management.endpoints.web.exposure.include=mappings
Then we can see all the endpoints at:
http://localhost:8080/actuator/mappings
Don't forget to add the actuator to the POM.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Late to the party but you can directly use
#Autowired
private RequestMappingHandlerMapping requestHandlerMapping;
this.requestHandlerMapping.getHandlerMethods()
.forEach((key, value) -> /* whatever */));
Related
I'm using Spring Boot v2.7.2 and the latest version of Spring Kafka provided by spring-boot-dependencies:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
I want the app to load all configuration from file hence I created the beans with this bare minimum configuration:
public class KakfaConfig {
#Bean
public ProducerFactory<Integer, FileUploadEvent> producerFactory() {
return new DefaultKafkaProducerFactory<>(Collections.emptyMap());
}
#Bean
public KafkaTemplate<Integer, FileUploadEvent> kafkaTemplate() {
return new KafkaTemplate<Integer, anEvent>(producerFactory());
}
}
It works and loads the configuration from the application.yaml below as expected.
spring:
application:
name: my-app
kafka:
bootstrap-servers: localhost:9092
producer:
client-id: ${spring.application.name}
# transaction-id-prefix: "tx-"
template:
default-topic: my-topic
However, if I uncomment the transaction-id-prefix line, the application fails to start with the exception
java.lang.IllegalArgumentException: The 'ProducerFactory' must support transactions
The documentation in here reads
If you provide a custom producer factory, it must support
transactions. See ProducerFactory.transactionCapable().
The only way I managed to make it work is removing the transaction prefix from the application.yaml and configure it in the code as per below:
#Bean
public ProducerFactory<Integer, FileUploadEvent> fileUploadProducerFactory() {
var pf = new DefaultKafkaProducerFactory<Integer, FileUploadEvent>(Collections.emptyMap());
pf.setTransactionIdPrefix("tx-");
return pf;
}
Any thoughts on how I can configure everything using the application properties file? Is this a bug?
The only solution atm is really setting the transaction-prefix-id in the code whilst creating the ProducerFactory, despite it's already been defined in the application.yaml.
The Spring Boot team replied as per below:
The intent is that transactions should be used and that the ProducerFactory should support them. The transaction-id-prefix property can be set and this results in the auto-configuration of the kafkaTransactionManager bean. However, if you define your own ProducerFactory (to constrain the types, for example) there's no built-in way to have the transaction-id-prefix applied to that ProducerFactory.
It's a fundamental principle of auto-configuration that it backs off when a user defines a bean of their own. If we post-processed the user's bean to change its configuration, it would no longer be possible for your code to take complete control of how things are configured. Unfortunately, this flexibility does sometimes require you to write a little bit more code. This is one such time.
If we want to keep the prefix as a property in the application.yaml file, we can inject it to avoid config duplication:
#Value("${spring.kafka.producer.transaction-id-prefix}")
private String transactionPrefix;
#Bean
public ProducerFactory<Integer, FileUploadEvent> fileUploadProducerFactory() {
var pf = new DefaultKafkaProducerFactory<Integer, FileUploadEvent>(Collections.emptyMap());
pf.setTransactionIdPrefix(transactionIdPrefix);
return pf;
}
I have used this example to implement OptimisticLockException handling:
How to retry JPA transactions after an OptimisticLockException
Dependency in pom.xml:
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>${hypersistence-utils.version}</version>
</dependency>
I have annotated the method which may receive OptimisticLockException:
#Retry(times = 10, on = OptimisticLockException.class)
public void modifySomething(){...}
However, when RollbackException/OptimisticLockException/StaleStateException occurs, I get a stack trace, but the method is not retried.
I use it with Guice 4.1.0. Should I bind it somewhere or write the method interceptor?
How to add AOP aspect?
You need to add the RetryAspect to your Spring configuration.
If you are using Guice, you have to create a similar Aspect because the one coming with Hypersistence Utils is only working with Spring.
In Spring, the configuration is very easy; you can do it like this:
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(
basePackages = {
"io.hypersistence.utils.spring.aop"
}
)
public class RetryAspectConfiguration {
}
Has anyone managed to get Spring Boot w/ Spring Security to handle AuthorizedEvent's (i.e. for audit log)?
I have implemented the following application event listener:
#Component
public class AuthorizationSuccessAudit implements ApplicationListener<AuthorizedEvent> {
private static Logger auditLogger = LoggerFactory.getLogger("audit");
#Override
public void onApplicationEvent(AuthorizedEvent event) {
auditLogger.info("Authorization granted to user: {} - {}", event.getAuthentication().getName(), event.getConfigAttributes());
}
}
and have a test MVC endpoint annotated with #PreAuthorize. I was expecting that the spring security grants would show up on the log. While this works for every other event I used (AuthenticationSuccessEvent, AuthenticationFailureEvent, AbstractAuthenticationFailureEvent) it does not for the AuthorizedEvent.
I tried browsing the Spring Boot source and it seems this event is not handled in AuthorizationAuditListener.java, is this possibly a bug or am I hacking at it the wrong way?
As per spring boot documentation, Use Spring Boot Actuator (audit framework for Spring Boot), and provide your own implementations of AbstractAuthorizationAuditListener.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
And something similar to this..
class TestAuthorizationAuditListener extends AbstractAuthorizationAuditListener {
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
}
#Override
public void onApplicationEvent(AbstractAuthorizationEvent event) {
}
}
It looks like spring boot can not realize that here you want to handle event.
Try to annotate method so that spring knows that here you want to handle event
#EventListener(value = {AuthorizedEvent.class})
public void onApplicationEvent(AuthorizedEvent event) {
auditLogger.info("Authorization granted to user: {} - {}", event.getAuthentication().getName(), event.getConfigAttributes());
}
On successful authorization AuthorizedEvent should be triggered. make sure
FilterSecurityInterceptor should set setPublishAuthorizationSuccess true
I noticed Spring Boot Actuator only works if your application uses Spring MVC (DispatcherServlet) to handle endpoints. By default, this servlet is included if you add the module spring-boot-starter-web into your project.
Once this servlet exists, the class EndpointWebMvcAutoConfiguration customize the Spring MVC to support endpoints and other management properties.
For record, my application implements a Vaadin Servlet to navigate on screens, so is there any way to enable Spring Boot Actuator in this case?
You won't be able to reuse the EndpointWebMVCAutoConfiguration class as it is explicitly conditionnal on DispatcherServlet.class. If you look at the implementation, you'll see that the Actuator has a lot of dependencies on Spring MVC.
It would be a little ballzy but you could consider implementing your own autoconfiguration class inspired by EndpointWebMVCAutoConfiguration.
I wish you good luck if you go down that path ;)
You can have both. If you have a VaadinServlet, you can try with something like:
#SpringBootApplication
public class AdminApplication {
#Bean
public ServletRegistrationBean<SpringVaadinServlet> springVaadinServlet() {
SpringVaadinServlet servlet = new SpringVaadinServlet();
ServletRegistrationBean<SpringVaadinServlet> registrationBean = new ServletRegistrationBean<>(servlet, "/your-web-app/*");
registrationBean.setLoadOnStartup(1);
registrationBean.setName("VaadinServlet");
return registrationBean;
}
}
#SpringUI(path = "/")
public class VaadinUI extends UI {
...
}
Notice the need for a registration name, a custom servlet mapping URL, and custom path in the #SpringUI annotation.
You can find a running demo here.
I am using Spring Boot, and some of it's modules add its own endpoints and security configuration. I also have my own endpoints and a security configuration. As this setting are applied in a given order, some rules got overwritten.
Is there an easy way to list the effective HttpSecurity in the application?
The easiest way would be if there would be an actuator endpoint, an "extended version of" /mappings, which would list the effective access rules for each mapping.
You have the Option to use Spring Boot Actuator, see here http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready.html. You have to paste it in your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
The Endpoints like /autoconfig, /beans, etc. might help you.
Another solution is to obtain the SecurityFilerChain:
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
FilterChainProxy fcp = (FilterChainProxy) ctx.getBean("springSecurityFilterChain");
// obtain information and log...
}