I am trying to call an oauth API, this call gets made by my main API internally,
I have created a resttemplate class -
#EnableOAuth2Client
#Configuration
public class MonkeyRestTemplate {
#Autowired
Environment env;
public OAuth2RestTemplate oauth2RestTemplate() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setAccessTokenUri(env.getRequiredProperty("monkey.api.accessToken.url"));
clientCredentialsResourceDetails.setClientId(env.getRequiredProperty("monkey.api.client.id"));
clientCredentialsResourceDetails.setClientSecret(env.getRequiredProperty("monkey.api.client.secret"));
return new OAuth2RestTemplate(clientCredentialsResourceDetails);
}
}
and here's my controller class -
#RestController
#RequestMapping("/monkeyinfo")
public class MonkeyApiService {
#Autowired
private MonkeyRestTemplate oauth2RestTemplate;
#Autowired
private Environment env;
#RequestMapping(value = "/oauth2", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers="Accept=application/json" )
public MonkeyMain getProducts(#RequestBody String holder) {
ResponseEntity<MonkeyMain> forEntity = oauth2RestTemplate.oauth2RestTemplate().getForEntity(env.getProperty("monkey.bananainfo.POST.uri"),
MonkeyMain.class);
System.out.println(forEntity.getBody());
return forEntity.getBody();
}
}
MonkeyMain.class is my main model class to Marshall/Unmarshall Json body.
but when this API gets called, I am getting following warning with 403 forbidden Status -
Warning RequestContextHolder has a NULL RequestContext, therefore we are returning a NullRequestContext, therefore not all features may work as desired.
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
Please guide.
Related
I created a spring boot app with a controller layer, a service layer and a persistance layer. And i used spring security with JWT to provide the login process and to manage http sessions. I injected the service in the controller as follows :
#RestController
#ComponentScan(basePackages ={"com.example.service"})
#RequestMapping("/releve")
#CrossOrigin(origins = "http://192.168.1.13:4200")
public class ReleveController {
#Autowired
ReleveService releveService;
#Autowired
#Qualifier("authenticationManagerBean")
AuthenticationManager authenticationManager;
#Autowired
PasswordEncoder encoder;
#PreAuthorize("hasRole('ADMIN')")
#GetMapping(value="/listNiveaux")
public List<Niveau> listNiveaux() {
return releveService.listNiveaux();
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/modifNiveaux",consumes = "application/json")
public void modifNiveaux(#RequestBody Niveau niveau) {
releveService.modifNiveaux(niveau);
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/delNiveaux",consumes = "application/json")
public void delNiveaux(#RequestBody Niveau niveau) {
releveService.delNiveaux(niveau);
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/sauvegardeNiveau")
public void sauvegardeNiveau() {
releveService.sauvegardeNiveau();
}
}
Here are the annotations that i used in my service :
#Service(value = "ReleveService")
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
#EnableJpaRepositories(basePackages="com.example.releve.repository", entityManagerFactoryRef="entityManagerFactory")
public class ReleveServiceImpl implements ReleveService {
...
}
The problem is that despite the fact that the scope is session, the service ( or the bean ) is reinstanciated in every REST request : it does not get destroyed however, i used #PostConstruct and #PreDestroy to verify that it does not have the same behavior as the request scope.
How do i solve this ?
PS : i use Angular for the front-end app and there is another controller class for the login web service.
I know this question has been asked here but I can't find an answer. I have a Spring REST controller endpoint that accepts path variables but I keep getting a 404 instead of 200.
Here's my controller method:
#GetMapping("/colorNames/{colorFamily}")
public ResponseEntity<List<ColorNameFamilyDTO>> getColorNamesByColorFamily(#PathVariable String colorFamily)
{
List<ColorNameFamilyDTO> colorInformations = service.getColorNamesByColorFamily(colorFamily.toUpperCase());
return ResponseEntity.ok(colorInformations);
}
and my test is:
#RunWith(SpringRunner.class)
#WebMvcTest(InfoController.class)
public class InfoControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private InfoService service;
#Autowired
private ObjectMapper objectMapper;
#Test
public void testGetColorNamesByFamily() throws Exception
{
List<ColorNameFamilyDTO> colorInformations = new ArrayList<>();
Mockito.when(service.getColorNamesByColorFamily(Mockito.anyString()))
.thenReturn(colorInformations);
mockMvc.perform(get("/colorNames/{colorFamily}", "Blue")
.contentType("text/plain")).andExpect(status().isOk());
}
}
I've tried use param and also specifying the string in the path directly. What's going wrong? I'm using SpringBoot 2.1.3.RELEASE.
Adding a doPrint() shows up this on the console:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /colorNames/Blue
Parameters = {}
Headers = [Content-Type:"text/plain"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.controller.Controller
Method = public org.springframework.http.ResponseEntity<java.util.List<com.dto.ColorNameFamilyDTO>> com.controller.getColorNamesByColorFamily(java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
If there is no endpoint for "/colorNames", you will get HTTP 404. Therefore, check your controller. Your controller class should be marked with #RestController annotation.
You can try to use RequestBuilder object to call controller
requestBuilder = MockMvcRequestBuilders.get("/colorNames/{colorFamily}", "Blue")
.contentType(MediaType.TEXT_PLAIN_VALUE)
.accept(MediaType.APPLICATION_JSON_UTF8);
mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andDo(print())
.andReturn();
print() will state you which api path you are calling
Update :
Please check controller and test classes
#RestController
public class AddDataController {
#Autowired
public AddDataService addDataService;
#GetMapping(value = "colourNames/{pathVar}")
public ResponseEntity dataAdded(#PathVariable String pathVar){
return addDataService.printPathVariable(pathVar);
}
}
and Test class should be
#WebMvcTest(AddDataController.class)
#RunWith(SpringRunner.class)
class AddDataControllerTest {
#MockBean
public AddDataService addDataService;
#Autowired
public MockMvc mockMvc;
#BeforeEach
public void setUp(){
MockitoAnnotations.initMocks(this);
}
#Test
void dataAdded() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/colourNames/{pathVar}", "Blue")
.contentType(MediaType.TEXT_PLAIN_VALUE);
mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
}
Can you share print() response here
I hava a controller like this:
#PostMapping
public ResponseEntity<RestResponse> test(#RequestBody #Valid RestRequest request) {
BodyRs BodyRs = service.setup(request.getBody());
return ResponseEntity.status(HttpStatus.OK).body(RestResponse.builder().body(BodyRs).build());
}
When I call this controller with Request = '{}'(RestRequest not blank), it jump into:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
HolderService holderService;
#Autowired
LogService logService;
#Override
protected ResponseEntity<Object> handleNotValid(WebRequest request) {
RestResponseHeader responseHeader = RestResponseHeader.builder()
.respCode("10")
.respDesc(fieldError.getField() + " " + fieldError.getDefaultMessage())
.build();
RestResponse restResponse = RestResponse.builder()
.header(responseHeader)
.build();
holderService.setRestResponse(restResponse);
logService.log("100");
return ResponseEntity.ok().body(restResponse);
}
How can I Mock LogService, because when I run unit test without starting MQ, it error at this line 'logService.log("100")'.
-I used to Mock it outside but when code jump into RestExceptionHandler, it is not a Mock object anymore.
If you are talking about unit test for RestExceptionHandler, you can either inject the mock, or (preferably in my mind) switch to use constructor autowiring and then it is easier to pass a mock in the constructor:
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
private HolderService holderService;
private LogService logService;
#Autowired
public RestExceptionHandler(HolderService holderService, LogService logService) {
this.holderService = holderService;
this.logService = logService;
}
...
}
If you are talking about integration test on your controller, you can use Springboot's MockBean annotation to create a mocked bean of your logger service
Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html
I have a Rest controller with a Device (Device must be resolvem, I'm using spring-mobile-device) as a Parameter. The unit test gave me a status 415.
Here is the Code of
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequestDto authenticationRequest,
Device device) throws AuthenticationException {
Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String token = this.tokenGenerator.generateToken(userDetails, device);
return ResponseEntity.ok(new AuthenticationResponseDto(token));
}
Unit test
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
res.andExpect(status().isOk());
Well basically I was wrong with my configuration. It is mandatory configure the Web Config for testing in same way that production configuration but are grammatically different. Well I learned a lot about MockMVC config with this problem.
Here's the solution if you want do unit testing with spring mobile.
First Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebTestConfig.class})
#WebAppConfiguration
public class WebTestConfigAware {
#Autowired
private WebApplicationContext context;
protected MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
DeviceResolverRequestFilter deviceResolverRequestFilter = new DeviceResolverRequestFilter();
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(this.springSecurityFilterChain, deviceResolverRequestFilter).build();
}
}
Second class
#Configuration
#EnableWebMvc
#Import({RootTestConfig.class, WebCommonSecurityConfig.class})
public class WebTestConfig extends WebMvcConfigurerAdapter{
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ServletWebArgumentResolverAdapter(new DeviceWebArgumentResolver()));
argumentResolvers.add(new SitePreferenceHandlerMethodArgumentResolver());
}
}
and Test Class
public class AuthenticationControllerTest extends WebTestConfigAware {
#Test
public void testAuthenticationRequest() throws Exception {
AuthenticationRequestDto authentication = new AuthenticationRequestDto();
authentication.setUsername("admin");
authentication.setPassword("Test1234");
String jsonAuthentication = TestUtil.convertObjectToJsonString(authentication);
ResultActions res = mockMvc.perform(post("/auth")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(jsonAuthentication));
res.andExpect(status().isOk());
}
In your test class you are improperly constructing your request
// a couple of issues here explained below
ResultActions res = mockMvc.perform(post("/auth", authentication, device).contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(authentication)));
post("/auth", authentication, device) authentication and device are interpreted as path URI so they are not needed here, your controller URI does not have any path URI variables.
If your intent is to pass 2 objects as the body of the request then you need to modify your test request and your controller request handler. You cannot pass 2 objects as the body of a request, you need to encapsulate both objects in one like
class AuthenticationRequest {
private AuthenticationRequestDto authenticationDto;
private Device device;
// constructor, getters and setters
}
In your controller
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> authenticationRequest(#RequestBody AuthenticationRequest request) throws AuthenticationException {
AuthenticationRequestDto authenticationDto = request.getAuthenticationDto();
Device device = request.getDevice();
// ....
}
Also in you test you need to pass a JSON object string, you are converting it to bytes (this is why you are getting a 415):
// note the change in the TestUtils, the method being called is convertObjectToJsonString (you'll need to add it)
ResultActions res = mockMvc.perform(post("/auth").contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonString(new Authenticationrequest(authentication, device))));