i'm trying to test a rest authentification method but i'm stucked in some errors within Postman. The error is about HHTP header it can't reads the message from JSON, the exception message is: HttpMessageNotReadableException
here's the RestController class:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
EmployeRepository employeRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
JwtTokenProvider tokenProvider;
/**
* METHODE D'AUTHENTIFICATION
*
* #param loginRequest
* #return
*/
#PostMapping("/signin")
#ApiImplicitParams(#ApiImplicitParam(name = "Authorization", value = "Bearer token", required = true, dataType = "String", paramType = "header"))
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Optional<Employe> ListEmployees = employeRepository.findByMail(loginRequest.getEmail());
List<Employe> listEmpl = new ArrayList<>();
ListEmployees.ifPresent(listEmpl::add);
if (listEmpl.size() == 1) {
final Employe empl = listEmpl.stream().findFirst().get();
Boolean matches = passwordEncoder.matches(loginRequest.getPassword(),
listEmpl.stream().findFirst().get().getMp());
if (matches.equals(true)) {
if ( empl.getMail()!= null) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
headers.add("Authorization", new JwtAuthenticationResponse(jwt).getAccessToken());
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
} else if (empl.getMp()!= null) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
empl.getMail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
}
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}}
the POSTMAN URI:
http://localhost:8082/api/auth/signin
the message that i get:
{
"timestamp": 1548681879270,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<?> com.Cynapsys.Pointage.Controller.AuthController.authenticateUser(com.Cynapsys.Pointage.Model.LoginRequest)",
"path": "/api/auth/signin"
}
You are missing main data in your question and that is your request body for /signin POST call.
But your error message is giving required hint to solve your issue.
"message": "Required request body is missing: public
org.springframework.http.ResponseEntity
com.Cynapsys.Pointage.Controller.AuthController.authenticateUser(com.Cynapsys.Pointage.Model.LoginRequest)"
You are missing required LoginRequest in your call.
Related
I'm writing a Flutter web project with a Spring boot backend and am really battling with getting the authentication stuff to work.
In flutter web I have a "sign_in" method which receives an email and password and passes it to a repository method which sends a post request to the server. See code below. Currently it appears as if the post never returns as the "done with post" line never prints.
Future<String> signIn(String email, String password) async {
authenticationRepository.setStatus(AuthenticationStatus.unknown());
print('signIn user: email: $email pw: $password');
User user = User('null', email, password: password);
//print('user: $user');
var url;
if (ServerRepository.SERVER_USE_HTTPS) {
url = new Uri.https(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
} else {
url = new Uri.http(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
}
// print('url: $url');
var json = user.toUserRegisterEntity().toJson();
print('Sending request: $json');
// var response = await http.post(url, body: json);
var response = await ServerRepository.performPostRequest(url, jsonBody: json, printOutput: true, omitHeaders: true );
print('Response status: ${response.statusCode}');
print('Response body b4 decoding: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
if (response.statusCode != 201) {
authenticationRepository
.setStatus(AuthenticationStatus.unauthenticated());
throw FailedRequestError('${responseBody['message']}');
}
User user2 = User(
responseBody['data']['_id'], responseBody['data']['email'],
accessToken: responseBody['accessToken'],
refreshToken: responseBody['refreshToken']);
print('user2 $user2');
authenticationRepository
.setStatus(AuthenticationStatus.authenticated(user2));
return responseBody['data']['_id']; // return the id of the response
}
static Future<Response> performPostRequest(Uri url, {String? accessToken, var jsonBody, bool printOutput = false, bool omitHeaders=false} ) async {
var body = json.encode(jsonBody ?? '');
if(printOutput){
print('Posting to url: $url');
print('Request Body: $body');
}
Map<String, String> userHeader = {
HttpHeaders.authorizationHeader: 'Bearer ${accessToken ?? 'accessToken'}',
"Content-type": "application/json",
};
if(omitHeaders){
userHeader = { };
}
print('performing post: ');
var response = await http.post(
url,
body: body,
headers: userHeader,
);
print('done with post?!');
if(printOutput){
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
}
return response;
}
My console output is as follows when attempting the request:
signIn user: email: XXXXXX#gmail.com pw: XXxxXXx500!
Sending request: {email: XXXXXX#gmail.com, password: XXxxXXx500!}
Posting to url: http://localhost:8080/auth/login
Request Body: {"email":"XXXXXX#gmail.com","password":"XXxxXXx500!"}
performing post:
So it seems like the response is never sent by the server.
On my server, using Spring boot security the setup is as follows (I based it from this tutorial). Securityconfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final JWTUtils jwtTokenUtil;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(jwtTokenUtil, authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/auth/login");
http.csrf().disable();
//http.cors(); //tried but still no repsonse
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers( "/auth/**").permitAll(); // no restrictions on this end point
http.authorizeRequests().antMatchers(POST, "/users").permitAll();
http.authorizeRequests().antMatchers(GET, "/users/**").hasAnyAuthority("ROLE_USER");
http.authorizeRequests().antMatchers(POST, "/users/role/**").hasAnyAuthority("ROLE_ADMIN");
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And the filter handling the "/auth/login" end point:
#Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JWTUtils jwtTokenUtil;
private final AuthenticationManager authenticationManager;
#Autowired
public CustomAuthenticationFilter(JWTUtils jwtTokenUtil, AuthenticationManager authenticationManager) {
this.jwtTokenUtil = jwtTokenUtil;
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("attemptAuthentication");
log.info("type "+request.getHeader("Content-Type"));
try {
//Wrap the request
MutableHttpServletRequest wrapper = new MutableHttpServletRequest(request);
//Get the input stream from the wrapper and convert it into byte array
byte[] body;
body = StreamUtils.copyToByteArray(wrapper.getInputStream());
Map<String, String> jsonRequest = new ObjectMapper().readValue(body, Map.class);
log.info("jsonRequest "+jsonRequest);
String email = jsonRequest.get("email");
String password = jsonRequest.get("password");
log.info("jsonRequest username is "+email);
log.info("jsonRequest password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
} catch (IOException e) {
e.printStackTrace();
}
//if data is not passed as json, but rather form Data - then this should allow it to work as well
String email = request.getParameter("email");
String password = request.getParameter("password");
log.info("old username is "+email);
log.info("old password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("successfulAuthentication");
User user = (User) authResult.getPrincipal();
String[] tokens = jwtTokenUtil.generateJWTTokens(user.getUsername()
,user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())
, request.getRequestURL().toString() );
String access_token = tokens[0];
String refresh_token = tokens[1];
log.info("tokens generated");
Map<String, String> tokensMap = new HashMap<>();
tokensMap.put("access_token", access_token);
tokensMap.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
log.info("writing result");
response.setStatus(HttpServletResponse.SC_OK);
new ObjectMapper().writeValue(response.getWriter(), tokensMap);
}
}
When I try the "auth/login" endpoint using postman, I get the correct response with the jwt tokens. See below:
I'm really stuck and have no idea how to fix it. I've tried setting cors on, changing the content-type (which helped making the server see the POST request instead of an OPTIONS request). Any help/explanation would be greatly appreciated.
After lots of trial and error I stumbled across this answer on a JavaScript/ajax question.
It boils down to edge/chrome not liking the use of localhost in a domain. so, if you're using a Spring Boot server, add the following bean to your application class (remember to update the port number):
#Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:56222"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Origin","Access-Control-Allow-Origin",
"Content-Type","Accept","Authorization","Origin,Accept","X-Requested-With",
"Access-Control-Request-Method","Access-Control-Request-Headers"));
corsConfiguration.setExposedHeaders(Arrays.asList("Origin","Content-Type","Accept","Authorization",
"Access-Control-Allow-Origin","Access-Control-Allow-Origin","Access-Control-Allow-Credentials"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET","PUT","POST","DELETE","OPTIONS"));
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
I have a request interceptor config for my feign client that i will like to verify if it is configured properly. It is suppose to make request to the auth url and get a authorization taken.
This seems to work fine. But i think its not putting it to every request sent to to the resource server. Hence i keep getting 403. but when i try this on postman with the auth token generated in my code it works fine.
Bellow is the code
#Component
public class FeignC2aSystemOAuthInterceptor implements RequestInterceptor {
#Value("${c2a.oauth2.clientId}")
private String clientId;
#Value("${c2a_system.authUrl}")
private String authUrl;
#Value("${c2a.oauth2.clientSecret}")
private String clientSecret;
private String jwt;
private LocalDateTime expirationDate = LocalDateTime.now();
private final RestTemplate restTemplate;
public FeignC2aSystemOAuthInterceptor(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public void apply(RequestTemplate requestTemplate) {
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
System.out.println("JUST AFTER REQUEST" + this.jwt);
}
/* use the token */
System.out.println("USE THE TOKEN" + this.jwt);
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
private void requestToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("client_id", clientId);
map.add("client_secret", clientSecret);
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<C2AAuthResponse> response = restTemplate.postForEntity(authUrl, request, C2AAuthResponse.class);
this.jwt = Objects.requireNonNull(response.getBody()).getAccessToken();
LocalDateTime localDateTime = LocalDateTime.now();
this.expirationDate = localDateTime.plusSeconds(response.getBody().getExpiresIn());
}
config
#Configuration
public class FeignC2aSystemConfig {
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
};
#Bean
FeignC2aSystemOAuthInterceptor fen () {
return new FeignC2aSystemOAuthInterceptor(getRestTemplate());
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and client
#FeignClient(name = "c2aSystem", url = "${c2a_system.base_url}", configuration = FeignC2aSystemConfig.class)
public interface C2AApiClient {
#PostMapping(value = C2ASystemIntegrationUrls.SEND, produces = "application/json", consumes = "application/json")
HttpServletResponse sendSms(#RequestBody C2aMessage c2aMessage);
#GetMapping(value = C2ASystemIntegrationUrls.GETLIST, produces = "application/json", consumes = "application/json")
List<MessageData> getMessages();
}
during logging i have noticed that it i call the interceptor and i can see the auth token logged using sout.
Please i would like to know if i have made a mess somewhere along the way that might cause it not to apply the authorization token to the request, thanks
You're using the RequestTemplate API wrong in this line:
requestTemplate.header("Authorization: Bearer " + this.jwt);
the header method accepts 2 parameters. First a key and then the corresponding value, and there's an overload with a String vararg. Your code will complile because of the varag parameter but won't work because it'll be handled as an empty array argument.
The implementation in the RequestTemplate is clear. If the array is empty, it'll consider that header for removal.
The fix is easy, just put the JWT token into the second argument instead of concatenating it with the header key, like this:
requestTemplate.header("Authorization: Bearer ", this.jwt);
The service provider supplies me with the header data: grant_type, Content-Type. And the body data: grant_type, username and password. Which I use in Postman where it generates OK token. But in the Spring application it generates an error HttpClientErrorException $ BadRequest: 400 Bad Request.
I have the class to set the body data:
public class BodyToken {
private String grant_type = "password";//set body data
private String username = "User";//set body data
private String password = "123";//set body data
private String access_token;
#JsonGetter("access_token")
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
#JsonGetter("grant_type")
public String getGrant_type() {
return grant_type;
}
#JsonGetter("username")
public String getUsername() {
return username;
}
#JsonGetter("password")
public String getPassword() {
return password;
}
}
This is the controller where the header data is set:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
HttpEntity request = new HttpEntity(headers);
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}
OK was solved with using the Class MultiValueMap and LinkedMultiValueMap. The credentials are added to this new object and it is sent together with the request:
#PostMapping("/TokenGeneration")
#ResponseBody
public BodyToken TokenGeneration() throws IOException {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("grant_type", "password");//set header data
headers.set("Content-Type", "application/x-www-form-urlencoded");//set header data
MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();//line solution
body.add("grant_type", "password");//line solution
body.add("username", "user");//line solution
body.add("password", "123");//line solution
HttpEntity request = new HttpEntity(body, headers);//and I add this body to HttpEntity
headers.add("User-Agent", "Spring's RestTemplate" );
ResponseEntity<BodyToken> response = restTemplate.exchange(
"https://sw/token",
HttpMethod.POST,
request,
BodyToken.class
);
try {
return response.getBody();
} catch (Exception e) {
BodyToken body = new BodyToken();
log.info(e.getMessage());
return body;
}
}
Background
I have one REST API which is calling third party rest API using resttemplate which requires csrf-token and cookie for auth,i am hard coding the same csrf-token in my local rest API and trying to hit the controller url but its failing...
I have set csrf-token and cookie fetched from the web for auth but its giving me No CSRF token was found.
public HttpHeaders buildHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("csrf_token", csrf_token);
headers.set("Cookie", cookie);
return headers;
}
postman response
{
"data": {
"ArchiveUser": null
},
"errors": [
{
"message": "No CSRF token was found!",
"locations": [
{
"line": 1,
"column": 39
}
],
"path": [
"ArchiveUser"
]
}
]
}
Controller
#RestController
#RequestMapping("/*")
public class UserController {
public static final Logger logger = LoggerFactory.getLogger(UserController.class);
#Autowired
UserService userService;
#RequestMapping(value="/create" ,method=RequestMethod.POST,consumes= {MediaType.APPLICATION_JSON_VALUE},produces= {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> createUser(#RequestBody CreateUserRequest createUserRequest){
return userService.createUser(createUserRequest);
}
}
UserService
#Value("${admin.csrf_token}")
private String csrf_token;
#Value("${admin.Cookie}")
private String cookie;
public ResponseEntity<?> createUser(CreateUserRequest createUserRequest) {
createUserRequest.setQuery(createQueryString);
Gson g = new Gson();
logger.debug(g.toJson(createUserRequest));
HttpEntity<CreateUserRequest> requestEntity = new HttpEntity<CreateUserRequest>(createUserRequest,
buildHttpHeaders());
logger.debug("httpenetity" + requestEntity);
ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity,
String.class);
return response;
}
public HttpHeaders buildHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("csrf_token", csrf_token);
headers.set("Cookie", cookie);
return headers;
}
yml input
admin:
csrf_token: xxxxx
Cookie: _csrf=xxxxxx
I am badly stuck here.
this issue is resolved after re-lloking on my code,there was variable name mismatch
I have a rest controller that requires the user to be authenticated. But when the test is run the response is always 401. I am using the "WithSecurityContext" annotation, but it does not work.
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
#WithSecurityContext(factory = WithOAuth2AuthenticationSecurityContextFactory.class)
public #interface WithOAuth2Authentication {
String clientId() default "temporal";
String username() default "username";
String[] scopes() default { "read", "write", "trust" };
}
Class implementing annotation
public class WithOAuth2AuthenticationSecurityContextFactory implements WithSecurityContextFactory<WithOAuth2Authentication> {
#Override
public SecurityContext createSecurityContext(WithOAuth2Authentication annotation) {
Set<String> scopes = new HashSet<>();
Collections.addAll(scopes, annotation.scopes());
OAuth2Request oAuth2Request = new OAuth2Request(null, annotation.clientId(), null, true, scopes, null, null, null, null);
Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, new TestingAuthenticationToken(annotation.username(), null, "read"));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth2Authentication);
return context;
}
}
Unit test
#Before
public void setup() {
this.mapper = new ObjectMapper();
RestDocumentationResultHandler document =
document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()));
this.mock = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.apply(SecurityMockMvcConfigurers.springSecurity())
.alwaysDo(document)
.build();
}
#Test
#WithOAuth2Authentication
public void create() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept-Language", "en");
httpHeaders.add("Content-Type", "application/json");
httpHeaders.add("Accept", "application/json");
String JSON = this.mapper.writeValueAsString(new Register.Project());
this.mock.perform(post("/project/create")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.headers(httpHeaders)
.content(JSON))
.andDo(print())
.andExpect(status().isOk());
}
The result of the test
MockHttpServletResponse:
Status = 401
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate, no-store], Pragma=[no-cache, no-cache], Expires=[0], X-Frame-Options=[DENY], WWW-Authenticate=[Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"], Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
Forwarded URL = null
Redirected URL = null
Cookies = []