test flow device_code; consent
parent
315d9e5b78
commit
41254e5fef
|
@ -0,0 +1,146 @@
|
||||||
|
|
||||||
|
package com.monkeyk.sos.web.controller;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.monkeyk.sos.domain.shared.SOSConstants.AUTHORIZATION_ENDPOINT_URI;
|
||||||
|
import static com.monkeyk.sos.domain.shared.SOSConstants.DEVICE_VERIFICATION_ENDPOINT_URI;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2023/10/18
|
||||||
|
* <p>
|
||||||
|
* consent flow
|
||||||
|
*
|
||||||
|
* @author shengzhao Li
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
public class AuthorizationConsentController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AuthorizationConsentController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RegisteredClientRepository registeredClientRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展,自定义的 consent
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/oauth2/consent")
|
||||||
|
public String consent(Principal principal, Model model,
|
||||||
|
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
|
||||||
|
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
|
||||||
|
@RequestParam(OAuth2ParameterNames.STATE) String state,
|
||||||
|
@RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {
|
||||||
|
|
||||||
|
//再次检查 client
|
||||||
|
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
||||||
|
if (registeredClient == null) {
|
||||||
|
if (LOG.isWarnEnabled()) {
|
||||||
|
LOG.warn("Not found RegisteredClient by clientId: {}", clientId);
|
||||||
|
}
|
||||||
|
model.addAttribute("error", "Invalid client_id: " + clientId);
|
||||||
|
return "consent_error";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove scopes that were already approved
|
||||||
|
Set<String> scopesToApprove = new HashSet<>();
|
||||||
|
Set<String> previouslyApprovedScopes = new HashSet<>();
|
||||||
|
|
||||||
|
OAuth2AuthorizationConsent currentAuthorizationConsent =
|
||||||
|
this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
|
||||||
|
Set<String> authorizedScopes;
|
||||||
|
if (currentAuthorizationConsent != null) {
|
||||||
|
authorizedScopes = currentAuthorizationConsent.getScopes();
|
||||||
|
} else {
|
||||||
|
authorizedScopes = Collections.emptySet();
|
||||||
|
}
|
||||||
|
for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
|
||||||
|
if (OidcScopes.OPENID.equals(requestedScope)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (authorizedScopes.contains(requestedScope)) {
|
||||||
|
previouslyApprovedScopes.add(requestedScope);
|
||||||
|
} else {
|
||||||
|
scopesToApprove.add(requestedScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addAttribute("clientId", clientId);
|
||||||
|
model.addAttribute("state", state);
|
||||||
|
model.addAttribute("scopes", withDescription(scopesToApprove));
|
||||||
|
model.addAttribute("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
|
||||||
|
model.addAttribute("principalName", principal.getName());
|
||||||
|
model.addAttribute("userCode", userCode);
|
||||||
|
if (StringUtils.hasText(userCode)) {
|
||||||
|
model.addAttribute("requestURI", DEVICE_VERIFICATION_ENDPOINT_URI);
|
||||||
|
} else {
|
||||||
|
model.addAttribute("requestURI", AUTHORIZATION_ENDPOINT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "consent";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
|
||||||
|
Set<ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
|
||||||
|
for (String scope : scopes) {
|
||||||
|
scopeWithDescriptions.add(new ScopeWithDescription(scope));
|
||||||
|
|
||||||
|
}
|
||||||
|
return scopeWithDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ScopeWithDescription {
|
||||||
|
private static final String DEFAULT_DESCRIPTION
|
||||||
|
= "UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";
|
||||||
|
private static final Map<String, String> SCOPE_DESCRIPTIONS = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
SCOPE_DESCRIPTIONS.put(
|
||||||
|
OidcScopes.PROFILE,
|
||||||
|
"This application will be able to read your profile information."
|
||||||
|
);
|
||||||
|
SCOPE_DESCRIPTIONS.put(
|
||||||
|
"message.read",
|
||||||
|
"This application will be able to read your message."
|
||||||
|
);
|
||||||
|
SCOPE_DESCRIPTIONS.put(
|
||||||
|
"message.write",
|
||||||
|
"This application will be able to add new messages. It will also be able to edit and delete existing messages."
|
||||||
|
);
|
||||||
|
SCOPE_DESCRIPTIONS.put(
|
||||||
|
"other.scope",
|
||||||
|
"This is another scope example of a scope description."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String scope;
|
||||||
|
public final String description;
|
||||||
|
|
||||||
|
ScopeWithDescription(String scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
this.description = SCOPE_DESCRIPTIONS.getOrDefault(scope, DEFAULT_DESCRIPTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -206,6 +206,7 @@
|
||||||
<p>
|
<p>
|
||||||
在第2步进行的同时, 设备上后台将定时(如每隔5秒)向<code>spring-oauth-server</code>发起获取token的请求<code>/oauth2/token</code>
|
在第2步进行的同时, 设备上后台将定时(如每隔5秒)向<code>spring-oauth-server</code>发起获取token的请求<code>/oauth2/token</code>
|
||||||
(需要使用第1步中获取到 <em>device_code</em> 的值),
|
(需要使用第1步中获取到 <em>device_code</em> 的值),
|
||||||
|
<br/>
|
||||||
直到获取成功(即第2步操作完成授权设备登录)或超时(即设备轮询请求等待的时长超出第1步返回的时间<em>expires_in</em>)
|
直到获取成功(即第2步操作完成授权设备登录)或超时(即设备轮询请求等待的时长超出第1步返回的时间<em>expires_in</em>)
|
||||||
</p>
|
</p>
|
||||||
device_code: <input type="text" ng-model="deviceCode" placeholder="GQ-K6n5kwLfu3XpDja-b3SlPbTfqYir..." size="70"/>
|
device_code: <input type="text" ng-model="deviceCode" placeholder="GQ-K6n5kwLfu3XpDja-b3SlPbTfqYir..." size="70"/>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width,user-scalable=no"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||||
|
<link rel="shortcut icon" href="../static/favicon.ico" th:href="@{/favicon.ico}"/>
|
||||||
|
|
||||||
|
<title>Consent Error - Spring Security&OAuth2.1</title>
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/main::header-css}"/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-danger">Consent Error</h1>
|
||||||
|
<p class="text-muted">Message: <span th:text="${error}"></span></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -25,7 +25,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>403 - Access Denied</h1>
|
<h1 class="text-danger">403 - Access Denied</h1>
|
||||||
<p class="text-muted">Sorry, you do not have permission to access this resource.</p>
|
<p class="text-muted">Sorry, you do not have permission to access this resource.</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue