AuthorizationController.java 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package timing.ukulele.auth.controller;
  2. import jakarta.servlet.http.HttpServletRequest;
  3. import jakarta.servlet.http.HttpServletResponse;
  4. import jakarta.servlet.http.HttpSession;
  5. import lombok.Data;
  6. import lombok.SneakyThrows;
  7. import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
  8. import org.springframework.security.oauth2.core.oidc.OidcScopes;
  9. import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
  10. import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
  11. import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
  12. import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
  13. import org.springframework.security.web.DefaultRedirectStrategy;
  14. import org.springframework.security.web.RedirectStrategy;
  15. import org.springframework.security.web.util.UrlUtils;
  16. import org.springframework.ui.Model;
  17. import org.springframework.util.ObjectUtils;
  18. import org.springframework.util.StringUtils;
  19. import org.springframework.web.bind.annotation.GetMapping;
  20. import org.springframework.web.bind.annotation.RequestParam;
  21. import org.springframework.web.bind.annotation.ResponseBody;
  22. import org.springframework.web.bind.annotation.RestController;
  23. import org.springframework.web.util.UriComponentsBuilder;
  24. import org.springframework.web.util.UriUtils;
  25. import timing.ukulele.auth.config.property.TimingSecurityProperties;
  26. import timing.ukulele.auth.model.Result;
  27. import java.nio.charset.StandardCharsets;
  28. import java.security.Principal;
  29. import java.util.*;
  30. /**
  31. * 认证服务器相关自定接口
  32. */
  33. @RestController
  34. public class AuthorizationController {
  35. private final TimingSecurityProperties securityProperties;
  36. private final RegisteredClientRepository registeredClientRepository;
  37. private final OAuth2AuthorizationConsentService authorizationConsentService;
  38. private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
  39. public AuthorizationController(
  40. TimingSecurityProperties securityProperties,
  41. RegisteredClientRepository registeredClientRepository,
  42. OAuth2AuthorizationConsentService authorizationConsentService) {
  43. this.securityProperties = securityProperties;
  44. this.registeredClientRepository = registeredClientRepository;
  45. this.authorizationConsentService = authorizationConsentService;
  46. }
  47. @SneakyThrows
  48. @ResponseBody
  49. @GetMapping(value = "/oauth2/consent/redirect")
  50. public Result<String> consentRedirect(HttpServletRequest request,
  51. HttpServletResponse response,
  52. @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
  53. @RequestParam(OAuth2ParameterNames.STATE) String state,
  54. @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
  55. @RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {
  56. // 携带当前请求参数重定向至前端页面
  57. UriComponentsBuilder uriBuilder = UriComponentsBuilder
  58. .fromUriString(securityProperties.getConsentPageUri())
  59. .queryParam(OAuth2ParameterNames.SCOPE, UriUtils.encode(scope, StandardCharsets.UTF_8))
  60. .queryParam(OAuth2ParameterNames.STATE, UriUtils.encode(state, StandardCharsets.UTF_8))
  61. .queryParam(OAuth2ParameterNames.CLIENT_ID, clientId)
  62. .queryParam(OAuth2ParameterNames.USER_CODE, userCode);
  63. String uriString = uriBuilder.build(Boolean.TRUE).toUriString();
  64. if (ObjectUtils.isEmpty(userCode) || !UrlUtils.isAbsoluteUrl(securityProperties.getDeviceActivateUri())) {
  65. // 不是设备码模式或者设备码验证页面不是前后端分离的,无需返回json,直接重定向
  66. redirectStrategy.sendRedirect(request, response, uriString);
  67. return null;
  68. }
  69. // 兼容设备码,需响应JSON,由前端进行跳转
  70. return Result.success(uriString);
  71. }
  72. @ResponseBody
  73. @GetMapping(value = "/oauth2/consent/parameters")
  74. public Result<Map<String, Object>> consentParameters(Principal principal,
  75. @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
  76. @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
  77. @RequestParam(OAuth2ParameterNames.STATE) String state,
  78. @RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {
  79. // 获取consent页面所需的参数
  80. Map<String, Object> consentParameters = getConsentParameters(scope, state, clientId, userCode, principal);
  81. return Result.success(consentParameters);
  82. }
  83. /**
  84. * 根据授权确认相关参数获取授权确认与未确认的scope相关参数
  85. *
  86. * @param scope scope权限
  87. * @param state state
  88. * @param clientId 客户端id
  89. * @param userCode 设备码授权流程中的用户码
  90. * @param principal 当前认证信息
  91. * @return 页面所需数据
  92. */
  93. private Map<String, Object> getConsentParameters(String scope,
  94. String state,
  95. String clientId,
  96. String userCode,
  97. Principal principal) {
  98. if (principal == null) {
  99. throw new RuntimeException("认证信息已失效.");
  100. }
  101. // Remove scopes that were already approved
  102. Set<String> scopesToApprove = new HashSet<>();
  103. Set<String> previouslyApprovedScopes = new HashSet<>();
  104. RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
  105. if (registeredClient == null) {
  106. throw new RuntimeException("客户端不存在");
  107. }
  108. OAuth2AuthorizationConsent currentAuthorizationConsent =
  109. this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
  110. Set<String> authorizedScopes;
  111. if (currentAuthorizationConsent != null) {
  112. authorizedScopes = currentAuthorizationConsent.getScopes();
  113. } else {
  114. authorizedScopes = Collections.emptySet();
  115. }
  116. for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
  117. if (OidcScopes.OPENID.equals(requestedScope)) {
  118. continue;
  119. }
  120. if (authorizedScopes.contains(requestedScope)) {
  121. previouslyApprovedScopes.add(requestedScope);
  122. } else {
  123. scopesToApprove.add(requestedScope);
  124. }
  125. }
  126. Map<String, Object> parameters = new HashMap<>(7);
  127. parameters.put("clientId", registeredClient.getClientId());
  128. parameters.put("clientName", registeredClient.getClientName());
  129. parameters.put("state", state);
  130. parameters.put("scopes", withDescription(scopesToApprove));
  131. parameters.put("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
  132. parameters.put("principalName", principal.getName());
  133. parameters.put("userCode", userCode);
  134. if (StringUtils.hasText(userCode)) {
  135. parameters.put("requestURI", "/oauth2/device_verification");
  136. } else {
  137. parameters.put("requestURI", "/oauth2/authorize");
  138. }
  139. return parameters;
  140. }
  141. private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
  142. Set<ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
  143. for (String scope : scopes) {
  144. scopeWithDescriptions.add(new ScopeWithDescription(scope));
  145. }
  146. return scopeWithDescriptions;
  147. }
  148. @Data
  149. public static class ScopeWithDescription {
  150. private static final String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";
  151. private static final Map<String, String> scopeDescriptions = new HashMap<>();
  152. static {
  153. scopeDescriptions.put(
  154. OidcScopes.PROFILE,
  155. "This application will be able to read your profile information."
  156. );
  157. scopeDescriptions.put(
  158. "message.read",
  159. "This application will be able to read your message."
  160. );
  161. scopeDescriptions.put(
  162. "message.write",
  163. "This application will be able to add new messages. It will also be able to edit and delete existing messages."
  164. );
  165. scopeDescriptions.put(
  166. "other.scope",
  167. "This is another scope example of a scope description."
  168. );
  169. }
  170. public final String scope;
  171. public final String description;
  172. ScopeWithDescription(String scope) {
  173. this.scope = scope;
  174. this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
  175. }
  176. }
  177. }