After enabling Zuul proxy for the Spring boot app (We have set of miroservices), spring mvc interceptor doesnot work - spring-boot

Below is the Application.java. It has code to invoke the interceptor
#EnableEurekaClient
#SpringBootApplication
#EnableZuulProxy
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public TokenValidateInterceptor tokenValidateInterceptor() {
TokenValidateInterceptor localeChangeInterceptor = new TokenValidateInterceptor();
System.out.println("In WEB MVC Intereptor, Interceptor returned");
return localeChangeInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("In WEB MVC Intereptor");
// registry.addInterceptor(tokenValidateInterceptor()).addPathPatterns("/");
registry.addInterceptor(tokenValidateInterceptor()).addPathPatterns("/api/**");
// registry.addInterceptor(new
// TokenValidateInterceptor()).addPathPatterns("/api/**");
}
}
Below is the snippet of the interceptor code:
#Component
public class TokenValidateInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOG = Logger.getLogger(TokenValidateInterceptor.class);
// before the actual handler will be executed ..
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String apikey = request.getHeader("apikey");
// if (LOG.isDebugEnabled()) {
LOG.info("#### Starting TokenValidateInterceptor.preHandle ####");
LOG.info("apikey-->" + apikey);
// }
if (StringUtils.isBlank(apikey) || apikey == null || apikey.isEmpty()) {
return true;
}
}
}
But the call does not reach prehandle of the interceptor.

AFAIK, all requests that are defined in Zuul routing are handled by ZuulServlet.
Spring MVC doesn't handle these requests, so any Spring HandlerInterceptor will not be called for these requests. If you need any preprocessing for API requests, you should implement it in Zuul prefilter or servlet filter.

Related

Can not get user info with Spring Security SAML WITHOUT Spring Boot

I´m working on SAML integration in an older project but I can´t get the user information.
I've guided me with the response of this question:
https://stackoverflow.com/questions/70275050/spring-security-saml-identity-metadata-without-spring-boot
The project has these versions:
spring framework 5.3.24
spring security 5.6.10
opensaml 3.4.6
This is my code:
#Configuration
public class SAMLSecurityConfig {
private static final String URL_METADATA = "https://auth-dev.mycompany.com/app/id/sso/saml/metadata";
#Bean("samlRegistration")
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(URL_METADATA)
.registrationId("id")
.build();
return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration);
}
}
#EnableWebSecurity
public class WebSecurity {
#Configuration
#Order(2)
public static class SAMLSecurityFilter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.saml2Login(Customizer.withDefaults())
.antMatcher("/login/assertion")
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
}
#Controller("loginController")
public class BoCRLoginController {
#RequestMapping(value = "/login/assertion", method = {RequestMethod.POST},
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> assertLoginData(#AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
System.out.println(principal); //here I get a null
return new ResponseEntity<>(HttpStatus.OK);
}
}
Once I did the login on okta the class: Saml2AuthenticatedPrincipal comes null value.
Could you help me to know why I received null value on the object Saml2AuthenticatedPrincipal where suppose have to receive the user information?

spring boot login check implement way

My way of implementing login check
#Configuration
public class ViewControllerImpl implements WebMvcConfigurer {
#Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer adapter = new WebMvcConfigurer() {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new InterceptorConfig()).addPathPatterns("/**")
.excludePathPatterns("/login", "/", "/session", "/static/**");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
WebMvcConfigurer.super.addResourceHandlers(registry);
}
};
return adapter;
}
}
public class InterceptorConfig implements HandlerInterceptor
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
Object loginStatus = request.getSession().getAttribute("loginStatus");
if(loginStatus == "success"){
return true;
}
else {
// request.getRequestDispatcher("login").forward(request, response);
response.sendRedirect("/login");
return false;
}
}
}
After the username and password are successfully verified,I will store a loginstatus in the session.
if loginstatus is success, it means that you have logged in.
Is this code implementation safe?
Do I need to use spring security?
Like #m-deinum already said you should really just utilize Spring-Security. There is a super quick tutorial on how to do this here: https://spring.io/guides/gs/securing-web/
You will start to learn it with BasicAuth and an InMemoryDB for user and password storage. You will end up with an application that returns a status Code 200 for logged in user or a status code 401 for a denied access.
From there on it is easy to extend your application to user user and password stored in a custom db or even OAuth.
Have no fear using it - its easy and fun :)

Springboot Jersey - Custom Filter with ServletProperties.FILTER_STATIC_CONTENT_REGEX

I have a Springboot Application running with a Jersey config line property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, "/mywebpage/.*") to enable rendering static html/js/css/image... content for that specific path prefix. All is fine and mywebpage is loading perfectly on /mywebpage/index.html.
Now after running mywebpage for a few months, we want to redirect a percentage of users (those enabled for a beta) to a new webpage (say https://stackoverflow.com/). So I'm trying to write a Filter for the path /mywebpage/index.html to redirect the user to the new page if they are enabled for the beta. Now the part that stumped me, is that for some reason, no filters are being invoked for any calls to /mywebpage/.* (calls made by the browser to get the html/js/css/... content) (checked with breakpoints and dummy logs). I assume the suspect would be the property ServletProperties.FILTER_STATIC_CONTENT_REGEX.
I already have the bit to compute whether the user is beta enabled, and just need to fit it in the Filter.
Here's what I was trying:
#Provider
public class RedirectionFilter implements ContainerRequestFilter {
#Autowired
private BetaAudienceService betaAudienceService;
#Context
private UriInfo info;
private static final Logger log = LogManager.getLogger(getClass());
#Override
public void filter(ContainerRequestContext request) throws IOException {
log.info("Test Log :: Path=" + request.getUriInfo().getAbsolutePath().getPath());
if (request.getUriInfo().getAbsolutePath().getPath().equals("/mywebpage/index.html") && isBetaEnabled(request)) {
try {
request.abortWith(Response.temporaryRedirect(new URI("https://stackoverflow.com/")).build());
} catch (URISyntaxException e) {
throw new IOException("Failed to build or respond with Redirection URI", e);
}
}
}
private boolean isBetaEnabled(ContainerRequestContext request) { ... }
}
and
public class JerseyConfig extends ResourceConfig {
#Autowired
private ApplicationContext appCtx;
#PostConstruct
public void setup() {
register(A.class);
register(B.class);
...
}
public JerseyConfig() {
property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, Constants.STATIC_CONTENT_PATH);
}
}
Any suggestions on how I can get around this? Or probably is my approach wrong altogether?
I was able to figure this out.
In this case, Jersey is set to run as a Filter (since we need to use ServletProperties.FILTER_STATIC_CONTENT_REGEX) and Jersey handles all the service requests except for the specific paths /mywebpage/* where Jersey is configured to ignore the request due to the static content filter property. Hence those ignored/filtered requests are handled by Springboot directly, which means we can just use a Springboot filter.
Here's the code for the Filter:
public class RedirectionFilter extends OncePerRequestFilter /* can also just `implements Filter` instead */ {
public static final String REDIRECT_PATH = "/mywebpage/index.html";
private static final Logger log = LogManager.getLogger(RedirectionFilter.class);
private final MyConfig myConfig;
public UnifiedShellRedirectionFilter(MyConfig myConfig) {
this.myConfig = myConfig;
}
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("Test Log :: Path: " + request.getPathInfo());
if (isBetaEnabled(request)) {
response.sendRedirect(myConfig.getRedirectionEndpoint() /* "https://stackoverflow.com/" */);
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
log.info("Destructing Filter: " + this.getClass().getSimpleName());
}
private boolean isBetaEnabled(HttpServletRequest request) { ... }
}
And put the following in your #Configuration class:
#Bean
// #Autowired
public FilterRegistrationBean registerRedirectionFilter(MyConfig myConfig) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setName(RedirectionFilter.class.getSimpleName());
registrationBean.setFilter(new UnifiedShellRedirectionFilter(myConfig));
registrationBean.addUrlPatterns(RedirectionFilter.REDIRECT_PATH);
return registrationBean;
}

Spring Webflux ErrorHandling - #RestControllerAdvice with #ExceptionHandler or DefaultErrorAttributes?

In Spring Webflux what is the prefered way of Exception Handling?
#RestControllerAdvice comes from Spring MVC whereas DefaultErrorAttributes comes from Spring Webflux.
However, in Spring Webflux someone could use #RestControllerAdvice. What would be the advantages/disadvantages?
#RestControllerAdvice
#RestControllerAdvice
public class ControllerAdvice
{
#ExceptionHandler(Throwable.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Mono<Map<String, Object>> exceptions(Throwable e)
{
return Mono.just(Map.of("message", "bad"));
}
}
Extend DefaultErrorAttributes
#Component
public class ErrorAttributes extends DefaultErrorAttributes
{
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace)
{
var ex = getError(request);
var attributes = new LinkedHashMap<String, Object>();
attributes.put("status", HttpStatus.BAD_REQUEST.value());
attributes.put("message", "bad");
return attributes;
}
}
I want to stay in the reactive world, so I tend more towards DefaultErrorAttributes (which plays well with DefaultErrorWebExceptionHandler in Webflux). However, in #RestControllerAdvice I could also use Mono.just(...).
It is same. Like WebMvc.
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(AnyException.class)
public Mono<EntityResponse<YourModel>> example(AnyException exception) {
return EntityResponse.fromObject(new YourModel()).status(HttpStatus.NOT_FOUND).build();
}
}
In Spring Webflux in case functional routes declaration, you can also implement your own ExceptionHandler instead of DefaultErrorWebExceptionHandler:
class SystemErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
return super.handle(exchange, throwable)
// debug, process
.contextWrite(...);
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
// for all routs
return route(all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> error = getErrorAttributes(request, ErrorAttributeOptions.of());
Throwable t = this.getError(request);
// map exception on response
return ServerResponse.status(status).body(...);
}
}
Then use your implementation of AbstractErrorWebExceptionHandler in the spring configuration with #AutoConfigureBefore(WebFluxAutoConfiguration.class)

Expose/Filter Controller Request Mappings by Port/Connector

I have a relatively simple Spring Boot application that, by default, is secured over SSL on port 9443 using a self-signed certificate, which works great for serving up APIs to, say, a mobile app. However, I would now like to develop an unsecured web application with its own frontend and serve up a subset of the content I allow over SSL.
This is what I've come up with so far, which enables port 8080 over HTTP in addition to port 9443, the latter I've defined in application.properties:
#SpringBootApplication
#ComponentScan
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addAdditionalTomcatConnectors(createWebsiteConnector());
return tomcat;
}
private Connector createWebsiteConnector() {
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(8080);
return connector;
}
}
I am now faced with the task of only exposing endpoints to the 8080 connection, and all of them to 9443. Obviously, the latter currently works by default, but right now 8080 can access everything 9443 can. Ideally, I would like to control access to certain request mappings defined in a "shared" controller that both connections have access to, i.e. something like this:
#RequestMapping(value = "/public", method = RequestMethod.GET)
public List<String> getPublicInfo() {
// ...
}
#HTTPSOnly
#RequestMapping(value = "/secured", method = RequestMethod.GET)
public List<String> getSecuredInfo() {
// ...
}
I assume something like what I have above isn't actually possible, but does anyone know how I could achieve the same effect?
Thanks in advance for any help!
Alright, I think I actually managed to solve this myself, but I'm open to other suggestions if anyone thinks they have a better solution:
#SpringBootApplication
#ComponentScan
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addAdditionalTomcatConnectors(createWebsiteConnector());
return tomcat;
}
private Connector createWebsiteConnector() {
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(8080);
return connector;
}
private static HashSet<String> uriWhitelist = new HashSet<>(4);
static {
// static website content
uriWhitelist.add("/");
uriWhitelist.add("/index.html");
uriWhitelist.add("/logo_48x48.png");
// public APIs
uriWhitelist.add("/public");
}
private static class PortFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request instanceof RequestFacade) {
RequestFacade requestFacade = (RequestFacade) request;
if (requestFacade.getServerPort() != 9443 && !uriWhitelist.contains(requestFacade.getRequestURI())) {
// only allow unsecured requests to access whitelisted endpoints
return;
}
}
filterChain.doFilter(request, response);
}
}
#Bean
public FilterRegistrationBean portFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
PortFilter filter = new PortFilter();
filterRegistrationBean.setFilter(filter);
return filterRegistrationBean;
}
}

Resources