-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCallbackController.java
111 lines (102 loc) · 5.41 KB
/
CallbackController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.webfleet.oauth.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.webfleet.oauth.common.Constants;
import com.webfleet.oauth.common.KnownUrls;
import com.webfleet.oauth.common.RandomKey;
import com.webfleet.oauth.common.exception.CallbackException;
import com.webfleet.oauth.service.TokenStoreService;
import com.webfleet.oauth.service.feign.Authserver;
import com.webfleet.oauth.service.feign.OAuthToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttribute;
import javax.servlet.http.HttpSession;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
/**
* Note that according to the spec. this endpoint should only be available over a secure channel (e.g. HTTPS)
*
* @see {@link <a href="https://tools.ietf.org/html/rfc6749#section-10.5">...</a>}
*/
@Controller
@RequestMapping(KnownUrls.CALLBACK)
public class CallbackController extends AbstractMenuController {
private static final Logger LOG = LoggerFactory.getLogger(CallbackController.class);
private final TokenStoreService tokenStoreService;
private final String clientId;
private final String clientSecret;
private final ObjectMapper objectMapper;
private String redirectUri;
private Authserver authserver;
private String scopes;
public CallbackController(final Authserver authserver,
final ObjectMapper objectMapper,
final TokenStoreService tokenStoreService,
@Value("${webfleet.clientid}") final String clientId,
@Value("${webfleet.clientsecret}") final String clientSecret,
@Value("${webfleet.redirecturi}") final String redirectUri,
@Value("${webfleet.scopes}") final String scopes) {
super(KnownUrls.HOME, KnownUrls.SERVICE, KnownUrls.CONSUME);
this.authserver = authserver;
this.objectMapper = objectMapper;
this.tokenStoreService = tokenStoreService;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUri = redirectUri;
this.scopes = scopes;
}
@RequestMapping(params = {"!code"})
public void callbackError(@RequestParam(value = "error") String error,
@RequestParam(value = "error_description", required = false) String errorDescription) {
LOG.error("Authserver returned an error {} [{}]", error, errorDescription);
throw new CallbackException("Authserver returned an error: " + error);
}
@RequestMapping(params = {"code"})
public String callback(Model model,
Principal principal,
HttpSession session,
@RequestParam(value = "code") String code,
@RequestParam("state") String state,
@SessionAttribute("random") RandomKey randomKey
) {
LOG.info("State and random string: {}, {}", state, randomKey.getKey());
// state and random parameters should be the same, this may be used by an integrator to verify the request hasn't been tampered
if (!randomKey.getKey().equals(state)) {
throw new CallbackException("State parameter values don't match, someone might have stolen user's grant. Abort token exchange process.");
}
Map<String, String> params = new HashMap<>();
// we are following oauth authorization code grant flow
// to request a token pair using an auth code flow we provide the obtained authorization code
params.put("grant_type", "authorization_code");
// oauth client identifier
params.put("client_id", clientId);
params.put("client_secret", clientSecret);
// the code we received and we need to provide as indicated with grant_type parameter
params.put("code", code);
// MUST be the same as used to initiate the flow, see https://tools.ietf.org/html/rfc6749#section-10.6
params.put("redirect_uri", redirectUri);
// Obtain token and store it somewhere safely
// This can be executed in a background thread without blocking user's workflow since we dont' require
// further interaction to access APIs on users' behalf
params.put("scope", scopes);
final OAuthToken oAuthToken = this.authserver.token(params);
tokenStoreService.saveRefreshToken(oAuthToken.getRefreshToken(), principal.getName());
// Store access_token in a session attribute for using it while its valid
session.setAttribute(Constants.OAUTH_ACCESS_TOKEN_SESSION_ATTRIBUTE, oAuthToken.getValue());
// Show the token in the UI for demo purposes
try {
model.addAttribute("response", objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(oAuthToken));
addMenu(model);
return KnownUrls.View.CALLBACK.viewName();
} catch (JsonProcessingException e) {
throw new CallbackException("Couldn't parse authserver response", e);
}
}
}