diff --git a/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java b/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java new file mode 100644 index 000000000..4e0cddcd4 --- /dev/null +++ b/pjcloud-app-server/pjcloud-app-server-biz/src/main/java/com/bsd/pjcloud/app/controller/AppUserAutoLoginController.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2025, lengleng 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: lengleng (wangiegie@gmail.com) + */ + +package com.bsd.pjcloud.app.controller; + + +import com.bsd.pjcloud.admin.api.dto.UserTokenDTO; +import com.bsd.pjcloud.admin.api.feign.RemoteTokenService; +import com.bsd.pjcloud.app.service.AppUserService; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.annotation.Inner; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * app用户表 + * + * @author aeizzz + * @date 2022-12-07 09:52:03 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/appLogin") +@Tag(description = "appLogin", name = "浙政钉诗画单点登录") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class AppUserAutoLoginController { + + private final AppUserService appUserService; + + + private final RemoteTokenService remoteTokenService; + + @Inner(value = false) + @GetMapping("/getAuthToken/{token}") + public R info(@PathVariable(name = "token") String token) { +// 拼接请求参数 + UserTokenDTO userTokenDTO = new UserTokenDTO(); + userTokenDTO.setUsername("admin"); // 只需要用户名,就可以生成 token + userTokenDTO.setAuthorities(List.of("sys_user_add")); // 权限列表的字符串,能调用的接口范围 + + // feign 调用 auth 模块生成 token +// R authenticationTokenR = remoteTokenService.generateToken(userTokenDTO); + R authenticationTokenR = remoteTokenService.generateToken(userTokenDTO); + // token 信息 +// OAuth2AccessTokenAuthenticationToken data = authenticationTokenR.getData(); +// OAuth2AccessToken accessToken = data.getAccessToken(); +// // 刷新 token 信息 +// OAuth2RefreshToken refreshToken = data.getRefreshToken(); +// // 客户端信息 +// RegisteredClient registeredClient = data.getRegisteredClient(); + Object accessToken = authenticationTokenR.getData().get("accessToken"); + return R.ok(accessToken); + } +// +// /** +// * 获取当前用户全部信息 +// * @return 用户信息 +// */ +// @GetMapping(value = { "/info" }) +// public R info() { +// String username = SecurityUtils.getUser().getUsername(); +// AppUser user = appUserService.getOne(Wrappers.query().lambda().eq(AppUser::getUsername, username)); +// if (user == null) { +// return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_QUERY_ERROR)); +// } +// return R.ok(appUserService.findUserInfo(user)); +// } +// +// /** +// * 分页查询 +// * @param page 分页对象 +// * @param appUserDTO app用户表 +// * @return +// */ +// @Operation(summary = "分页查询", description = "分页查询") +// @GetMapping("/page") +// public R getAppUserPage(Page page, AppUserDTO appUserDTO) { +// return R.ok(appUserService.getUsersWithRolePage(page, appUserDTO)); +// } +// +// /** +// * 通过id查询app用户表 +// * @param userId id +// * @return R +// */ +// @Operation(summary = "通过id查询", description = "通过id查询") +// @GetMapping("/details/{userId}") +// public R getById(@PathVariable("userId") Long userId) { +// return R.ok(appUserService.selectUserVoById(userId)); +// } +// +// /** +// * @return R +// */ +// @Operation(summary = "通过userName查询", description = "通过userName查询") +// @GetMapping("/details") +// public R getByUserName(AppUser user) { +// AppUser one = appUserService.getOne(Wrappers.query(user), false); +// return R.ok(one == null ? null : CommonConstants.SUCCESS); +// } +// +// /** +// * 新增app用户表 +// * @param appUser app用户表 +// * @return R +// */ +// @Operation(summary = "新增app用户表", description = "新增app用户表") +// @SysLog("新增app用户表") +// @PostMapping +// @HasPermission("app_appuser_add") +// public R save(@RequestBody AppUserDTO appUser) { +// appUserService.saveUser(appUser); +// return R.ok(); +// } +// +// /** +// * 修改app用户表 +// * @param appUser app用户表 +// * @return R +// */ +// @Operation(summary = "修改app用户表", description = "修改app用户表") +// @SysLog("修改app用户表") +// @PutMapping +// @HasPermission("app_appuser_edit") +// public R updateById(@RequestBody AppUserDTO appUser) { +// return R.ok(appUserService.updateUser(appUser)); +// } +// +// /** +// * 通过id删除app用户表 +// * @param ids userIds +// * @return R +// */ +// @Operation(summary = "通过ids删除app用户表", description = "通过ids删除app用户表") +// @SysLog("通过id删除app用户表") +// @DeleteMapping +// @HasPermission("app_appuser_del") +// public R removeById(@RequestBody Long[] ids) { +// return R.ok(appUserService.deleteAppUserByIds(ids)); +// } +// +// /** +// * 导出excel 表格 +// * @param appUser 查询条件 +// * @return excel 文件流 +// */ +// @ResponseExcel +// @GetMapping("/export") +// @HasPermission("app_appuser_export") +// public List export(AppUserDTO appUser) { +// return appUserService.listUser(appUser); +// } +// +// @SysLog("修改个人信息") +// @PutMapping("/edit") +// public R updateUserInfo(@Valid @RequestBody AppUserDTO userDto) { +// if (UserTypeEnum.TOC.getStatus().equals(SecurityUtils.getUser().getUserType())) { +// userDto.setUserId(SecurityUtils.getUser().getId()); +// // TOC 以手机号为key +// userDto.setUsername(SecurityUtils.getUser().getUsername()); +// } +// return appUserService.updateUserInfo(userDto); +// } +// +// /** +// * 导入用户 +// * @param excelVOList 用户列表 +// * @param bindingResult 错误信息列表 +// * @return R +// */ +// @PostMapping("/import") +// @HasPermission("app_appuser_export") +// public R importUser(@RequestExcel List excelVOList, BindingResult bindingResult) { +// return appUserService.importUser(excelVOList, bindingResult); +// } +// +// @Operation(summary = "注册app用户表", description = "注册app用户表") +// @SysLog("注册app用户表") +// @Inner(value = false) +// @PostMapping("/register") +// public R register(@RequestBody AppUserDTO appUser) { +// return appUserService.registerAppUser(appUser); +// } + +} diff --git a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java index be9275e75..b13649dd6 100644 --- a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java +++ b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java @@ -21,6 +21,7 @@ import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.dto.UserTokenDTO; import com.bsd.pjcloud.admin.api.entity.SysOauthClientDetails; import com.bsd.pjcloud.admin.api.entity.SysTenant; import com.bsd.pjcloud.admin.api.feign.RemoteClientDetailsService; @@ -47,19 +48,31 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.*; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.security.Principal; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Objects; @@ -88,6 +101,76 @@ public class PjcloudTokenEndpoint { private final CacheManager cacheManager; + private final OAuth2TokenGenerator tokenGenerator; + @Inner + @SneakyThrows + @PostMapping("/generate-token") + public R generateToken(@RequestBody UserTokenDTO userTokenDTO) { + // 构建授权信息 + RegisteredClient registeredClient = RegisteredClient.withId(SecurityConstants.FROM).clientId(SecurityConstants.FROM) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + .tokenSettings(TokenSettings.builder() + .accessTokenTimeToLive(Duration.ofHours(24)) // token 有效期 24 小时 + .refreshTokenTimeToLive(Duration.ofDays(7)) // refresh token 有效期 7 天 + .accessTokenFormat(OAuth2TokenFormat.REFERENCE) + .build()) + .build(); + + + // 构建用户信息 , 这里只拼接了 UserTokenDTO 中的 username 和 authorities ,其他字段写死,可以自行场地; + PjcloudUser pigUser = new PjcloudUser(1L, userTokenDTO.getUsername(),1L,null,"","","", "", 1L + , "", true, true,"1", true,false, userTokenDTO.getAuthorities() + .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); + + Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(pigUser, StrUtil.EMPTY); + + DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() + .registeredClient(registeredClient) + .principal(usernamePasswordAuthentication) + .authorizationServerContext(AuthorizationServerContextHolder.getContext()); + + OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization + .withRegisteredClient(registeredClient) + .principalName(usernamePasswordAuthentication.getName()); + + // ----- Access token ----- + OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + .authorizationGrant(new OAuth2ClientAuthenticationToken(registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null)) + .build(); + OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); + + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), + generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); + + if (generatedAccessToken instanceof ClaimAccessor) { + authorizationBuilder.id(accessToken.getTokenValue()) + .token(accessToken, + (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, + ((ClaimAccessor) generatedAccessToken).getClaims())) + .attribute(Principal.class.getName(), usernamePasswordAuthentication); + } else { + authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken); + } + + + // ----- Refresh token ----- + OAuth2RefreshToken refreshToken; + tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); + OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); + refreshToken = (OAuth2RefreshToken) generatedRefreshToken; + authorizationBuilder.refreshToken(refreshToken); + + OAuth2Authorization authorization = authorizationBuilder + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + .build(); + + this.authorizationService.save(authorization); + + return R.ok(new OAuth2AccessTokenAuthenticationToken(registeredClient, usernamePasswordAuthentication, accessToken, + refreshToken, authorization.getAccessToken().getClaims())); + } /** * 认证页面 * @param modelAndView diff --git a/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java b/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java index bf727eacb..c02ada6a0 100644 --- a/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java +++ b/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java @@ -41,6 +41,9 @@ public class PjcloudCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospe if (Objects.isNull(oldAuthorization)) { throw new InvalidBearerTokenException(token); } + if (SecurityConstants.FROM.equals(oldAuthorization.getRegisteredClientId())){ + return (PjcloudUser) ((UsernamePasswordAuthenticationToken) Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName())).getPrincipal(); + } // 客户端模式默认返回 if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) { diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserTokenDTO.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserTokenDTO.java new file mode 100644 index 000000000..ad4b4b0fd --- /dev/null +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserTokenDTO.java @@ -0,0 +1,19 @@ +package com.bsd.pjcloud.admin.api.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class UserTokenDTO { + /** + * 用户名 + */ + private String username; + + /** + * 权限列表 + */ + private List authorities = new ArrayList<>(); +} diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteTokenService.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteTokenService.java index 61bde07b0..9ab861223 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteTokenService.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteTokenService.java @@ -18,6 +18,7 @@ package com.bsd.pjcloud.admin.api.feign; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.dto.UserTokenDTO; import com.bsd.pjcloud.common.core.constant.CommonConstants; import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; import com.bsd.pjcloud.common.core.util.R; @@ -62,4 +63,13 @@ public interface RemoteTokenService { R> queryToken(@RequestParam("token") String token, @RequestParam(CommonConstants.TENANT_ID) String tenantId); + + /** + * 手动生成token + * @param userTokenDTO + * @return + */ + @NoToken + @PostMapping("/token/generate-token") + R generateToken(@RequestBody UserTokenDTO userTokenDTO); }