【问题标题】:Share SPRING_SECURITY_CONTEXT between two applications在两个应用程序之间共享 SPRING_SECURITY_CONTEXT
【发布时间】:2018-04-19 23:24:56
【问题描述】:

我有两个不同的 Spring Boot 应用程序,它们在不同端口(8080、8081)和不同配置(application.yml)上的 localhost 上运行。这些应用程序使用 SSO 和 OAuth 2.0 从授权服务器获取授权令牌。我登录到我的第一个应用程序,获得授权,这里一切正常。现在我需要与第二个 Spring Boot 应用程序(在端口 8081 上)共享这些身份验证详细信息,以授权授权服务器中的第二个应用程序。谷歌搜索并找到了 2 个方法:我可以尝试在两个应用程序之间共享 HttpSession(但我认为这是多余的)或 HttpSessionSecurityContextRepository 为 SecurityContextRepository,这似乎更方便。这里的问题是我无法做到这一点,我仍然不确定在 2 个应用程序之间共享安全上下文是否是个好主意。

我现在尝试了什么:

  1. 通过 GET 请求中的标头从第一个应用程序共享授权令牌(根据授权服务器请求规范定制),但它不起作用 - 第二个应用程序不接受请注意这个令牌。
  2. 将授权 cookie 从第一个应用程序共享到第二个应用程序,但它也不起作用。

我无法在第二个应用程序上通过授权服务器进行授权,因为它可能不是带有 @Controller 的 Spring Boot 应用程序,而是任何其他应用程序没有 HTML 表单,所以我需要先授权应用(带有 UI),获取执行授权请求所需的所有数据并将其传递给第二个应用(第三个、第四个...),以便他们也能够执行授权请求。

提前致谢!

【问题讨论】:

  • 您的授权/资源服务器是另一个应用程序。对吗?您是否请求相同的 client_id 和 client_secret ?
  • 是的,资源服务器是另一个应用程序。从第二个应用程序我请求不同的 client_id 和 client_secret。当然,与第一个应用不同。

标签: spring rest spring-mvc single-sign-on spring-security-oauth2


【解决方案1】:

我假设您的授权/资源服务器是外部应用程序。您可以使用您的第一个应用程序成功登录,因此流程正常。您有两个客户端应用程序具有自己的 client_id、client_secret 等参数。如果这些参数不同,那么授权/资源服务器将为第一个和第二个客户端应用程序返回不同的 bareer token 和 sessionid cookie。否则您需要在授权/资源服务器中对它们进行授权。 我会在用户登录第一个应用程序时提供,然后在后台你也登录第二个应用程序。 为了自动授权第二个应用程序,您可以尝试在第一次应用程序登录成功后使用自己的参数手动为第二个应用程序执行 oauth2 登录流程,并将 cookie 发送到您从 oauth2 登录获得的前端。

对于手动 oauth2 登录,您可以尝试以下代码:

private Cookie oauth2Login(String username, String password, String clientId, String clientSecret) {
    try {
        String oauthHost = InetAddress.getByName(OAUTH_HOST).getHostAddress();
        HttpHeaders headers = new HttpHeaders();
        RestTemplate restTemplate = new RestTemplate();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();

        // Basic Auth
        String plainCreds = clientId + ":" + clientSecret;
        byte[] plainCredsBytes = plainCreds.getBytes();
        byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
        String base64Creds = new String(base64CredsBytes);
        headers.add("Authorization", "Basic " + base64Creds);
        // form param
        map.add("username", username);
        map.add("password", password);
        map.add("grant_type", GRANT_TYPE);
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
                headers);
        // CALLING TOKEN URL
        OauthTokenRespone res = null;
        try {
            res = restTemplate.postForObject(OAUTH_HOST, request,
                    OauthTokenRespone.class);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        Optional<OauthTokenRespone> optRes = Optional.ofNullable(res);
        String accessToken = optRes.orElseGet(() -> new OauthTokenRespone("", "", "", "", "", ""))
                .getAccess_token();
        // CALLING RESOURCE
        headers.clear();
        map.clear();
        headers.setContentType(MediaType.APPLICATION_JSON);
        map.add("access_token", accessToken);
        request = new HttpEntity<MultiValueMap<String, String>>(map, headers);

        Cookie oauthCookie = null;
        if (accessToken.length() > 0) {
            HttpEntity<String> response = restTemplate.exchange(
                    OAUTH_RESOURCE_URL.replace(OAUTH_HOST, oauthHost) + "?access_token=" + accessToken,
                    HttpMethod.POST, request, String.class);
            String cookie = Optional.ofNullable(response.getHeaders().get("Set-Cookie"))
                    .orElseGet(() -> Arrays.asList(new String(""))).get(0);
            if (cookie.length() > 0) {
                String[] c = cookie.split(";")[0].split("=");
                oauthCookie = new Cookie(c[0], c[1]);
                oauthCookie.setHttpOnly(true);
            }
        }
        return Optional.ofNullable(oauthCookie).orElseGet(() -> new Cookie("Ops", ""));
    } catch (Throwable t) {
        return new Cookie("Ops", "");
    }
}

@JsonIgnoreProperties(ignoreUnknown = true)
public class OauthTokenRespone {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String expires_in;
    private String scope;
    private String organization;
    // getter and setter
  }

并在首次应用登录后调用此方法,如下所示:

Cookie oauthCookie = oauth2Login(authenticationRequest.getUsername(), authenticationRequest.getPassword(),
            CLIENT_ID, CLIENT_SECRET);

获取 cookie 后,您需要更改其名称(例如 JSESSIONID-SECOND),因为相同的 cookie 会相互覆盖,并且还需要将其域路径更改为第二个应用程序域。

response.addCookie(oauthCookie);

最后你需要在响应中添加 cookie(它是 HttpServletResponse 参考)。

希望对你有帮助!

【讨论】:

  • 感谢您的回复,但我们找到了另一个解决方案!在 OAuth 上下文中,我们有另一个实体,称为“资源服务器”。您可以通过从 Authentication Server 获取的 access_token 访问它。因此,在第二个应用程序中,我添加了 @EnableResourceServer 注释,在标头中使用 access_token 发出 GET 请求,并获得了我需要的信息。很简单也很伤心,我花了 2 周时间找出最佳解决方案。但再次感谢您的回复。我没有快速回复您的回复,因为我的工作真的很忙... \=
猜你喜欢
  • 2010-11-17
  • 2011-12-15
  • 2012-02-11
  • 2018-08-25
  • 2017-11-28
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多