From 55218a0baaadaafd21aec5d624ed1b4bad782eae Mon Sep 17 00:00:00 2001 From: bynt Date: Fri, 17 Mar 2023 13:47:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9F=AD=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=20=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baiye/core/constant/Oauth2Constant.java | 160 ++++++++++++++++++ .../core/constant/SecurityConstants.java | 4 + .../global/handle/GlobalExceptionHandler.java | 3 + .../baiye/service/CdpUserDetailsService.java | 22 +++ .../baiye/service/UserDetailsServiceImpl.java | 51 ++++-- cdp-iaas/authorization-server/pom.xml | 6 + .../baiye/auth/config/AuthServerConfig.java | 149 +++++++++++----- .../auth/config/SingleLoginTokenServices.java | 156 +++++++++++++++++ .../auth/config/properties/SmsProperties.java | 44 +++++ .../auth/controller/OauthController.java | 58 +++++++ .../baiye/auth/controller/SmsController.java | 31 ++++ .../auth/feign/IRemoteMemberService.java | 30 ++++ .../com/baiye/auth/service/SmsService.java | 15 ++ .../auth/service/impl/SmsServiceImpl.java | 64 +++++++ .../auth/sms/SmsCodeAuthenticationFilter.java | 80 +++++++++ .../sms/SmsCodeAuthenticationProvider.java | 49 ++++++ .../SmsCodeAuthenticationSecurityConfig.java | 38 +++++ .../auth/sms/SmsCodeAuthenticationToken.java | 58 +++++++ .../auth/sms/granter/SmsCodeTokenGranter.java | 99 +++++++++++ .../CustomWebRespExceptionTranslator.java | 1 - .../com/baiye/filter/ValidateCodeFilter.java | 3 +- .../main/resources/config/application-dev.yml | 37 ---- .../resources/config/application-prod.yml | 32 +--- .../src/main/resources/config/application.yml | 41 ++++- .../src/main/java/com/baiye/Member.java | 83 +++++++++ .../main/java/com/baiye/dto/MemberDto.java | 81 +++++++++ .../com/baiye/feign/IRemoteUserService.java | 20 ++- .../com/baiye/mapstruct/MemberMapStruct.java | 31 ++++ .../com/baiye/query/MemberQueryCriteria.java | 44 +++++ .../baiye/controller/MemberController.java | 79 +++++++++ .../controller/UserDetailsController.java | 12 ++ .../java/com/baiye/mapper/MemberMapper.java | 53 ++++++ .../com/baiye/service/IMemberService.java | 54 ++++++ .../baiye/service/impl/MemberServiceImpl.java | 159 +++++++++++++++++ .../baiye/service/impl/UserServiceImpl.java | 12 +- .../src/main/java/com/baiye/util/D.java | 70 ++++++++ .../main/resources/mapper/MemberMapper.xml | 105 ++++++++++++ 37 files changed, 1892 insertions(+), 142 deletions(-) create mode 100644 cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/Oauth2Constant.java create mode 100644 cdp-common/cdp-common-security/src/main/java/com/baiye/service/CdpUserDetailsService.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/SingleLoginTokenServices.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/properties/SmsProperties.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/OauthController.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/SmsController.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/feign/IRemoteMemberService.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/SmsService.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/impl/SmsServiceImpl.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationFilter.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationProvider.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationSecurityConfig.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationToken.java create mode 100644 cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/granter/SmsCodeTokenGranter.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/Member.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/dto/MemberDto.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/mapstruct/MemberMapStruct.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/query/MemberQueryCriteria.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/MemberController.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/mapper/MemberMapper.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/IMemberService.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/MemberServiceImpl.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/util/D.java create mode 100644 cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/resources/mapper/MemberMapper.xml diff --git a/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/Oauth2Constant.java b/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/Oauth2Constant.java new file mode 100644 index 0000000..03ddae4 --- /dev/null +++ b/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/Oauth2Constant.java @@ -0,0 +1,160 @@ +package com.baiye.core.constant; + +/** + * 认证URL常量 + * + * @author xuzhanfu + * @date 2019-10-10 17:46 + **/ +public class Oauth2Constant { + + + public static final String ALL = "/**"; + + public static final String OAUTH_ALL = "/oauth/**"; + + public static final String OAUTH_AUTHORIZE = "/oauth/authorize"; + + public static final String OAUTH_CHECK_TOKEN = "/oauth/check_token"; + + public static final String OAUTH_CONFIRM_ACCESS = "/oauth/confirm_access"; + + public static final String OAUTH_TOKEN = "/oauth/token"; + + public static final String OAUTH_TOKEN_KEY = "/oauth/token_key"; + + public static final String OAUTH_ERROR = "/oauth/error"; + + public static final String OAUTH_MOBILE = "/oauth/mobile"; + + /** + * 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称 + */ + public static final String DEFAULT_PARAMETER_NAME_MOBILE = "mobile"; + + /** + * 社交登录,传递的参数名称 + */ + public static final String DEFAULT_PARAMETER_NAME_SOCIAL = "social"; + + /** + * 验证码 key + */ + public static final String VALIDATE_CODE_KEY = "key"; + /** + * 验证码 code + */ + public static final String VALIDATE_CODE_CODE = "code"; + /** + * 认证类型参数 key + */ + public static final String GRANT_TYPE = "grant_type"; + /** + * 登录类型 + */ + public static final String LOGIN_TYPE = "login_type"; + + /** + * 刷新模式 + */ + public static final String REFRESH_TOKEN = "refresh_token"; + /** + * 授权码模式 + */ + public static final String AUTHORIZATION_CODE = "authorization_code"; + /** + * 客户端模式 + */ + public static final String CLIENT_CREDENTIALS = "client_credentials"; + /** + * 密码模式 + */ + public static final String PASSWORD = "password"; + /** + * 简化模式 + */ + public static final String IMPLICIT = "implicit"; + + public static final String SIGN_KEY = "MATE"; + + public static final String CAPTCHA_KEY = "mate.captcha."; + + public static final String SMS_CODE_KEY = "mate.sms.code."; + + public static final String CAPTCHA_HEADER_KEY = "key"; + + public static final String CAPTCHA_HEADER_CODE = "code"; + + public static final int LOGIN_USERNAME_TYPE = 1; + + public static final int LOGIN_MOBILE_TYPE = 2; + + public static final String HEADER_TOKEN = "Mate-Auth"; + + /** + * 自定义client表名 + */ + public static final String CLIENT_TABLE = "mate_sys_client"; + + + public static final String CAPTCHA_ERROR = "验证码不正确!"; + + public static final String SUPER_ADMIN = "admin"; + + /** + * 基础查询语句 + */ + public static final String CLIENT_BASE = "select client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, " + + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity," + + "refresh_token_validity, additional_information, autoapprove from " + CLIENT_TABLE; + + public static final String FIND_CLIENT_DETAIL_SQL = CLIENT_BASE + " order by client_id"; + + public static final String SELECT_CLIENT_DETAIL_SQL = CLIENT_BASE + " where client_id = ?"; + + /** + * 标志 + */ + public static final String FROM = "from"; + + /** + * 内部 + */ + public static final String FROM_IN = "Y"; + + /** + * 权限标识前缀 + */ + public static final String MATE_PERMISSION_PREFIX = "mate.permission."; + + /** + * 字段描述开始:用户ID + */ + public static final String MATE_USER_ID = "userId"; + + /** + * 用户名 + */ + public static final String MATE_USER_NAME = "userName"; + + /** + * 用户头像 + */ + public static final String MATE_AVATAR = "avatar"; + + /** + * 用户权限ID + */ + public static final String MATE_ROLE_ID = "roleId"; + + /** + * 用户类型 + */ + public static final String MATE_TYPE = "type"; + + /** + * 租户ID + */ + public static final String MATE_TENANT_ID = "tenantId"; + +} diff --git a/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/SecurityConstants.java b/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/SecurityConstants.java index df27be6..55b8cf1 100644 --- a/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/SecurityConstants.java +++ b/cdp-common/cdp-common-core/src/main/java/com/baiye/core/constant/SecurityConstants.java @@ -91,4 +91,8 @@ public final class SecurityConstants { public static final CharSequence REFRESH_TOKEN = "refresh_token"; + + public static final String SMS_GRANT_TYPE = "sms_code"; + + public static final String SIGN_KEY = "BY_CDP"; } diff --git a/cdp-common/cdp-common-exception/src/main/java/com/baiye/exception/global/handle/GlobalExceptionHandler.java b/cdp-common/cdp-common-exception/src/main/java/com/baiye/exception/global/handle/GlobalExceptionHandler.java index 614580d..90dbd93 100644 --- a/cdp-common/cdp-common-exception/src/main/java/com/baiye/exception/global/handle/GlobalExceptionHandler.java +++ b/cdp-common/cdp-common-exception/src/main/java/com/baiye/exception/global/handle/GlobalExceptionHandler.java @@ -70,6 +70,9 @@ public class GlobalExceptionHandler { return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage())); } + + + /** * 处理 EntityExist */ diff --git a/cdp-common/cdp-common-security/src/main/java/com/baiye/service/CdpUserDetailsService.java b/cdp-common/cdp-common-security/src/main/java/com/baiye/service/CdpUserDetailsService.java new file mode 100644 index 0000000..3c5639c --- /dev/null +++ b/cdp-common/cdp-common-security/src/main/java/com/baiye/service/CdpUserDetailsService.java @@ -0,0 +1,22 @@ +package com.baiye.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * 用户详细扩展 + * @author pangu + */ +public interface CdpUserDetailsService extends UserDetailsService { + + /** + * 根据手机号登录 + * @param mobile + * @return UserDetails + * @throws UsernameNotFoundException + */ + UserDetails loadUserByMobile(String mobile) throws UsernameNotFoundException; + + +} diff --git a/cdp-common/cdp-common-security/src/main/java/com/baiye/service/UserDetailsServiceImpl.java b/cdp-common/cdp-common-security/src/main/java/com/baiye/service/UserDetailsServiceImpl.java index c269d0d..55477a3 100644 --- a/cdp-common/cdp-common-security/src/main/java/com/baiye/service/UserDetailsServiceImpl.java +++ b/cdp-common/cdp-common-security/src/main/java/com/baiye/service/UserDetailsServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import com.baiye.component.LoginUser; import com.baiye.config.LoginProperties; import com.baiye.core.constant.CacheKey; +import com.baiye.core.constant.DefaultNumberConstants; import com.baiye.core.constant.SecurityConstants; import com.baiye.dto.UserSmallDto; import com.baiye.feign.IRemoteUserService; @@ -15,7 +16,6 @@ import org.springframework.cache.CacheManager; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -25,15 +25,14 @@ import java.util.List; import java.util.Set; /** - * - * @description 用户详细信息 * @author Enzo + * @description 用户详细信息 * @date 2020-08-05 17:35 */ @Slf4j @Service @RequiredArgsConstructor -public class UserDetailsServiceImpl implements UserDetailsService { +public class UserDetailsServiceImpl implements CdpUserDetailsService { /** @@ -41,20 +40,21 @@ public class UserDetailsServiceImpl implements UserDetailsService { */ private final IRemoteUserService remoteUserService; - /** - * 缓存管理器,在第一次调用加载用户的方法系统会自动将其缓存 - */ - private final CacheManager cacheManager; - /** * 用户登录设置 */ private final LoginProperties loginProperties; + /** + * 缓存管理器,在第一次调用加载用户的方法系统会自动将其缓存 + */ + private final CacheManager cacheManager; + /** * 设置用户信息是否被缓存 + * * @param enableCache */ public void setEnableCache(Boolean enableCache) { @@ -63,6 +63,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { /** * 通过用户名加载用户 + * * @param username * @return * @throws UsernameNotFoundException @@ -88,6 +89,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { /** * 构建UserDetails + * * @param user * @return */ @@ -95,30 +97,43 @@ public class UserDetailsServiceImpl implements UserDetailsService { if (user == null) { throw new UsernameNotFoundException("用户不存在"); } - // SysUser sysUser = result.getBody(); Set dbAuthsSet = new HashSet<>(); if (CollUtil.isNotEmpty(user.getRoleNames())) { - user.getRoleNames().forEach(role -> { - dbAuthsSet.add(SecurityConstants.ROLE + role); - }); - // TODO: 2020/8/12 远程查询部门数据权限 - //...... + user.getRoleNames().forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role)); } - if (user.getIsAdmin()){ + if (Boolean.TRUE.equals(user.getIsAdmin())) { dbAuthsSet.add(SecurityConstants.ADMIN); - }else if (CollUtil.isNotEmpty(user.getPermissions())){ + } else if (CollUtil.isNotEmpty(user.getPermissions())) { dbAuthsSet.addAll(user.getPermissions()); } // 封装为Collection String[] authArray = dbAuthsSet.stream().filter(StringUtils::isNotBlank).toArray(String[]::new); // 不能传入空字符串 List authorityList = null; - if (CollUtil.isNotEmpty(dbAuthsSet)){ + if (CollUtil.isNotEmpty(dbAuthsSet)) { authorityList = AuthorityUtils.createAuthorityList(authArray); } return new LoginUser(user.getId(), user.getUsername(), user.getPassword(), user.getEnabled(), true, true, true, authorityList, new ArrayList<>()); } + + @Override + public UserDetails loadUserByMobile(String mobile) throws UsernameNotFoundException { + Cache cache = this.cacheManager.getCache(CacheKey.USER_DETAILS); + if (cache != null && cache.get(mobile) != null) { + // 缓存中存在,直接从缓存中获取 + return (LoginUser) cache.get(mobile).get(); + } + // 缓存不存在则调用feign获取用户信息 + UserSmallDto result = this.remoteUserService.getUserDetailsByMobile(mobile, SecurityConstants.FROM_IN); + UserDetails userDetails = this.getUserDetails(result); + // 放入缓存 + if (Boolean.TRUE.equals(loginProperties.getCacheEnable())) { + assert cache != null; + cache.put(mobile, userDetails); + } + return userDetails; + } } diff --git a/cdp-iaas/authorization-server/pom.xml b/cdp-iaas/authorization-server/pom.xml index cb1bdbd..3ff3837 100644 --- a/cdp-iaas/authorization-server/pom.xml +++ b/cdp-iaas/authorization-server/pom.xml @@ -60,6 +60,12 @@ 1.0-SNAPSHOT + + com.aliyun + dysmsapi20170525 + 2.0.23 + + diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/AuthServerConfig.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/AuthServerConfig.java index a394b50..4e6185d 100644 --- a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/AuthServerConfig.java +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/AuthServerConfig.java @@ -1,72 +1,97 @@ package com.baiye.auth.config; -import com.baiye.auth.translator.CustomWebRespExceptionTranslator; +import com.baiye.auth.feign.IRemoteMemberService; +import com.baiye.auth.sms.granter.SmsCodeTokenGranter; import com.baiye.auth.service.IOnlineUserService; +import com.baiye.auth.translator.CustomWebRespExceptionTranslator; import com.baiye.core.constant.CacheKey; import com.baiye.core.constant.SecurityConstants; -import com.baiye.component.LoginUser; +import com.baiye.core.util.RedisUtils; import com.baiye.service.CustomClientDetailsService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.CompositeTokenGranter; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import javax.sql.DataSource; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; /** - * - * @description 授权服务配置 * @author Enzo + * @description 授权服务配置 * @date 2020-08-03 16:14 */ @Configuration @EnableAuthorizationServer @RequiredArgsConstructor public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { + @Value("${cdp.auth.isSingleLogin:false}") + private boolean isSingleLogin; + + /** + * redis连接对象 + */ + private final RedisConnectionFactory redisConnectionFactory; + /** * 认证管理器 */ private final AuthenticationManager authenticationManager; + private final IRemoteMemberService remoteMemberService; + /** - * 在线用户管理 + * clientService */ - private IOnlineUserService onlineUserService; + private final ClientDetailsService clientService; + /** - * 数据源,使用sql查询客户端信息 + * UserDetailsService,用来加载用户对象 */ - private final DataSource dataSource; + private final UserDetailsService userDetailsService; + + /** - * redis连接对象 + * 在线用户管理 */ - private final RedisConnectionFactory redisConnectionFactory; + private IOnlineUserService onlineUserService; /** - * UserDetailsService,用来加载用户对象 + * 数据源,使用sql查询客户端信息 */ - private final UserDetailsService userDetailsService; + private final DataSource dataSource; + + + private final RedisUtils redisUtils; @Autowired @@ -75,8 +100,6 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { } - - @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) { oauthServer.allowFormAuthenticationForClients().checkTokenAccess("permitAll()"); @@ -99,10 +122,22 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Override @SuppressWarnings("unchecked") - public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + DefaultTokenServices tokenServices = createDefaultTokenServices(); + // token增强链 + TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); + // 把jwt增强,与额外信息增强加入到增强链 + tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter())); + tokenServices.setTokenEnhancer(tokenEnhancerChain); + // 配置tokenServices参数 + addUserDetailsService(tokenServices); + endpoints + .tokenGranter(tokenGranter(endpoints, tokenServices)) + .tokenServices(tokenServices) + .accessTokenConverter(jwtAccessTokenConverter()) // 配置请求方式 - .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST) + .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) // 配置token的存储位置 .tokenStore(redisTokenStore()) // 自定义生成token @@ -118,36 +153,46 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { .exceptionTranslator(new CustomWebRespExceptionTranslator()); } + + private DefaultTokenServices createDefaultTokenServices() { + DefaultTokenServices tokenServices = new SingleLoginTokenServices(isSingleLogin, onlineUserService); + tokenServices.setTokenStore(redisTokenStore()); + // 支持刷新Token + tokenServices.setSupportRefreshToken(Boolean.TRUE); + tokenServices.setReuseRefreshToken(Boolean.FALSE); + tokenServices.setClientDetailsService(clientService); + addUserDetailsService(tokenServices); + return tokenServices; + } + /** * 自定义token生成器 + * * @return */ @Bean public TokenEnhancer tokenEnhancer() { return (accessToken, authentication) -> { - if (accessToken instanceof DefaultOAuth2AccessToken){ - LoginUser loginUser = (LoginUser) authentication.getUserAuthentication().getPrincipal(); - // TODO: 2020/8/5 在token中后续添加权限信息 - /* - * 获取权限列表 - */ - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - Map additionalInformation = new LinkedHashMap(4); - additionalInformation.put(SecurityConstants.DETAILS_USER_ID,loginUser.getUserId()); - additionalInformation.put(SecurityConstants.DETAILS_USERNAME, loginUser.getUsername()); - additionalInformation.put(SecurityConstants.DATA_SCOPES, loginUser.getDataScopes()); - additionalInformation.put(SecurityConstants.AUTHORITIES_KEY,authorities); - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); - onlineUserService.saveOnlineUser(accessToken); + if (accessToken instanceof DefaultOAuth2AccessToken) { + SingleLoginTokenServices.setUser(authentication, (DefaultOAuth2AccessToken) accessToken); + // onlineUserService.saveOnlineUser(accessToken); } return accessToken; }; } + + private void addUserDetailsService(DefaultTokenServices tokenServices) { + if (userDetailsService != null) { + PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); + provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService)); + tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider))); + } + } + /** * 用redis实现,将令牌保存到redis中 + * * @return TokenStore */ @Bean @@ -158,4 +203,32 @@ public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { return redisTokenStore; } + + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); + jwtAccessTokenConverter.setSigningKey(SecurityConstants.SIGN_KEY); + return jwtAccessTokenConverter; + } + + + /** + * 重点 + * 先获取已经有的五种授权,然后添加我们自己的进去 + * + * @param endpoints AuthorizationServerEndpointsConfigurer + * @return TokenGranter + */ + private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints, DefaultTokenServices tokenServices) { + List granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter())); + // 短信验证码模式 + granters.add(new SmsCodeTokenGranter(authenticationManager, tokenServices, endpoints.getClientDetailsService(), + endpoints.getOAuth2RequestFactory(), redisUtils, remoteMemberService)); + + // 增加密码模式 + granters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory())); + return new CompositeTokenGranter(granters); + } + + } diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/SingleLoginTokenServices.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/SingleLoginTokenServices.java new file mode 100644 index 0000000..dcc0f99 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/SingleLoginTokenServices.java @@ -0,0 +1,156 @@ +package com.baiye.auth.config; + +import cn.hutool.json.JSONUtil; +import com.baiye.auth.service.IOnlineUserService; +import com.baiye.component.LoginUser; +import com.baiye.core.constant.SecurityConstants; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.common.*; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 重写 DefaultTokenServices,实现登录同应用同账号互踢 + * + * @author pangu + * @date 2021-2-10 + * @since 2.3.8 + */ +public class SingleLoginTokenServices extends DefaultTokenServices { + + private TokenStore tokenStore; + private TokenEnhancer accessTokenEnhancer; + + + /** + * 是否登录同应用同账号互踢 + */ + private final boolean isSingleLogin; + + /** + * 是否登录同应用同账号互踢 + */ + private final IOnlineUserService onlineUserService; + + public SingleLoginTokenServices(boolean isSingleLogin, IOnlineUserService onlineUserService) { + this.isSingleLogin = isSingleLogin; + this.onlineUserService = onlineUserService; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { + OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); + OAuth2RefreshToken refreshToken = null; + if (existingAccessToken != null) { + if (isSingleLogin) { + if (existingAccessToken.getRefreshToken() != null) { + tokenStore.removeRefreshToken(existingAccessToken.getRefreshToken()); + } + tokenStore.removeAccessToken(existingAccessToken); + } else if (existingAccessToken.isExpired()) { + if (existingAccessToken.getRefreshToken() != null) { + refreshToken = existingAccessToken.getRefreshToken(); + // The token store could remove the refresh token when the + // access token is removed, but we want to + // be sure... + tokenStore.removeRefreshToken(refreshToken); + } + tokenStore.removeAccessToken(existingAccessToken); + } else { + // Re-store the access token in case the authentication has changed + tokenStore.storeAccessToken(existingAccessToken, authentication); + return existingAccessToken; + } + } + + // Only create a new refresh token if there wasn't an existing one + // associated with an expired access token. + // Clients might be holding existing refresh tokens, so we re-use it in + // the case that the old access token + // expired. + if (refreshToken == null) { + refreshToken = createRefreshToken(authentication); + } + // But the refresh token itself might need to be re-issued if it has + // expired. + else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { + ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken; + if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { + refreshToken = createRefreshToken(authentication); + } + } + + OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); + tokenStore.storeAccessToken(accessToken, authentication); + // In case it was modified + refreshToken = accessToken.getRefreshToken(); + if (refreshToken != null) { + tokenStore.storeRefreshToken(refreshToken, authentication); + } + setUser(authentication, (DefaultOAuth2AccessToken) accessToken); + onlineUserService.saveOnlineUser(accessToken); + return accessToken; + + } + + static void setUser(OAuth2Authentication authentication, DefaultOAuth2AccessToken accessToken) { + LoginUser user = (LoginUser) authentication.getUserAuthentication().getPrincipal(); + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + Map additionalInformation = new LinkedHashMap<>(4); + additionalInformation.put(SecurityConstants.DETAILS_USER_ID, user.getUserId()); + additionalInformation.put(SecurityConstants.DETAILS_USERNAME, user.getUsername()); + additionalInformation.put(SecurityConstants.DATA_SCOPES, user.getDataScopes()); + additionalInformation.put(SecurityConstants.AUTHORITIES_KEY, authorities); + accessToken.setAdditionalInformation(additionalInformation); + } + + private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { + if (!isSupportRefreshToken(authentication.getOAuth2Request())) { + return null; + } + int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); + String value = UUID.randomUUID().toString(); + if (validitySeconds > 0) { + return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() + + (validitySeconds * 1000L))); + } + return new DefaultOAuth2RefreshToken(value); + } + + private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); + int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); + if (validitySeconds > 0) { + token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); + } + token.setRefreshToken(refreshToken); + token.setScope(authentication.getOAuth2Request().getScope()); + + return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; + } + + @Override + public void setTokenStore(TokenStore tokenStore) { + this.tokenStore = tokenStore; + super.setTokenStore(tokenStore); + } + + @Override + public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { + this.accessTokenEnhancer = accessTokenEnhancer; + super.setTokenEnhancer(accessTokenEnhancer); + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/properties/SmsProperties.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/properties/SmsProperties.java new file mode 100644 index 0000000..22081cb --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/config/properties/SmsProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.baiye.auth.config.properties; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author Zheng Jie + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "sms") +public class SmsProperties { + + @ApiModelProperty(value = "accessKeyId") + private String accessKeyId; + + @ApiModelProperty(value = "accessKeySecret") + private String accessKeySecret; + + @ApiModelProperty(value = "signName") + private String signName; + + @ApiModelProperty(value = "templateCode") + private String templateCode; + + +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/OauthController.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/OauthController.java new file mode 100644 index 0000000..465fc3e --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/OauthController.java @@ -0,0 +1,58 @@ +package com.baiye.auth.controller; + +import cn.hutool.crypto.digest.DigestUtil; +import com.alibaba.nacos.common.utils.Md5Utils; +import com.baiye.core.annotation.Log; +import com.baiye.core.base.api.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 自定义Oauth2自定义返回格式 + * + * @author pangu + * @link https://segmentfault.com/a/1190000020317220?utm_source=tag-newest + */ +@RestController +@RequestMapping("/oauth") +@AllArgsConstructor +@Api(value = "Oauth2管理") +public class OauthController { + + private final TokenEndpoint tokenEndpoint; + + + @Log(value = "用户登录", exception = "用户登录请求异常") + @PostMapping("/token") + @ApiOperation(value = "用户登录Post") + @ApiImplicitParams({ + @ApiImplicitParam(value = "grant_type", required = true, name = "授权类型"), + @ApiImplicitParam(value = "username", required = true, name = "用户名"), + @ApiImplicitParam(value = "password", required = true, name = "密码"), + @ApiImplicitParam(value = "scope", required = true, name = "使用范围"), + }) + public Result> postAccessToken(Principal principal, @RequestParam Map parameters) throws HttpRequestMethodNotSupportedException { + OAuth2AccessToken body = tokenEndpoint.postAccessToken(principal, parameters).getBody(); + DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) body; + Map data = new LinkedHashMap<>(token.getAdditionalInformation()); + data.put("accessToken", token.getValue()); + if (token.getRefreshToken() != null) { + data.put("refreshToken", token.getRefreshToken().getValue()); + } + return Result.data(data); + } + + +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/SmsController.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/SmsController.java new file mode 100644 index 0000000..2cc394c --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/controller/SmsController.java @@ -0,0 +1,31 @@ +package com.baiye.auth.controller; + +import com.baiye.auth.service.SmsService; +import com.baiye.core.base.api.Result; +import io.swagger.annotations.Api; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; + +/** + * @author Enzo + * @date : 2023/3/15 + */ +@RestController +@RequestMapping("/sms") +@Api(value = "阿里云短信服务") +@RequiredArgsConstructor +public class SmsController { + + private final SmsService smsService; + + @GetMapping("/getCode") + @ResponseBody + public Result sendSMS(@RequestParam String mobile){ + if (StringUtils.isBlank(mobile)) { + return Result.fail("发送短信失败"); + } + return smsService.sendSms(mobile) ? Result.success() : Result.fail(); + } + +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/feign/IRemoteMemberService.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/feign/IRemoteMemberService.java new file mode 100644 index 0000000..1d95d30 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/feign/IRemoteMemberService.java @@ -0,0 +1,30 @@ +package com.baiye.auth.feign; + +import com.baiye.core.constant.SecurityConstants; +import com.baiye.core.constant.ServiceNameConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * @author Enzo + * @description 用户feign接口 + * @date 2020-08-12 16:50 + */ +@FeignClient(contextId = "remoteMemberService", value = ServiceNameConstants.BACKSTAGE_SERVER) +public interface IRemoteMemberService { + + + /** + * 号码查询会员 + * + * @param username + * @param from + * @return + */ + @GetMapping(value = "/member/createOrUpdate") + Boolean createOrUpdate(@RequestParam("username") String username, @RequestHeader(SecurityConstants.FROM) String from); + + +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/SmsService.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/SmsService.java new file mode 100644 index 0000000..98e8f2d --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/SmsService.java @@ -0,0 +1,15 @@ +package com.baiye.auth.service; + +/** + * @author Enzo + * @date : 2023/3/15 + */ +public interface SmsService { + + /** + * 发送短信 + * @param mobile + * @return + */ + Boolean sendSms(String mobile); +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/impl/SmsServiceImpl.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/impl/SmsServiceImpl.java new file mode 100644 index 0000000..907bbf9 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/service/impl/SmsServiceImpl.java @@ -0,0 +1,64 @@ +package com.baiye.auth.service.impl; + +import cn.hutool.core.util.RandomUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import com.baiye.auth.config.properties.SmsProperties; +import com.baiye.auth.service.SmsService; +import com.baiye.core.constant.DefaultNumberConstants; +import com.baiye.core.constant.Oauth2Constant; +import com.baiye.core.util.RedisUtils; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +/** + * @author Enzo + * @date : 2023/3/15 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SmsServiceImpl implements SmsService { + + private final RedisUtils redisUtils; + + private final SmsProperties smsProperties; + + + @Override + @SneakyThrows + public Boolean sendSms(String mobile) { + Config config = new Config() + .setAccessKeyId(smsProperties.getAccessKeyId()) + .setAccessKeySecret(smsProperties.getAccessKeySecret()); + config.endpoint = "dysmsapi.aliyuncs.com"; + String code = RandomUtil.randomNumbers(DefaultNumberConstants.SIX_NUMBER); + Client client = new Client(config); + SendSmsRequest sendSmsRequest = new SendSmsRequest(); + sendSmsRequest + .setSignName(smsProperties.getSignName()) + .setPhoneNumbers(mobile) + .setTemplateCode(smsProperties.getTemplateCode()); + HashMap param = new HashMap<>(DefaultNumberConstants.EIGHT_NUMBER); + param.put("code", code); + sendSmsRequest.setTemplateParam(JSON.toJSONString(param)); + RuntimeOptions runtime = new RuntimeOptions(); + SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, runtime); + if ("OK".equals(response.body.code)) { + redisUtils.set(Oauth2Constant.SMS_CODE_KEY.concat(mobile), + code, DefaultNumberConstants.FIVE_NUMBER, TimeUnit.MINUTES); + return Boolean.TRUE; + } + return Boolean.FALSE; + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationFilter.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationFilter.java new file mode 100644 index 0000000..a0f0584 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationFilter.java @@ -0,0 +1,80 @@ +package com.baiye.auth.sms; + +import com.baiye.core.constant.Oauth2Constant; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.swing.text.html.FormSubmitEvent; +import java.util.Objects; + +/** + * 短信验证码验证过滤器 + * + * @author pangu + */ +public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + /** + * 请求中的参数 + */ + private String mobileParameter = Oauth2Constant.DEFAULT_PARAMETER_NAME_MOBILE; + + private boolean postOnly = true; + + public SmsCodeAuthenticationFilter() { + super(new AntPathRequestMatcher(Oauth2Constant.OAUTH_MOBILE, "POST")); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + if (postOnly && !FormSubmitEvent.MethodType.POST.name().equals(request.getMethod())) { + throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); + } + + // 获取请求中的参数值 + String mobile = obtainMobile(request); + + if (Objects.isNull(mobile)) { + mobile = ""; + } + + mobile = mobile.trim(); + + SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile); + + // Allow subclasses to set the "details" property + setDetails(request, authRequest); + + return this.getAuthenticationManager().authenticate(authRequest); + } + + /** + * 获取手机号 + */ + protected String obtainMobile(HttpServletRequest request) { + return request.getParameter(mobileParameter); + } + + protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + } + + public void setMobileParameter(String mobileParameter) { + Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null"); + this.mobileParameter = mobileParameter; + } + + public void setPostOnly(boolean postOnly) { + this.postOnly = postOnly; + } + + public final String getMobileParameter() { + return mobileParameter; + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationProvider.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationProvider.java new file mode 100644 index 0000000..1038bff --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationProvider.java @@ -0,0 +1,49 @@ +package com.baiye.auth.sms; + +import com.baiye.service.CdpUserDetailsService; +import lombok.AllArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; + +import java.util.Objects; + +/** + * 短信验证码验证提供者 + * + * @author pangu + */ +@AllArgsConstructor +public class SmsCodeAuthenticationProvider implements AuthenticationProvider { + + private final CdpUserDetailsService userDetailsService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication; + + /** + * 调用 {@link UserDetailsService} + */ + UserDetails user = userDetailsService.loadUserByMobile((String) authenticationToken.getPrincipal()); + + if (Objects.isNull(user)) { + throw new InternalAuthenticationServiceException("手机号或验证码错误"); + } + + SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities()); + + authenticationResult.setDetails(authenticationToken.getDetails()); + + return authenticationResult; + + } + + @Override + public boolean supports(Class authentication) { + return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationSecurityConfig.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationSecurityConfig.java new file mode 100644 index 0000000..c690ab8 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationSecurityConfig.java @@ -0,0 +1,38 @@ +package com.baiye.auth.sms; + +import com.baiye.service.CdpUserDetailsService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; + +/** + * 短信验证码登录方式配置 + * + * @author pangu + */ +@Component +@RequiredArgsConstructor +public class SmsCodeAuthenticationSecurityConfig + extends SecurityConfigurerAdapter { + + private final CdpUserDetailsService userDetailsService; + + @Override + public void configure(HttpSecurity http) { + + // 过滤器 + SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(); + smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); + + // 获取验证码提供者 + SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider(userDetailsService); + + // 将短信验证码校验器注册到 HttpSecurity, 并将短信验证码过滤器添加在 UsernamePasswordAuthenticationFilter 之前 + http.authenticationProvider(smsCodeAuthenticationProvider) + .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationToken.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationToken.java new file mode 100644 index 0000000..84f10f2 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/SmsCodeAuthenticationToken.java @@ -0,0 +1,58 @@ +package com.baiye.auth.sms; + +import lombok.SneakyThrows; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * 手机号+验证码登录令牌获取 + * + * @author pangu + * @since 2020-7-21 + */ +public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = -3629824093049247125L; + + private final Object principal; + + public SmsCodeAuthenticationToken(String mobile) { + super(null); + this.principal = mobile; + setAuthenticated(false); + } + + public SmsCodeAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return this.principal; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + @Override + @SneakyThrows + public void setAuthenticated(boolean isAuthenticated) { + if (isAuthenticated) { + throw new IllegalArgumentException( + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + } + + super.setAuthenticated(false); + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/granter/SmsCodeTokenGranter.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/granter/SmsCodeTokenGranter.java new file mode 100644 index 0000000..e8f7bb6 --- /dev/null +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/sms/granter/SmsCodeTokenGranter.java @@ -0,0 +1,99 @@ +package com.baiye.auth.sms.granter; + +import cn.hutool.core.text.CharSequenceUtil; +import com.baiye.auth.feign.IRemoteMemberService; +import com.baiye.auth.sms.SmsCodeAuthenticationToken; +import com.baiye.core.constant.Oauth2Constant; +import com.baiye.core.constant.SecurityConstants; +import com.baiye.core.util.RedisUtils; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 手机号验证码登录TokenGranter + * + * @author pangu + * @since 2020-7-21 + */ +public class SmsCodeTokenGranter extends AbstractTokenGranter { + + + private final AuthenticationManager authenticationManager; + + private final IRemoteMemberService remoteMemberService; + + private RedisUtils redisUtils; + + public SmsCodeTokenGranter(AuthenticationManager authenticationManager, + AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, RedisUtils redisUtils,IRemoteMemberService remoteMemberService) { + this(authenticationManager, tokenServices, clientDetailsService, requestFactory, SecurityConstants.SMS_GRANT_TYPE, remoteMemberService); + this.redisUtils = redisUtils; + } + + protected SmsCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType, IRemoteMemberService remoteMemberService) { + super(tokenServices, clientDetailsService, requestFactory, grantType); + this.authenticationManager = authenticationManager; + this.remoteMemberService = remoteMemberService; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); + String mobile = parameters.get("mobile"); + String code = parameters.get("code"); + + if (CharSequenceUtil.isBlank(code)) { + throw new UserDeniedAuthorizationException("请输入验证码!"); + } + + String codeFromRedis; + // 从Redis里读取存储的验证码信息 + try { + codeFromRedis = redisUtils.get(Oauth2Constant.SMS_CODE_KEY + mobile).toString(); + } catch (Exception e) { + throw new UserDeniedAuthorizationException("验证码不存在!"); + } + + if (codeFromRedis == null) { + throw new UserDeniedAuthorizationException("验证码已过期!"); + } + // 比较输入的验证码是否正确 + if (!CharSequenceUtil.equalsIgnoreCase(code, codeFromRedis)) { + throw new UserDeniedAuthorizationException("验证码不正确!"); + } + + redisUtils.del(Oauth2Constant.SMS_CODE_KEY + mobile); + // 创建或修改账号 + remoteMemberService.createOrUpdate(mobile, SecurityConstants.FROM_IN); + + Authentication userAuth = new SmsCodeAuthenticationToken(mobile); + ((AbstractAuthenticationToken) userAuth).setDetails(parameters); + try { + userAuth = authenticationManager.authenticate(userAuth); + } catch (AccountStatusException | BadCredentialsException ase) { + //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) + throw new InvalidGrantException(ase.getMessage()); + } + // If the username/password are wrong the spec says we should send 400/invalid grant + + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("Could not authenticate user: " + mobile); + } + + OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, userAuth); + } +} diff --git a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/translator/CustomWebRespExceptionTranslator.java b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/translator/CustomWebRespExceptionTranslator.java index b95e023..dcd21de 100644 --- a/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/translator/CustomWebRespExceptionTranslator.java +++ b/cdp-iaas/authorization-server/src/main/java/com/baiye/auth/translator/CustomWebRespExceptionTranslator.java @@ -23,7 +23,6 @@ public class CustomWebRespExceptionTranslator implements WebResponseExceptionTra @Override public ResponseEntity translate(Exception e) { - ResponseEntity.BodyBuilder status = ResponseEntity.status(HttpStatus.UNAUTHORIZED); String message = "认证失败"; log.error(message, e); if (e instanceof UnsupportedGrantTypeException) { diff --git a/cdp-iaas/gateway-server/src/main/java/com/baiye/filter/ValidateCodeFilter.java b/cdp-iaas/gateway-server/src/main/java/com/baiye/filter/ValidateCodeFilter.java index 5ef78f5..22e4dff 100644 --- a/cdp-iaas/gateway-server/src/main/java/com/baiye/filter/ValidateCodeFilter.java +++ b/cdp-iaas/gateway-server/src/main/java/com/baiye/filter/ValidateCodeFilter.java @@ -54,7 +54,8 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory { } // 如果是刷新token的请求,直接放行 String grandType = request.getQueryParams().getFirst("grant_type"); - if (CharSequenceUtil.equals(SecurityConstants.REFRESH_TOKEN,grandType)){ + if (CharSequenceUtil.equals(SecurityConstants.REFRESH_TOKEN,grandType) + || CharSequenceUtil.equals(SecurityConstants.SMS_GRANT_TYPE,grandType)){ return chain.filter(exchange); } // 主要是对swagger认证进行放行 diff --git a/cdp-iaas/gateway-server/src/main/resources/config/application-dev.yml b/cdp-iaas/gateway-server/src/main/resources/config/application-dev.yml index 1a1e5fa..59f4556 100644 --- a/cdp-iaas/gateway-server/src/main/resources/config/application-dev.yml +++ b/cdp-iaas/gateway-server/src/main/resources/config/application-dev.yml @@ -12,43 +12,6 @@ spring: driver-class-name: com.mysql.jdbc.Driver username: ${MYSQL_USER:root} password: ${MYSQL_PASSWORD:y7z7noq2} - cloud: - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true # admin-service ADMIN-SERVICE /admin-service/** -> 微服务 (ADMIN-SERVICE) 自动转发,忽略大小写 - routes: - - id: backstage-server - uri: lb://backstage-server # 负载均衡转发到哪个目的地 - predicates: - - Path=/sys/** - filters: - - StripPrefix=1 - - id: cdp-wechat-api - uri: lb://cdp-wechat-api - predicates: - - Path=/api-wechat/** - filters: - - StripPrefix=1 - - id: cdp-entrance-api - uri: lb://cdp-entrance-api - predicates: - - Path=/api-file/** - filters: - - StripPrefix=1 - - id: dy-tool-member-api - uri: lb://dy-tool-member-api - predicates: - - Path=/api-tiktok/** - filters: - - StripPrefix=1 - - id: cdp-tool-xhs-api - uri: lb://cdp-tool-xhs-api - predicates: - - Path=/api-xhs/** - filters: - - StripPrefix=1 nacos: server-addr: 127.0.0.1:8848 # server-addr: 101.35.109.129 diff --git a/cdp-iaas/gateway-server/src/main/resources/config/application-prod.yml b/cdp-iaas/gateway-server/src/main/resources/config/application-prod.yml index a76bc90..96061a5 100644 --- a/cdp-iaas/gateway-server/src/main/resources/config/application-prod.yml +++ b/cdp-iaas/gateway-server/src/main/resources/config/application-prod.yml @@ -12,37 +12,7 @@ spring: driver-class-name: com.mysql.jdbc.Driver username: ${MYSQL_USER:root} password: ${MYSQL_PASSWORD:baiye@RDS2023.} - cloud: - gateway: - discovery: - locator: - enabled: true - lower-case-service-id: true # admin-service ADMIN-SERVICE /admin-service/** -> 微服务 (ADMIN-SERVICE) 自动转发,忽略大小写 - routes: - - id: backstage-server - uri: lb://backstage-server # 负载均衡转发到哪个目的地 - predicates: - - Path=/sys/** - filters: - - StripPrefix=1 - - id: cdp-wechat-api - uri: lb://cdp-wechat-api - predicates: - - Path=/api-wechat/** - filters: - - StripPrefix=1 - - id: cdp-entrance-api - uri: lb://cdp-entrance-api - predicates: - - Path=/api-file/** - filters: - - StripPrefix=1 - - id: cdp-tool-xhs-api - uri: lb://cdp-tool-xhs-api - predicates: - - Path=/api-xhs/** - filters: - - StripPrefix=1 + nacos: server-addr: 172.16.69.134:8848 # server-addr: 101.35.109.129:8848 diff --git a/cdp-iaas/gateway-server/src/main/resources/config/application.yml b/cdp-iaas/gateway-server/src/main/resources/config/application.yml index 01dc4f2..0ecac70 100644 --- a/cdp-iaas/gateway-server/src/main/resources/config/application.yml +++ b/cdp-iaas/gateway-server/src/main/resources/config/application.yml @@ -1,9 +1,46 @@ spring: - application: - name: @artifactId@ profiles: active: dev + application: + name: @artifactId@ + cloud: + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true # admin-service ADMIN-SERVICE /admin-service/** -> 微服务 (ADMIN-SERVICE) 自动转发,忽略大小写 + routes: + - id: backstage-server + uri: lb://backstage-server # 负载均衡转发到哪个目的地 + predicates: + - Path=/sys/** + filters: + - StripPrefix=1 + - id: cdp-wechat-api + uri: lb://cdp-wechat-api + predicates: + - Path=/api-wechat/** + filters: + - StripPrefix=1 + - id: cdp-entrance-api + uri: lb://cdp-entrance-api + predicates: + - Path=/api-file/** + filters: + - StripPrefix=1 + - id: cdp-tool-xhs-api + uri: lb://cdp-tool-xhs-api + predicates: + - Path=/api-xhs/** + filters: + - StripPrefix=1 + - id: auth-server + uri: lb://auth-server + predicates: + - Path=/cdp-uaa/** + filters: + - StripPrefix=1 security: decode: private-key: MIICXAIBAAKBgQDYws76QjIeL8KDfr9sAFF14ccKy+iF9j71JO/5brB969Gjjf4gr9JM7OYWr2T51lWa+4sCt7/zn7XlIo9vmOD2mqohC0D4gSkixBlDAlp511OQQbZlb4fEw8W1/IltcpTolhjYh9dQoilmUThZB6bhims/0P6auSN6qvtIVS0tPwIDAQABAoGAREyNvxkghZZy6dAELNmk0UoE14gMijle+QtcefHAtsyZT7mr+0yrLQXwMfGuFXLNonnkAUU4vGD0hXBwVa+MIlMWnPCb9fxuj45dXQjycoD2kCY6l+rhuEJqu/ruHAWBnp30vGtLf2+EoRKFAAoGiwILiHa3cDr/k+n3JwRXn9ECQQD9+rN7Z2gvakWYgwwJIV5ebKd/MVainYrp0biYNlqi8ELgMlFtFahMLnqDpy+vhnRdcnha2672E3tc7GjKPzfFAkEA2nxNUHb5hCU26eudBcZ9lxaMw3o+6F3aGDLv9SVwvHtQWXN0CZM72UO7/E2dfgeelUyNakk3WqG56WFmoLDdMwJABeX2mR0TrFY5e4s/kk62FFdNpISO0IP8H+YA9Xf9rt8Jjo9cmL3yBKLnsXsGfnsO5MStyt5jN8/IA6Zx4JCLSQJAMVYmY0gqegOpTdNNpvM2gvqtmKqvL+uZhyNhejsVJQq3jyt6BXuA5UPdXFDugnoX/mDGAj08SbQBdkjvUtP9bwJBAIPaqbt+qHxXcBqpUaM3zDp1hQHH2xxpYSJsJWF99RB5srRL4AsA4N6Hy63zo6335k1IAPaH6uk9vobyhiCC/E0= diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/Member.java b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/Member.java new file mode 100644 index 0000000..d2a0940 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/Member.java @@ -0,0 +1,83 @@ +package com.baiye; + +import com.baiye.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.Date; +import java.util.Objects; + +/** + * @author Zheng Jie + * @date 2019-03-25 + */ +@Setter +@Getter +@TableName("tb_member") +public class Member extends BaseEntity implements Serializable { + + private static final long serialVersionUID = -3797115638759597781L; + + @NotNull(groups = {Update.class}) + @TableId(value = "member_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @NotBlank + @ApiModelProperty(value = "用户名称") + private String username; + + @Email + @NotBlank + @ApiModelProperty(value = "邮箱") + private String email; + + @NotBlank + @ApiModelProperty(value = "电话号码") + private String phone; + + @ApiModelProperty(value = "用户性别") + private String gender; + + @ApiModelProperty(value = "头像真实名称", hidden = true) + private String avatarName; + + @ApiModelProperty(value = "头像存储的路径", hidden = true) + private String avatarPath; + + @ApiModelProperty(value = "密码") + private String password; + + @NotNull + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Member user = (Member) o; + return Objects.equals(id, user.id) && + Objects.equals(username, user.username); + } + + @Override + public int hashCode() { + return Objects.hash(id, username); + } +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/dto/MemberDto.java b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/dto/MemberDto.java new file mode 100644 index 0000000..3227c00 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/dto/MemberDto.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.baiye.dto; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baiye.core.base.BaseDTO; +import com.baiye.core.excel.BoolCustomConverter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +/** + * 部门传输对象 + * @author Enzo + * @date 2022-05-14 + */ +@Getter +@Setter +@ExcelIgnoreUnannotated +public class MemberDto extends BaseDTO implements Serializable { + + private static final long serialVersionUID = 967234921171721967L; + private Long id; + + private Set roles; + + private Set jobs; + + private DeptSmallDto dept; + + private Long deptId; + + @ExcelProperty("用户名") + private String username; + + @ExcelProperty("昵称") + private String nickName; + + @ExcelProperty("邮箱") + private String email; + + @ExcelProperty("手机号") + private String phone; + + @ExcelProperty("性别") + private String gender; + + private String avatarName; + + private String avatarPath; + + @JsonIgnore + private String password; + + @ExcelProperty(value = "是否启用",converter = BoolCustomConverter.class) + private Boolean enabled; + + @ExcelProperty(value = "是否为管理员",converter = BoolCustomConverter.class) + @JsonIgnore + private Boolean isAdmin = false; + + private Date pwdResetTime; +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/feign/IRemoteUserService.java b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/feign/IRemoteUserService.java index 9d149dc..37dc8e0 100644 --- a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/feign/IRemoteUserService.java +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/feign/IRemoteUserService.java @@ -9,18 +9,32 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; /** - * - * @description 用户feign接口 * @author Enzo + * @description 用户feign接口 * @date 2020-08-12 16:50 */ @FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.BACKSTAGE_SERVER) public interface IRemoteUserService { /** - * 通过用户名查询用户 + * 用户名 查找 + * * @param username + * @param from * @return */ @GetMapping(value = "/inner/username") UserSmallDto getUserDetails(@RequestParam("username") String username, @RequestHeader(SecurityConstants.FROM) String from); + + + /** + * 号码查询会员 + * + * @param mobile + * @param from + * @return + */ + @GetMapping(value = "/inner/mobile") + UserSmallDto getUserDetailsByMobile(@RequestParam("mobile") String mobile, @RequestHeader(SecurityConstants.FROM) String from); + + } diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/mapstruct/MemberMapStruct.java b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/mapstruct/MemberMapStruct.java new file mode 100644 index 0000000..7d1e270 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/mapstruct/MemberMapStruct.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.baiye.mapstruct; + +import com.baiye.Member; +import com.baiye.core.base.BaseMapStruct; +import com.baiye.dto.MemberDto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ + +@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MemberMapStruct extends BaseMapStruct { +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/query/MemberQueryCriteria.java b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/query/MemberQueryCriteria.java new file mode 100644 index 0000000..56a9201 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-model/src/main/java/com/baiye/query/MemberQueryCriteria.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2020 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.baiye.query; + +import com.baiye.annotation.Query; +import com.baiye.annotation.type.SelectType; +import lombok.Data; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.List; + +/** + * @author Zheng Jie + * @date 2018-11-23 + */ +@Data +public class MemberQueryCriteria implements Serializable { + + private static final long serialVersionUID = -8942145971476050045L; + + @Query(blurry = {"email", "username", "nickName"}) + private String blurry; + + @Query + private Boolean enabled; + + + @Query(type = SelectType.BETWEEN) + private List createTime; +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/MemberController.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/MemberController.java new file mode 100644 index 0000000..b33874c --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/MemberController.java @@ -0,0 +1,79 @@ +package com.baiye.controller; + +import com.baiye.Member; +import com.baiye.User; +import com.baiye.annotation.Inner; +import com.baiye.core.page.PageResult; +import com.baiye.dto.UserDto; +import com.baiye.dto.UserSmallDto; +import com.baiye.query.MemberQueryCriteria; +import com.baiye.query.UserQueryCriteria; +import com.baiye.service.IMemberService; +import com.baiye.service.IUserService; +import com.baiye.util.SecurityUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +/** + * @author Enzo + * @date 2023-3-16 + */ +@Api(tags = "系统: 会员管理") +@Slf4j +@RestController +@RequestMapping("/member") +@RequiredArgsConstructor +public class MemberController { + + private final IMemberService memberService; + + + @ApiOperation("通过用户id查询用户姓名") + @GetMapping(value = "/id") + public ResponseEntity queryByUserId(@RequestParam Long id) { + return ResponseEntity.ok(this.memberService.getById(id).getUsername()); + } + + @ApiOperation("查询用户") + @GetMapping + @PreAuthorize("@el.check('member:list')") + public ResponseEntity query(MemberQueryCriteria queryCriteria, Pageable pageable) { + return new ResponseEntity<>(this.memberService.queryAll(queryCriteria, pageable), HttpStatus.OK); + } + + + @ApiOperation("修改用户") + @PutMapping + @PreAuthorize("@el.check('user:edit')") + public ResponseEntity update(@Validated(Member.Update.class) @RequestBody Member resources) { + this.memberService.updateMember(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + + @ApiOperation("修改头像") + @PostMapping(value = "/updateAvatar") + public ResponseEntity> updateAvatar(@RequestParam MultipartFile avatar) { + return new ResponseEntity<>(this.memberService.updateAvatar(avatar), HttpStatus.OK); + } + + + @Inner + @ApiOperation("通过用户名查询用户") + @GetMapping(value = "/createOrUpdate") + public ResponseEntity getUserDetails(@RequestParam String mobile){ + return ResponseEntity.ok(this.memberService.createOrUpdate(mobile)); + } + +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/UserDetailsController.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/UserDetailsController.java index 83372b3..6f8eec9 100644 --- a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/UserDetailsController.java +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/controller/UserDetailsController.java @@ -2,6 +2,7 @@ package com.baiye.controller; import com.baiye.annotation.Inner; import com.baiye.dto.UserSmallDto; +import com.baiye.service.IMemberService; import com.baiye.service.IUserService; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; @@ -23,10 +24,21 @@ import org.springframework.web.bind.annotation.RestController; public class UserDetailsController { private final IUserService userService; + private final IMemberService memberService; + @Inner @ApiOperation("通过用户名查询用户") @GetMapping(value = "/username") public ResponseEntity getUserDetails(@RequestParam String username){ return ResponseEntity.ok(this.userService.getUserDetails(username)); } + + + @Inner + @ApiOperation("通过用户名查询用户") + @GetMapping(value = "/mobile") + public ResponseEntity getUserDetailsByMobile(@RequestParam String mobile){ + return ResponseEntity.ok(this.memberService.getUserDetailsByMobile(mobile)); + } + } diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/mapper/MemberMapper.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/mapper/MemberMapper.java new file mode 100644 index 0000000..8bfa39a --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/mapper/MemberMapper.java @@ -0,0 +1,53 @@ +package com.baiye.mapper; + +import com.baiye.Member; +import com.baiye.User; +import com.baiye.dto.UserSmallDto; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.List; + +/** + * @author Enzo + * @date : 2023/3/16 + */ +public interface MemberMapper extends BaseMapper { + + /** + * 条件查询会员 + * @param wrapper + * @return + */ + List queryAll(QueryWrapper wrapper); + + /** + * 条件查询分页 + * @param wrapper + * @param page + * @return + */ + Page queryAllWithPage(QueryWrapper wrapper, Page page); + + /** + * id查询会员 + * @param id + * @return + */ + Member getById(Long id); + + /** + * 用户昵称查找 + * @param currentUsername + * @return + */ + Member queryByUsername(String currentUsername); + + /** + * 会员会员查找 + * @param mobile + * @return + */ + UserSmallDto getUserDetails(String mobile); +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/IMemberService.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/IMemberService.java new file mode 100644 index 0000000..7fd7dd4 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/IMemberService.java @@ -0,0 +1,54 @@ +package com.baiye.service; + +import com.baiye.Member; +import com.baiye.User; +import com.baiye.core.page.PageResult; +import com.baiye.dto.UserSmallDto; +import com.baiye.query.MemberQueryCriteria; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +/** + * @author Enzo + * @date : 2023/3/16 + */ +public interface IMemberService extends IService { + + /** + * 条件查询会员 + * @param queryCriteria + * @param pageable + * @return + */ + PageResult queryAll(MemberQueryCriteria queryCriteria, Pageable pageable); + + /** + * 修改会员信息 + * @param resources + */ + void updateMember(Member resources); + + /** + * 修改头像 + * @param avatar + * @return + */ + Map updateAvatar(MultipartFile avatar); + + /** + * 号码获取会员详细信息 + * @param mobile + * @return + */ + UserSmallDto getUserDetailsByMobile(String mobile); + + /** + * 创建或修改会员 + * @param mobile + * @return + */ + Boolean createOrUpdate(String mobile); +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/MemberServiceImpl.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/MemberServiceImpl.java new file mode 100644 index 0000000..0271b39 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/MemberServiceImpl.java @@ -0,0 +1,159 @@ +package com.baiye.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baiye.Member; +import com.baiye.core.base.api.ResultCode; +import com.baiye.core.constant.CacheKey; +import com.baiye.core.page.PageResult; +import com.baiye.core.util.RedisUtils; +import com.baiye.dto.UserSmallDto; +import com.baiye.exception.global.BadRequestException; +import com.baiye.exception.global.UpdateFailException; +import com.baiye.feign.IRemoteAuthService; +import com.baiye.mapper.MemberMapper; +import com.baiye.mapstruct.MemberMapStruct; +import com.baiye.properties.FileProperties; +import com.baiye.query.MemberQueryCriteria; +import com.baiye.service.IMemberService; +import com.baiye.util.FileUtil; +import com.baiye.util.PageUtils; +import com.baiye.util.QueryHelpUtils; +import com.baiye.util.SecurityUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.ImmutableMap; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotBlank; +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + * @author Enzo + * @date : 2023/3/16 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MemberServiceImpl extends ServiceImpl implements IMemberService { + + private final RedisUtils redisUtils; + + private final MemberMapper memberMapper; + + private final FileProperties properties; + + private final MemberMapStruct memberMapStruct; + + private final IRemoteAuthService remoteAuthService; + + @Override + public PageResult queryAll(MemberQueryCriteria queryCriteria, Pageable pageable) { + + QueryWrapper wrapper = null; + if (BeanUtil.isNotEmpty(queryCriteria)) { + wrapper = QueryHelpUtils.getWrapper(queryCriteria, Member.class); + } + Page page = PageUtils.startPageAndSort(pageable); + Page memberPage = this.memberMapper.queryAllWithPage(wrapper, page); + return PageResult.success(memberPage.getTotal(), memberPage.getPages(), + this.memberMapStruct.toDto(memberPage.getRecords())); + } + + @Override + public void updateMember(Member resources) { + // 如果用户被禁用,则清除用户登陆的信息 + if (Boolean.FALSE.equals(resources.getEnabled())) { + try { + this.remoteAuthService.delete(Collections.singleton(resources.getId())); + } catch (Exception e) { + throw new BadRequestException(ResultCode.FAILURE.getMsg()); + } + } + // 更新用户信息 + boolean result = this.updateById(resources); + if (!result) { + // 更新失败,全部回滚 + log.error("更新失败:{}", resources); + throw new UpdateFailException("更新失败,请联系管理员"); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Map updateAvatar(MultipartFile avatar) { + // 文件大小验证 + FileUtil.checkSize(properties.getAvatarMaxSize(), avatar.getSize()); + // 验证文件上传的格式 + String image = "gif jpg png jpeg"; + String fileType = FileUtil.getExtensionName(avatar.getOriginalFilename()); + if (fileType != null && !image.contains(fileType)) { + throw new BadRequestException("文件格式错误!, 仅支持 " + image + " 格式"); + } + Member member = memberMapper.queryByUsername(SecurityUtils.getCurrentUsername()); + String oldPath = member.getAvatarPath(); + File file = FileUtil.upload(avatar, properties.getPath().getAvatar()); + member.setAvatarPath(Objects.requireNonNull(file).getPath()); + member.setAvatarName(file.getName()); + memberMapper.updateById(member); + if (CharSequenceUtil.isNotBlank(oldPath)) { + FileUtil.del(oldPath); + } + @NotBlank String username = member.getUsername(); + this.delCaches(member.getId(), username); + return ImmutableMap.of("avatar", file.getName()); + } + + /** + * 通过用户名加载用户和角色基本信息 + * + * @param mobile + * @return + */ + @Override + @Cacheable(key = "'username:' + #p0") + public UserSmallDto getUserDetailsByMobile(String mobile) { + return this.memberMapper.getUserDetails(mobile); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createOrUpdate(String mobile) { + Member member = memberMapper.queryByUsername(mobile); + if (ObjectUtil.isNull(member)) { + member = new Member(); + member.setId(IdUtil.getSnowflake().nextId()); + member.setEnabled(Boolean.TRUE); + member.setPhone(mobile); + member.setUsername(mobile); + return this.save(member); + } + // 清除缓存 + this.delCaches(member.getId(), member.getUsername()); + return updateById(member); + } + + + /** + * 删除缓存 + * + * @param id + * @param username + */ + private void delCaches(Long id, String username) { + this.redisUtils.del(CacheKey.USER_ID + id); + this.redisUtils.del(CacheKey.USER_NAME + username); + } +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/UserServiceImpl.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/UserServiceImpl.java index 779670f..f934120 100644 --- a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/UserServiceImpl.java +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/service/impl/UserServiceImpl.java @@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.CharSequenceUtil; import com.baiye.*; -import com.baiye.core.base.api.Result; import com.baiye.core.base.api.ResultCode; import com.baiye.core.constant.CacheKey; import com.baiye.core.constant.EncryptionConstants; @@ -17,9 +16,8 @@ import com.baiye.dto.UserDto; import com.baiye.dto.UserSmallDto; import com.baiye.exception.global.*; import com.baiye.feign.IRemoteAuthService; -import com.baiye.feign.IRemoteWechatService; -import com.baiye.mapper.UserMapper; import com.baiye.mapper.UserJobMapper; +import com.baiye.mapper.UserMapper; import com.baiye.mapper.UserRoleMapper; import com.baiye.mapstruct.UserMapStruct; import com.baiye.properties.FileProperties; @@ -39,7 +37,6 @@ import com.google.common.collect.Maps; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; - import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; @@ -50,7 +47,6 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; @@ -86,14 +82,8 @@ public class UserServiceImpl extends ServiceImpl implements IU private final IRemoteAuthService remoteAuthService; - private final IRemoteWechatService remoteWechatService; - /** - * restTemplate提供远程访问功能,给RemoteTokenServices提供支持 - */ - private final RestTemplate lbRestTemplate; - /** * 查询所有,不进行分页 diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/util/D.java b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/util/D.java new file mode 100644 index 0000000..f5561e4 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/java/com/baiye/util/D.java @@ -0,0 +1,70 @@ +package com.baiye.util; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Enzo + * @date : 2023/2/16 + */ +public class D { + + + static int num = 0; + + public static void main(String[] args) { + // 序号1 + int[] ints = {1, 3, 4}; + String text = "今天,{}请吃饭{}吃饭,花了{}元,在本店消费排行{}名"; + List permute = permute(2, text, ints); + System.out.println(permute); + } + + //存放最终答案的java容器 + static List ans = new ArrayList<>(); + + //全排列的方法 + public static List permute(int index, String text, int[] nums) { + //我们用来存放是否访问过的数组(避免重复使用某一元素) + boolean[] isVisted = new boolean[nums.length]; + //回溯法的核心,这是一个递归的调用 + dfs(index, text, nums, isVisted, new ArrayList<>()); + //返回答案 + + return ans; + } + + //核心算法 + public static void dfs(int index, String text, int[] nums, boolean[] isVisited, List path) { + //如果我们的路径,也就是我们用来存放我们一步步加入元素的容器的尺寸和num数组的长度是一样的,这说明我们找到了一个全排列,所以我们把它放入最终的答案数组 + if (path.size() == nums.length) { + List integers = Lists.newArrayList(path); + integers.add(0, index); + String format = CharSequenceUtil.format(text, integers.toArray()); + ans.add(format); + } + + //我们对于从下标0到数组num长度减1的数进行遍历 + for (int i = 0; i < nums.length; i++) { + //如果没有被访问,即被加入到路径容器中,就进行下面的操作 + if (!isVisited[i]) { + //变成访问过的元素 + isVisited[i] = true; + //加入到路径容器中 + path.add(nums[i]); + //递归进行深度优先搜索 + dfs(index, text, nums, isVisited, path); + //回溯的两步,去掉路径容器的最后的值,因为最后被添加的最先被回溯到 + path.remove(path.size() - 1); + //第二步,因为回溯,所以这个元素要被设置回未访问的 + isVisited[i] = false; + + num++; + } + } + } +} diff --git a/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/resources/mapper/MemberMapper.xml b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/resources/mapper/MemberMapper.xml new file mode 100644 index 0000000..b345373 --- /dev/null +++ b/cdp-manager/backstage-manger-server/backstage-manger-server-service/src/main/resources/mapper/MemberMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${property}.member_id as id, + ${property}.username, + ${property}.password, + ${property}.gender, + ${property}.phone, + ${property}.email, + ${property}.avatar_name, + ${property}.avatar_path, + ${property}.password, + ${property}.enabled, + ${property}.create_by, + ${property}.update_by, + ${property}.create_time, + ${property}.update_time + + + + + + + FROM + tb_member + + + + + + + + + + + + + + + + +