HnqzTokenEndpoint.java 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. *
  3. * Copyright (c) 2018-2025, hnqz All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. * Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * Neither the name of the pig4cloud.com developer nor the names of its
  14. * contributors may be used to endorse or promote products derived from
  15. * this software without specific prior written permission.
  16. * Author: hnqz
  17. *
  18. */
  19. package com.qunzhixinxi.hnqz.auth.endpoint;
  20. import cn.hutool.core.map.MapUtil;
  21. import cn.hutool.core.util.StrUtil;
  22. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  23. import com.qunzhixinxi.hnqz.common.core.constant.CacheConstants;
  24. import com.qunzhixinxi.hnqz.common.core.constant.PaginationConstants;
  25. import com.qunzhixinxi.hnqz.common.core.constant.SecurityConstants;
  26. import com.qunzhixinxi.hnqz.common.core.util.R;
  27. import com.qunzhixinxi.hnqz.common.data.tenant.TenantContextHolder;
  28. import com.qunzhixinxi.hnqz.common.security.annotation.Inner;
  29. import com.qunzhixinxi.hnqz.common.security.util.SecurityUtils;
  30. import lombok.AllArgsConstructor;
  31. import lombok.extern.slf4j.Slf4j;
  32. import org.springframework.cache.CacheManager;
  33. import org.springframework.data.redis.core.ConvertingCursor;
  34. import org.springframework.data.redis.core.Cursor;
  35. import org.springframework.data.redis.core.RedisTemplate;
  36. import org.springframework.data.redis.core.ScanOptions;
  37. import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
  38. import org.springframework.data.redis.serializer.RedisSerializer;
  39. import org.springframework.data.redis.serializer.StringRedisSerializer;
  40. import org.springframework.http.HttpHeaders;
  41. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  42. import org.springframework.security.oauth2.common.OAuth2RefreshToken;
  43. import org.springframework.security.oauth2.provider.AuthorizationRequest;
  44. import org.springframework.security.oauth2.provider.ClientDetails;
  45. import org.springframework.security.oauth2.provider.ClientDetailsService;
  46. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  47. import org.springframework.security.oauth2.provider.token.TokenStore;
  48. import org.springframework.web.bind.annotation.*;
  49. import org.springframework.web.servlet.ModelAndView;
  50. import javax.servlet.http.HttpServletRequest;
  51. import javax.servlet.http.HttpSession;
  52. import java.io.IOException;
  53. import java.util.ArrayList;
  54. import java.util.List;
  55. import java.util.Map;
  56. /**
  57. * @author hnqz
  58. * @date 2018/6/24 删除token端点
  59. */
  60. @Slf4j
  61. @RestController
  62. @AllArgsConstructor
  63. @RequestMapping("/token")
  64. public class HnqzTokenEndpoint {
  65. private static final String PIGX_OAUTH_ACCESS = SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX
  66. + "auth_to_access:";
  67. private final ClientDetailsService clientDetailsService;
  68. private final RedisTemplate redisTemplate;
  69. private final TokenStore tokenStore;
  70. private final CacheManager cacheManager;
  71. /**
  72. * 认证页面
  73. * @param modelAndView
  74. * @param error 表单登录失败处理回调的错误信息
  75. * @return ModelAndView
  76. */
  77. @GetMapping("/login")
  78. public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
  79. modelAndView.setViewName("ftl/login");
  80. modelAndView.addObject("error", error);
  81. return modelAndView;
  82. }
  83. /**
  84. * 确认授权页面
  85. * @param request
  86. * @param session
  87. * @param modelAndView
  88. * @return
  89. */
  90. @GetMapping("/confirm_access")
  91. public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
  92. Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
  93. modelAndView.addObject("scopeList", scopeList.keySet());
  94. Object auth = session.getAttribute("authorizationRequest");
  95. if (auth != null) {
  96. AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
  97. ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
  98. modelAndView.addObject("app", clientDetails.getAdditionalInformation());
  99. modelAndView.addObject("user", SecurityUtils.getUser());
  100. }
  101. modelAndView.setViewName("ftl/confirm");
  102. return modelAndView;
  103. }
  104. /**
  105. * 退出token
  106. * @param authHeader Authorization
  107. */
  108. @DeleteMapping("/logout")
  109. public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
  110. if (StrUtil.isBlank(authHeader)) {
  111. return R.ok(Boolean.FALSE, "退出失败,token 为空");
  112. }
  113. String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
  114. return delToken(tokenValue);
  115. }
  116. /**
  117. * 令牌管理调用
  118. * @param token token
  119. * @return
  120. */
  121. @Inner
  122. @DeleteMapping("/{token}")
  123. public R<Boolean> delToken(@PathVariable("token") String token) {
  124. OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
  125. if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
  126. return R.ok(Boolean.TRUE, "退出失败,token 无效");
  127. }
  128. OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
  129. // 清空用户信息
  130. cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName());
  131. // 清空access token
  132. tokenStore.removeAccessToken(accessToken);
  133. // 清空 refresh token
  134. OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
  135. tokenStore.removeRefreshToken(refreshToken);
  136. return R.ok();
  137. }
  138. /**
  139. * 查询token
  140. * @param params 分页参数
  141. * @return
  142. */
  143. @Inner
  144. @PostMapping("/page")
  145. public R<Page> tokenList(@RequestBody Map<String, Object> params) {
  146. // 根据分页参数获取对应数据
  147. String key = String.format("%s*:%s", PIGX_OAUTH_ACCESS, TenantContextHolder.getTenantId());
  148. List<String> pages = findKeysForPage(key, MapUtil.getInt(params, PaginationConstants.CURRENT),
  149. MapUtil.getInt(params, PaginationConstants.SIZE));
  150. redisTemplate.setKeySerializer(new StringRedisSerializer());
  151. redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
  152. Page result = new Page(MapUtil.getInt(params, PaginationConstants.CURRENT),
  153. MapUtil.getInt(params, PaginationConstants.SIZE));
  154. result.setRecords(redisTemplate.opsForValue().multiGet(pages));
  155. result.setTotal(redisTemplate.keys(key).size());
  156. return R.ok(result);
  157. }
  158. private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
  159. ScanOptions options = ScanOptions.scanOptions().count(1000L).match(patternKey).build();
  160. RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
  161. Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(
  162. redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
  163. List<String> result = new ArrayList<>();
  164. int tmpIndex = 0;
  165. int startIndex = (pageNum - 1) * pageSize;
  166. int end = pageNum * pageSize;
  167. assert cursor != null;
  168. while (cursor.hasNext()) {
  169. if (tmpIndex >= startIndex && tmpIndex < end) {
  170. result.add(cursor.next().toString());
  171. tmpIndex++;
  172. continue;
  173. }
  174. if (tmpIndex >= end) {
  175. break;
  176. }
  177. tmpIndex++;
  178. cursor.next();
  179. }
  180. try {
  181. cursor.close();
  182. }
  183. catch (IOException e) {
  184. log.error("关闭cursor 失败");
  185. }
  186. return result;
  187. }
  188. }