I'm currently writing an application that issues a JWT token on demand.
When the token is issued, the user should be redirected to a webpage. This works like a charm - but I need to set an authorization header for that redirect.
The user enters his credentials on Webpage A. Webpage A sends a POST Request to Server B. Server B checks the credentials and offers a token. Now the user should be redirected to Webpage C.
I tried the following:
#RequestMapping(value = "/token", method = RequestMethod.POST, produces = "application/json", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Object> token(
#RequestParam("user") String _username,
#RequestParam("secret") String _secret
) throws Exception
{
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("user", _username);
map.add("secret", _secret);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
HttpStatus statusCode = HttpStatus.FOUND;
HttpHeaders httpHeaders = new HttpHeaders();
try {
ResponseEntity<String> request = restTemplate.exchange(_url, HttpMethod.POST, entity, String.class);
} catch (Exception ex) {
ex.printStackTrance();
}
String response = request.getBody();
JSONObject _tokenObject = new JSONObject(response);
String _token = _tokenObject.getString("access_token");
httpHeaders.add("Authorization", "Bearer: " + _token);
URI _redirectUri = new URI("http://foo.example.com/webpageC");
httpHeaders.setLocation(_redirectUri);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
The redirect works, but only /token gets the Authorization Header as Response Header, right before the redirect happens.
How can I achieve that the header is sent to Webpage C?
Thanks.
Update
A forward: is not possible, as Webpage C is on another URL and not in the same Controller.
Anyone has an Idea how to solve?
Typically, we let the frontend developers handle the redirections. If you work on the backend, you could offer a restful API to issue JwtTokens. The frontend will worry about how to carry the Authorization header in the following redirected Http requests. Here is a simple login controller using mobile and password in exchange for the JwtToken.
#RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(#RequestBody Map<String, String> loginMap) {
User user = userService.findByMobile(mobile);
if(user == null || !user.getPassword().equals(password)) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}else {
String token = jwtUtils.createJwt(user.getId(), user.getUsername(), map);
return new Result(ResultCode.SUCCESS,token);
}
}
If you, as the backend, wish to handle the redirection anyway, redirect the request to a webpage with the token as a parameter, in this case:
GET http://www.example.com/login/success?token=xxx&redirectUrl=%2Fxxx
The related backend code would be:
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Optional<String> redirectUri = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME)
.map(Cookie::getValue);
if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) {
throw new BadRequestException();
}
String targetUrl = redirectUri.orElse(getDefaultTargetUrl());
String token = tokenProvider.createToken(authentication);
return UriComponentsBuilder.fromUriString(targetUrl)
.queryParam("token", token)
.build().toUriString();
}
Again, let the frontend put the token into the further request as the authorization header.
Keep in mind, you are returning a response so you can set the response header. You don't get to set the request header of the next request for the frontend.
Reference:
https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-2/
Related
I'm experiencing some troubles with a simple matter.
I'm trying to send a request to other REST service
//getting restTemplate from RestTemplateBuilder.build()
//endpoint and rest of variables came in properties
Map<String, String> map = new HashMap<>();
map.put("app", app);
map.put("username", username);
map.put("password", password);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String token = restTemplate.postForObject(loginEndpoint, headers, String.class, map);
And I recive:
Unexpected error occurred in scheduled task.
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
The weird thing, when I use a simple CURL call and works smooth.
Already checked the variables and endpoint, and it's correct.
In this case, endpoint must have appropiate placeholders on end point url.
I made this method to do it easy:
private String placeHolders(Map<String, String> values){
String response = "?";
boolean first = true;
for(Map.Entry<String, String> entry:values.entrySet()){
if(first){
first = false;
}else{
response+="&";
}
response+=entry.getKey()+"="+entry.getValue();
}
return response;
}
And the call now Is:
String token = restTemplate.postForObject(loginEndpoint+placeHolders, headers, String.class, map);
I was able to get my access tokens from my local server but need to use the access token retrieved from the signup get controller in the signup post controller. How do I get the "token" variable into the signup post controller to use in HTTP calls?
// Initial mapping for visiting the form
#RequestMapping(value = "/signup", method= RequestMethod.GET)
public String signupForm(#RequestParam("code") String code, #ModelAttribute("signup") emailInput form)
throws JsonProcessingException, IOException {
ResponseEntity<String> response = null;
System.out.println("Authorization Code: " + code);
RestTemplate restTemplate = new RestTemplate();
// Client id and secret replaced
String credentials = "clientId:clientSecret";
String encodedCredentials = new String(Base64.encodeBase64(credentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + encodedCredentials);
HttpEntity<String> request = new HttpEntity<String>(headers);
String access_token_url = "https://idfed.constantcontact.com/as/token.oauth2";
access_token_url += "?code=" + code;
access_token_url += "&redirect_uri=http://localhost:8080/signup";
access_token_url += "&grant_type=authorization_code";
response = restTemplate.exchange(access_token_url, HttpMethod.POST, request, String.class);
System.out.println("Full Access Token Response: " + response.getBody());
// Get the Access Token From the recieved JSON response
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(response.getBody());
String token = node.path("access_token").asText();
System.out.println("Here is my token: " + token);
return "/signup";
}
// What happens after the user presses the submit button
Second Controller where I need the variable to use in calls. I need it here so that I can use form variables collected from the HTML page to make calls after the user posts the form data.
#RequestMapping(value = "/signup", method= RequestMethod.POST)
public String signupSubmit(#ModelAttribute("signup") emailInput form, RedirectAttributes redirectAttributes)
throws IOException {
// Use access token here to make calls to endpoints
redirectAttributes.addFlashAttribute("email", form.getEmail());
return "redirect:/result";
}
I am working on a Java desktop application and after some search I was able to authenticate the user using RestTemplate. Now the situation is I have the cookie String at the desktop side(code given below). Now what I would like to do is to do two important things, get which user logged in using that cookie and access(GET,POST,DELETE) secured resources which are marked with #Secured or #PreAuthorize annotation.
here is my authentication code :
#Override
public void initialize(URL location, ResourceBundle resources) {
submitButton.setOnAction(event -> {
if(!(usernameField.getText().isEmpty() && passwordField.getText().isEmpty())){
try {
RestTemplate rest = new RestTemplate();
String jsessionid = rest.execute("http://localhost:8080/j_spring_security_check", HttpMethod.POST,
new RequestCallback() {
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
request.getBody().write(("j_username=" + usernameField.getText() + "&j_password=" + passwordField.getText()).getBytes());
}
}, new ResponseExtractor<String>() {
#Override
public String extractData(ClientHttpResponse response) throws IOException {
List<String> cookies = response.getHeaders().get("Cookie");
// assuming only one cookie with jsessionid as the only value
if (cookies == null) {
cookies = response.getHeaders().get("Set-Cookie");
}
String cookie = cookies.get(cookies.size() - 1);
System.out.println("Cookie is "+cookie);
int start = cookie.indexOf('=');
int end = cookie.indexOf(';');
return cookie.substring(start + 1, end);
}
});
// rest.put("http://localhost:8080/rest/program.json;jsessionid=" + jsessionid, new DAO("REST Test").asJSON());
} catch (AuthenticationException e) {
System.out.println("AuthenticationException");
}
} else {
System.out.println("Fields are empty");
}
});
}
Output of program is :
DEBUG: org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/j_spring_security_check"
DEBUG: org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/j_spring_security_check" resulted in 302 (Found)
Cookie is JSESSIONID=903B2924CCC84421931D52A4F0AA3C7E; Path=/; HttpOnly
If I was on server-side, I would have simply called the below method to get the currently authenticated user :
#Override
public Person getCurrentlyAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
} else {
return personDAO.findPersonByUsername(authentication.getName());
}
}
How can I get the currently authenticate user on desktop based java app so I can just use below method and authenticate on desktop java app. :
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication authentication = new UsernamePasswordAuthenticationToken(person1, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
This way, I can use #Secured annotations for the desktop java app as well. Thanks a lot.
Update
So on the server side I have created a method which gives me the logged in user. As suggested in an answer, I can use the same rest template, but I would like to store the cookie in users local db instead of passing the Resttemplates object around when user clicks here and there.
Server side method :
#Secured("ROLE_USER")
#RequestMapping(value = "/rest/getloggedinuser", method = RequestMethod.GET)
public
#ResponseBody
ResponseEntity<RestPerson> getLoggedInRestUser() {
Person person = this.personService.getCurrentlyAuthenticatedUser();
RestPerson restPerson = new RestPerson();
restPerson.setFirstname(person.getFirstName());
restPerson.setUsername(person.getUsername());
restPerson.setPassword("PROTECTED");
return new ResponseEntity<RestPerson>(restPerson, HttpStatus.OK);
}
Now, next for now, I am trying to use the same RestTemplate to check if this method works with code below, but I would really like to know how I can do this with just a cookie :
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", cookie);
HttpEntity requestEntity = new HttpEntity(null, requestHeaders);
ResponseEntity rssResponse = rest.exchange(
"/rest/getloggedinuser",
HttpMethod.GET,
requestEntity,
Person.class);
String rssResponseBody = (String)rssResponse.getBody();
System.out.println("Response body is ");
Is there a way to cast the Object in the ResponseBody to the Person object???
If you want to get some user information which is stored on the server-side, you should create a new service, for example "getUserInformation" on your server, which will provide such information.
You should not extract cookies manually, just reuse the same RestTemplate, it stores cookies internally (specifically in the underlying HttpClient). That's how you can reach secure resources.
UPDATE:
You don't need to pass around the RestTemplate, just make it a singleton and use it everywhere.
And rssResponse.getBody(); should return you a Person object, not String.
I struggled with an extrange spring behavior using RestTemplate (org.springframework.web.client.RestTemplate) without success.
I use in my hole application below code and always receive an XML response, which I parse and evaluate its result.
String apiResponse = getRestTemplate().postForObject(url, body, String.class);
But can't figure out why a server response is in JSON format after executing:
String apiResponse = getRestTemplate().getForObject(url, String.class);
I've debugged at low level RestTemplate and the content type is XML, but have no idea why the result is in JSON.
When I access from a browser the response is also in XML, but in apiResponse I got JSON.
I tried many options after reading Spring documentation
http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/web/client/RestTemplate.html
Also tried to modify explicitly the headers but still can't figure it out.
I debugged RestTemplate class and noticed that this method is always setting application/json:
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (responseType != null) {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canRead(responseType, null)) {
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
}
if (!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
if (logger.isDebugEnabled()) {
logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
Could you give an idea?
I could solve my issue with RC.'s help. I'll post the answer to help other people.
The problem was that Accept header is automatically set to APPLICATION/JSON so I had to change the way to invoke the service in order to provide the Accept header I want.
I changed this:
String response = getRestTemplate().getForObject(url, String.class);
To this in order to make the application work:
// Set XML content type explicitly to force response in XML (If not spring gets response in JSON)
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = getRestTemplate().exchange(url, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();
I'm using Spring Restful web service & having request body with request header as shown below:
#RequestMapping(value = "/mykey", method = RequestMethod.POST, consumes="applicaton/json")
public ResponseEntity<String> getData(#RequestBody String body, #RequestHeader("Auth") String authorization) {
try {
....
} catch (Exception e) {
....
}
}
I want to pass one more optional request header called "X-MyHeader". How do I specify this optional request header in Spring rest service?
Also, how do I pass this same value in response header??
Thanks!
UPDATE: I just found that I can set required=false in request header, so one issue is resolved. Now, the only issue remaining is how do I set the header in the response??
Use required=false in your #RequestHeader:
#PostMapping("/mykey")
public ResponseEntity<String> getData(
#RequestBody String body,
#RequestHeader(value = "Auth", required = false) String authorization) {}
This question is answered here:
In Spring MVC, how can I set the mime type header when using #ResponseBody
Here is a code sample from: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-httpentity
#RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
byte[] requestBody = requestEntity.getBody();
// do something with request header and body
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}