#RepositoryRestController - doesn't work in Spring boot 2 (Spring data rest) - spring

I'm, using SPRING DATA REST . I have this controller.
org.springframework.data.rest.webmvc.RepositoryRestController
import org.springframework.http.HttpMethod
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
#RepositoryRestController
class UserController {
#RequestMapping("/testing")
fun secured(): String {
return "Hello world"
}
#RequestMapping(method = arrayOf(RequestMethod.GET), value = "/scanners")
#ResponseBody
fun getProducers(): ResponseEntity<*> {
return ResponseEntity.ok<Any>("this is just a tedskljdksjdksjkdsjdkskdst")
}
}
I can't access this endpoint, i'm always getting 404 NOT FOUND, when i change to #RestController i can hit the route.

I found solution. If you have users entity, and your endpoint is by default /users, if you need to create new endpoint it must be named like this /users/testing, in that way you don't get 404 error.
#RepositoryRestController
class UserController {
#RequestMapping("/users/testing")
fun secured(): String {
return "Hello world"
}
#RequestMapping(method = arrayOf(RequestMethod.GET), value = "/scanners")
#ResponseBody
fun getProducers(): ResponseEntity<*> {
return ResponseEntity.ok<Any>("this is just a test")
}
}

Related

Convert JWT Authentication Principal to something more usable in spring

I have a spring boot microservice that validates a JWT (issued by a different service) for authentication. It is working nicely, and I can access the JWT details in my controller like so:
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
// MyController.java
#RestController
#RequestMapping("/")
public class MyController {
#GetMapping()
public String someControllerMethod(#AuthenticationPrincipal Jwt jwt) {
int userId = Integer.parseInt(jwt.getClaim("userid"));
...
}
}
That works great. I can extract what I need from the JWT and go on to talk to my database with the correct userid etc.
However I find it a bit tedious to have to use the Jwt type to get these values in each controller. Is there a way I can inject a different type as the #AuthenticationPrincipal?
E.g. my own class which has already extracted what is needed from the JWT, and exposes something like .getUserId() that returns an int?
That would also let me centralise the logic of parsing the claims or throwing exceptions if they are not as expected etc.
UPDATE
After more google spelunking, it seems I have two options
Option1: #ControllerAdvice and #ModelAttribute
As explained in this answer. I can do something like:
import com.whatever.CustomPrincipal; // a basic "data" class with some properties, getters, setters and constructor
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
#ControllerAdvice
public class SecurityControllerAdvice {
#ModelAttribute
public CustomPrincipal customPrincipal(Authentication auth) throws Exception {
CustomPrincipal customPrincipal;
if (auth != null && auth.getPrincipal() instanceof Jwt) {
Jwt jwt = (Jwt) auth.getPrincipal();
String sessionId = jwt.getClaimAsString("sessionid");
int userId = Integer.parseInt(jwt.getClaimAsString("userid"));
customPrincipal = new CustomPrincipal(userId, sessionId);
} else {
// log an error and throw an exception?
}
return customPrincipal;
}
}
and then
import com.whatever.CustomPrincipal;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestController;
#RestController
#ControllerAdvice
public class HelloWorldController {
#GetMapping("/controlleradvice")
public String index(#ModelAttribute CustomPrincipal cp) {
log.info(cp.getUserId());
return "whatever";
}
}
This seems pretty succinct, and neat and tidy. 1 new class with #ControllerAdvice, and bob's your uncle!
Option2: Using jwtAuthenticationConverter()
This answer shows another way to do it, using a "converter", which seems to convert the default Principal from a JWT to a custom object (that extends AbstractAuthenticationToken) that contains the JWT (.getCredentials()) as well as a custom object like CustomPrincipal (or a User class or something).
#EnableWebSecurity
public class SecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt(customizer -> customizer.jwtAuthenticationConverter((new MyPrincipalJwtConvertor())));
return http.build();
}
}
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.Jwt;
public class MyPrincipalJwtConvertor implements Converter<Jwt, MyAuthenticationToken> {
#Override
public MyAuthenticationToken convert(Jwt jwt) {
var principal = new MyPrincipal(Integer.parseInt(jwt.getClaimAsString("userid")), jwt.getClaimAsString("sessionid"));
return new MyAuthenticationToken(jwt, principal);
}
}
#RestController
public class HelloWorldController {
#GetMapping("/converter")
public String converter(#AuthenticationPrincipal MyPrincipal myPrincipal) {
log.info("/converter triggered");
log.info("" + myPrincipal.getUserId());
return "woo";
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class MyPrincipal {
private int userId;
private String sessionId;
}
Option 1 is much simpler it seems.
But Option 2 is nice, as, I have Filter's that run to do additional validation (like validate the session id in the JWT). When that filter runs, when it calls SecurityContext.getContext().getAuthentication().getPrincipal(), it will get the MyPrincipal object, and not have to call Jwt.getClaimAsString() and cast it etc.
I guess I am asking, are there pros and cons to these two approaches I have not considered? Is one of them perhaps bastardising/abusing something in a way it is not meant to be?
Or is it much the same and I should select whichever I prefer?

Spring boot How to add prefix "/api" to certain urls, but not on "/" "/login"

springboot 2.2.4 version
so i was trying to make prefix to every controller on my application
"/api"
i have done by following code
//DispatcherServletCustomConfiguration.java
#Configuration
public class DispatcherServletCustomConfiguration {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public DispatcherServletRegistrationBean dispatcherServletRegistration() {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
dispatcherServlet(), "/api/");
registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
}
but i want to exclude certain urls that returns static resources
such as "/" "/login" "/404page"
those url need to return index.html
however by adding those prefix, "index.html" is mapped to /api/
how can i distinguish url that return static resources(image, html, css)
and api calls that returns json to add prefix
ex) there is too many controller to add requestMapping for each controller
1st solution:
Remove the DispatcherServletRegistrationBean configuration and configure nothing at servlet level.
Use #RequestMapping("/api") on all the controllers at class level. Then you can achieve that the "/api" will be appended at the initial path for all requests and also the "/", "/login" paths can also be called succesfully.
2nd solution:
add url mappings in the config like below:
package com.test.sampleproject;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
#Configuration
public class AppConfig {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#SuppressWarnings("rawtypes")
#Bean
public ServletRegistrationBean axisServletRegistrationBean() {
#SuppressWarnings("unchecked")
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/*");
registration.addUrlMappings("/api/*");
return registration;
}
}
Controller class:
package com.test.sampleproject.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class RetryController {
#GetMapping("/test1")
public String sampleApi1() {
return "test1 api called";
}
#GetMapping("/")
public String sampleApi2() {
return "index";
}
}
The following url's are valid from this config:
localhost:8080/ ---> will hit the "/" path method in
RetryConroller
localhost:8080/api/test1 ---> will hit the "test1" path method in
RetryConroller
localhost:8080/test1 ---> will hit the "test1" path method in
RetryConroller

Get response body from NoFallbackAvailableException in spring cloud circuit breaker resilience4j

I want to call a third party API. I use spring cloud circuit breaker resilience4j.
Here is my service class :
package ir.co.isc.resilience4jservice.service;
import ir.co.isc.resilience4jservice.model.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
#Service
public class EmployeeService {
#Autowired
private RestTemplate restTemplate;
#Autowired
private CircuitBreakerFactory circuitBreakerFactory;
public Employee getEmployee() {
try {
String url = "http://localhost:8090/employee";
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuit-breaker");
return circuitBreaker.run(() -> restTemplate.getForObject(url, Employee.class));
} catch (NoFallbackAvailableException e) {
//I should extract error response body and do right action then return correct answer
return null;
}
}
}
ResilienceConfig:
package ir.co.isc.resilience4jservice.config;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
#Configuration
public class CircuitBreakerConfiguration {
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10)
.minimumNumberOfCalls(10)
.failureRateThreshold(25)
.permittedNumberOfCallsInHalfOpenState(3)
.build();
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(4))
.build();
return factory ->
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(circuitBreakerConfig)
.timeLimiterConfig(timeLimiterConfig)
.build());
}
}
in some situation third party api return ResponseEntity with statusCode = 500 and
body = {"errorCode":"CCBE"}.
response is look like this :
[503] during [POST] to [http://localhost:8090/employee]:[{"errorCode":"CCBE"}]
When I call this API and get internal server error with body, my catch block catchs api response.
In catch block I need retrieve response body and do some actions according to errorCode.
But I can not do this.
How can I extract body in this situation?

Is it possible to serve the swagger root from a sub path as opposed to the applcation context root?

I followed this example swagger configuration but would like to set the swagger root (the path with which the swagger.json is served) to <jersey-context-root>/api-or-some-other-path except that no matter what I pass to the config.setBasePath(some-sub-path); the swagger root is always the jersey app-context root defined in the application.yml file, i.e: spring.jersey.application-pathso it seems the basePath is hard-wired.
Look at your link and the code
this.register(ApiListingResource.class);
That ApiListingResource is the actual resource class that serves up the swagger.json endpoint. If you look at the link, you can see the class is annotated with the path (the {type:json|yaml} determines what type if data you will get back).
#Path("/swagger.{type:json|yaml}")
If you want to change the path, you need to register it differently. What you need to do is use the Resource.builder(ResourceClass) method to get a builder where we can change the path. For example you can do something like this.
Resource swaggerResource = Resource.builder(ApiListingResource.class)
.path("foobar/swagger.{type:json|yaml}")
.build();
Then instead of the the ResourceConfig#register() method, you use the ResourceConfig#registerResource(Resource) method.
this.registerResource(swaggerResource);
Here's a complete test using Jersey Test Framework
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ResourceBuilderTest extends JerseyTest {
#Path("/swagger.{type:json|yaml}")
public static class ApiListingResource {
#GET
#Produces("text/plain")
public String get() {
return "Hello World!";
}
}
#Override
public ResourceConfig configure() {
Resource swaggerResource = Resource.builder(ApiListingResource.class)
.path("foobar/swagger.{type:json|yaml}")
.build();
ResourceConfig config = new ResourceConfig();
config.registerResources(swaggerResource);
return config;
}
#Test
public void testIt() {
Response response = target("foobar/swagger.json")
.request()
.get();
String data = response.readEntity(String.class);
System.out.println(data);
assertEquals("Hello World!", data);
}
}

Request Mapping not working in Spring Boot

I have developed a simple web page using Spring Boot and I ma using combination of Class and Method Level Request Mapping Annotation but its not working under the below scenario.
Working when i hit http://localhost:9999/products
Not working when i hit http://localhost:9999/home/products
Controller Class:
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping(value= {"/","/home/"})
public class MainController {
#RequestMapping(value = {"products"},method=RequestMethod.GET)
public String index()
{
return "Home.html";
}
}
#RequestMapping(value= {"/","home"})
Make sure to have your controller class with the same package or in the child package of your main Spring boot application class with annotation #SpringBootApplication, it seems then only it would scan your controllers.
Below should work..
#Controller
#RequestMapping(value= "/home")
public class MainController {
#RequestMapping(value = "/products",method=RequestMethod.GET)
public String index()
{
return "Home.html";
}
}
Add #RestController annotation above your class name.

Resources