test flow device_code; consent

pull/4/head
shengzhaoli.shengz 2023-10-18 11:44:35 +08:00
parent 315d9e5b78
commit 41254e5fef
4 changed files with 180 additions and 1 deletions

View File

@ -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);
}
}
}

View File

@ -206,6 +206,7 @@
<p>
在第2步进行的同时, 设备上后台将定时(如每隔5秒)向<code>spring-oauth-server</code>发起获取token的请求<code>/oauth2/token</code>
(需要使用第1步中获取到 <em>device_code</em> 的值),
<br/>
直到获取成功(即第2步操作完成授权设备登录)或超时(即设备轮询请求等待的时长超出第1步返回的时间<em>expires_in</em>)
</p>
device_code: <input type="text" ng-model="deviceCode" placeholder="GQ-K6n5kwLfu3XpDja-b3SlPbTfqYir..." size="70"/>

View File

@ -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>

View File

@ -25,7 +25,7 @@
</head>
<body>
<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>
</div>
</body>