We are Attempting to Migrate Our Existing Spring WebServices App to SpringBoot and ran into an issue for which we seek your advice.
We have a Base Service Servlet that disables the GET on the port that the App is deployed on for Security reasons this servlet returns 501 Unimplemented Response as follows:
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.warn("GET request received!");
response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
}
public abstract class BaseServiceServlet extends HttpServlet {
...
}
public class ServiceServlet extends BaseServiceServlet {
...
}
public class ServletInitializer extends SpringBootServletInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
ServletRegistration.Dynamic application = container
.addServlet("ServiceServlet", new ServiceServlet());
application.setLoadOnStartup(2);
application.addMapping("/*");
}
}
Previously we had an old-fashioned HealthCheck JSP that we implemented. With the move to SpringBoot we are now using the SpringBoot Actuator.
But we find that if we set the Actuator health monitor port to the same one as the App when we try to monitor the health we get the 501 Unimplemented response.
Config is as follows:
# Spring-Boot Embedded Tomcat Config
server.port=8080
server.connection-timeout=10s
server.ssl.enabled=false
## Springboot based health monitor
management.endpoints.enabled-by-default=true
management.endpoint.health.enabled=true
management.endpoint.loggers.enabled=true
management.endpoints.web.cors.allowed-methods=GET
management.endpoint.beans.cache.time-to-live=20s
management.endpoints.web.base-path=/manage
management.server.port=8080
management.security.enabled=false
One way we could get around this problem is if we changed the actuator health check port to something else that Works.
Question:
How can we set the Actuator port to be the same as the App and make the actuator health check url which is something like http://localhost:8080/manage/health not return 501 Unimplemented from the Base Service Servlet ?
We can register DispatcherServlet with health endpoint in mapping before the ServiceServlet.
#SpringBootApplication
public class ServletInitializer extends SpringBootServletInitializer implements ServletContextInitializer {
#Autowired
private DispatcherServlet dispatcherServlet;
#Override
public void onStartup(ServletContext container) throws ServletException {
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", dispatcherServlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/manage", "/manage/health");
ServletRegistration.Dynamic application = container
.addServlet("ServiceServlet", new ServiceServlet());
application.setLoadOnStartup(2);
application.addMapping("/*");
}
}
Related
Dependencies from my pom:
2.2.5.RELEASE for Spring
3.3.5 for CXF
spring-boot-starter
spring-boot-starter-actuator
spring-boot-starter-web
spring-boot-devtools
spring-boot-configuration-processor
spring-boot-starter-tomcat
spring-boot-starter-test
cxf-spring-boot-starter-jaxws
cxf-rt-features-logging
Here are the server settings defined in the application.yml:
server:
port: 8080
servlet:
context-path: /cs
The first Servlet is a CXF JAXWS Endpoint configured like so:
// https://github.com/apache/cxf
#Bean(name=Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public IFileNetWSSoap documentService() {
return new DocumentServiceEndpoint();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), documentService());
endpoint.setServiceName(fileNetWS().getServiceName());
endpoint.setWsdlLocation(fileNetWS().getWSDLDocumentLocation().toString());
endpoint.publish(properties.getDocumentEndpoint());
Binding binding = endpoint.getBinding();
((SOAPBinding)binding).setMTOMEnabled(true);
return endpoint;
}
Currently listening at this address: http://localhost:8080/cs/services/document-service_1.0
The second Servlet is javax.servlet.http.HttpServlet (TomCat right now):
#WebServlet(urlPatterns = {"/image-service_1.0"})
public class ImageServiceEndpoint extends HttpServlet {
#Autowired
private BusinessService businessServices;
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
doGet(request, response);
}
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
this.businessServices.imageRetrieval(request, response);
}
}
Currently listening at this address: http://localhost:8080/cs/image-service_1.0
And finally, there is the Spring-Boot Actuator Servlet.
Currently listening at this address: http://localhost:8080/cs/actuator
My question is "How can I configure the WebServlet to listen on the CXF segment without breaking everything?" e.g. http://localhost:8080/cs/services/image-service_1.0
It has been brought to my attention that maybe I should use a Spring MVC Controller in lieu of the Servlet. The only requirement I have for this endpoint id to take in query string parameters and stream binary content back to the caller.
You can't take over the same path as CXF WS Endpoint (default: /services)
I a have working spring security project with vaadin session based on some github project. All is working fine until I create a new configuration static class where I want to specify the path where SSL should be required.
Here is my project and Application class in its original working state:
https://github.com/czetus/dluznikApp/blob/master/src/main/java/com/danes/main/Application.java
Added code to Application.java into first static class
public static class SecurityConfiguriation extends GlobalMethodSecurityConfiguration
#EnableWebSecurity
public static class WebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel()
.antMatchers("/v1*").requiresSecure();
}
}
The project is compiling and deploying wihtout any error. Problem starts when I start localhost:8080. I receive an exception defined here:
https://github.com/czetus/dluznikApp/blob/master/src/main/java/com/danes/main/servlet/VaadinSessionSecurityContextHolderStrategy.java .
java.lang.IllegalStateException: No VaadinSession bound to current thread
at com.danes.main.servlet.VaadinSessionSecurityContextHolderStrategy.getSession(VaadinSessionSecurityContextHolderStrategy.java:41) ~[classes/:na]
at com.danes.main.servlet.VaadinSessionSecurityContextHolderStrategy.clearContext(VaadinSessionSecurityContextHolderStrategy.java:12) ~[classes/:na]
at org.springframework.security.core.context.SecurityContextHolder.clearContext(SecurityContextHolder.java:73) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:180) ~[spring-security-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
...
If I remove annotation #EnableWebSecurity there is no error and ssl is not working.
When I was debugging I noticed that getSession is invoked too early in method
#Override
public void setContext(SecurityContext context) {
getSession().setAttribute(SecurityContext.class, context);
}
in class VaadinSessionSecurityContextHolderStrategy.java
So what do I have to do or is there some other way to not create this configuration class and get this path (pattern) to be secured by SSL ?
Could you skip using VaadinSessionSecurityContextHolderStrategy? The default Spring security setup with thread local strategy should work.
Edit
Security context is typically stored in the thread that serves the request. Security filter is run before Vaadin servlet gets the request and this means that the session will not exist yet so the security filter cannot use Vaadin session to store the security context.
You can add relevant user data to Vaadin session with session init listener When it gets called the filter has already added user information to the thread local security context holder.
#Component("vaadinServlet")
#WebServlet(urlPatterns = "/*", name = "MyVaadinServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = MyUi.class, productionMode = false)
public class MyVaadinServlet extends SpringVaadinServlet {
private static final Logger logger = LoggerFactory.getLogger(MyVaadinServlet.class);
#Override
protected void servletInitialized() throws ServletException {
getService().addSessionInitListener(this::onServletInit);
super.servletInitialized();
}
private void onServletInit(SessionInitEvent sessionInitEvent) {
SecurityContext securityContextOwnedByFilter = SecurityContextHolder.getContext();
VaadinSession session = sessionInitEvent.getSession();
User user = (User) securityContextOwnedByFilter.getAuthentication().getPrincipal();
session.setAttribute("user", user);
logger.info("User '{}' stored in session '{}'",
user.getUsername(),
session.getSession().getId());
}
}
I am running a Spring 4 web mvc project:
Issue:
My controlleradvice for 404 exception handler is not working. However, if I comment the "addResourceHandlers" method in WebConfig class, it will work. (I can't remove that as it resolves my static resources)_
This is my web config:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* Resource handler for static resources
*/
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
And this is my 404 exception handler:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404(Exception e) {
return "error/404";
}
}
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's a bit more complicated.
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly.
To do so, in the class that you are extending AbstractAnnotationConfigDispatcherServletInitializer override the onStartup method to expose the DispatcherServlet definition and add manually the needed flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//...
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//..
}
Then your existing code within ExceptionController should work and intercept the exception
I have very basic simple Spring Boot Rest application.
I needed to implement custom authentication in Spring Security: for every REST request I need to check username and password, that are in specific headers of every request ("username" and "password").
So I implemented custom AuthEntryPoint:
#Service
public class CustomAuthEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
String username = httpServletRequest.getHeader("username");
String password = httpServletRequest.getHeader("password");
if (!username.equals("admin") || !password.equals("admin")) {
throw new RuntimeException("", new BadCredentialsException("Wrong password"));
}
}
}
So, I realized, that RequestCacheAwareFilter is caching first request and headers are also stored in cache. So if I make a request with wrong pass and then with right one, I will still get an exception.
So, how could I override the CacheAwareFilter or disable it? Or am I doing something totally wrong?
Use custom WebSecurityConfigurerAdapter to set request cache to NullRequestCache:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestCache()
.requestCache(new NullRequestCache());
}
}
I just made the app stateless like here: How can I use Spring Security without sessions?
And now everything is okay.
I am using Spring Security with OAuth2. It's working fine except login success and failure handlers.
Like in spring web security OAuth2 does not have clearly defined success and failure handlers hooks to update DB and set response accordingly.
What filter do I need to extend and what should its position be in the Spring Security filter chain?
Specify successHandler and failureHandler for oauth2login method:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${successUrl}")
private String successUrl;
#Value("${failureUrl}")
private String failureUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.successHandler(successHandler())
.failureHandler(failureHandler());
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
return new SimpleUrlAuthenticationSuccessHandler(successUrl);
}
#Bean
SimpleUrlAuthenticationFailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler(failureUrl);
}
}
Tested for Spring Security 5.0.6
I personally use
#Component
public class MyAuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
System.out.println("Authenticated");
}
}
Additional informations in response can be set by CustomTokenEnhancer
This is a nice tutorial about how to use spring boot with oauth2. Down to the road they show how to configure sso filter by hand:
private Filter ssoFilter(OAuth2Configuration client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
//THIS IS THE PLACE YOU CAN SET THE HANDLER
filter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
return filter;
}
They didn't provide the line you need, here it is.
The success handler and failure handler are defined in the form-login (if you use Spring's XML). It is not different than any other spring-security definitions:
<security:form-login
login-page="/login/login.htm"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/login/login.htm?login_error=1" />
and you can find the handler here.
The "failure handler" is pretty similar.