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>
|
||||
在第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"/>
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
|
|
Loading…
Reference in New Issue