123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- /*
- *
- * Copyright (c) 2018-2025, hnqz All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * Neither the name of the pig4cloud.com developer nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * Author: hnqz
- *
- */
- package com.qunzhixinxi.hnqz.auth.endpoint;
- import cn.hutool.core.map.MapUtil;
- import cn.hutool.core.util.StrUtil;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.qunzhixinxi.hnqz.common.core.constant.CacheConstants;
- import com.qunzhixinxi.hnqz.common.core.constant.PaginationConstants;
- import com.qunzhixinxi.hnqz.common.core.constant.SecurityConstants;
- import com.qunzhixinxi.hnqz.common.core.util.R;
- import com.qunzhixinxi.hnqz.common.data.tenant.TenantContextHolder;
- import com.qunzhixinxi.hnqz.common.security.annotation.Inner;
- import com.qunzhixinxi.hnqz.common.security.util.SecurityUtils;
- import lombok.AllArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.cache.CacheManager;
- import org.springframework.data.redis.core.ConvertingCursor;
- import org.springframework.data.redis.core.Cursor;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.ScanOptions;
- import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
- import org.springframework.data.redis.serializer.RedisSerializer;
- import org.springframework.data.redis.serializer.StringRedisSerializer;
- import org.springframework.http.HttpHeaders;
- import org.springframework.security.oauth2.common.OAuth2AccessToken;
- import org.springframework.security.oauth2.common.OAuth2RefreshToken;
- import org.springframework.security.oauth2.provider.AuthorizationRequest;
- import org.springframework.security.oauth2.provider.ClientDetails;
- import org.springframework.security.oauth2.provider.ClientDetailsService;
- import org.springframework.security.oauth2.provider.OAuth2Authentication;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.web.bind.annotation.*;
- import org.springframework.web.servlet.ModelAndView;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpSession;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- /**
- * @author hnqz
- * @date 2018/6/24 删除token端点
- */
- @Slf4j
- @RestController
- @AllArgsConstructor
- @RequestMapping("/token")
- public class HnqzTokenEndpoint {
- private static final String PIGX_OAUTH_ACCESS = SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX
- + "auth_to_access:";
- private final ClientDetailsService clientDetailsService;
- private final RedisTemplate redisTemplate;
- private final TokenStore tokenStore;
- private final CacheManager cacheManager;
- /**
- * 认证页面
- * @param modelAndView
- * @param error 表单登录失败处理回调的错误信息
- * @return ModelAndView
- */
- @GetMapping("/login")
- public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
- modelAndView.setViewName("ftl/login");
- modelAndView.addObject("error", error);
- return modelAndView;
- }
- /**
- * 确认授权页面
- * @param request
- * @param session
- * @param modelAndView
- * @return
- */
- @GetMapping("/confirm_access")
- public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
- Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
- modelAndView.addObject("scopeList", scopeList.keySet());
- Object auth = session.getAttribute("authorizationRequest");
- if (auth != null) {
- AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
- ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
- modelAndView.addObject("app", clientDetails.getAdditionalInformation());
- modelAndView.addObject("user", SecurityUtils.getUser());
- }
- modelAndView.setViewName("ftl/confirm");
- return modelAndView;
- }
- /**
- * 退出token
- * @param authHeader Authorization
- */
- @DeleteMapping("/logout")
- public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
- if (StrUtil.isBlank(authHeader)) {
- return R.ok(Boolean.FALSE, "退出失败,token 为空");
- }
- String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
- return delToken(tokenValue);
- }
- /**
- * 令牌管理调用
- * @param token token
- * @return
- */
- @Inner
- @DeleteMapping("/{token}")
- public R<Boolean> delToken(@PathVariable("token") String token) {
- OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
- if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
- return R.ok(Boolean.TRUE, "退出失败,token 无效");
- }
- OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
- // 清空用户信息
- cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName());
- // 清空access token
- tokenStore.removeAccessToken(accessToken);
- // 清空 refresh token
- OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
- tokenStore.removeRefreshToken(refreshToken);
- return R.ok();
- }
- /**
- * 查询token
- * @param params 分页参数
- * @return
- */
- @Inner
- @PostMapping("/page")
- public R<Page> tokenList(@RequestBody Map<String, Object> params) {
- // 根据分页参数获取对应数据
- String key = String.format("%s*:%s", PIGX_OAUTH_ACCESS, TenantContextHolder.getTenantId());
- List<String> pages = findKeysForPage(key, MapUtil.getInt(params, PaginationConstants.CURRENT),
- MapUtil.getInt(params, PaginationConstants.SIZE));
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
- Page result = new Page(MapUtil.getInt(params, PaginationConstants.CURRENT),
- MapUtil.getInt(params, PaginationConstants.SIZE));
- result.setRecords(redisTemplate.opsForValue().multiGet(pages));
- result.setTotal(redisTemplate.keys(key).size());
- return R.ok(result);
- }
- private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
- ScanOptions options = ScanOptions.scanOptions().count(1000L).match(patternKey).build();
- RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
- Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(
- redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
- List<String> result = new ArrayList<>();
- int tmpIndex = 0;
- int startIndex = (pageNum - 1) * pageSize;
- int end = pageNum * pageSize;
- assert cursor != null;
- while (cursor.hasNext()) {
- if (tmpIndex >= startIndex && tmpIndex < end) {
- result.add(cursor.next().toString());
- tmpIndex++;
- continue;
- }
- if (tmpIndex >= end) {
- break;
- }
- tmpIndex++;
- cursor.next();
- }
- try {
- cursor.close();
- }
- catch (IOException e) {
- log.error("关闭cursor 失败");
- }
- return result;
- }
- }
|