fix(*) 首次提交Auth项目

This commit is contained in:
2023-08-23 16:30:57 +08:00
parent cc4ef5e62c
commit 7dc8fac2cf
60 changed files with 4653 additions and 0 deletions

2
.gitignore vendored
View File

@ -18,3 +18,5 @@
/ResourceManager/target/ /ResourceManager/target/
/WXEngine/WXEngine.iml /WXEngine/WXEngine.iml
/WXEngine/target/ /WXEngine/target/
/Auth/src/main/resources/application-test.yml
/Auth/src/main/resources/bootstrap-test.yml

153
Auth/pom.xml Normal file
View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.crtech.cloud.auth</groupId>
<artifactId>Auth</artifactId>
<version>1.0.1</version>
<!-- 父工程 -->
<parent>
<groupId>cn.crtech.cloud.dependencies</groupId>
<artifactId>Dependencies</artifactId>
<version>1.0.1</version>
<relativePath/>
</parent>
<!-- 依赖的版本锁定 -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<nimbus.jwt.version>9.14</nimbus.jwt.version>
<oauth2.version>2.2.5.RELEASE</oauth2.version>
<cn.crtech.cloud.common>1.0.1</cn.crtech.cloud.common>
<weixin-java-miniapp.version>4.4.0</weixin-java-miniapp.version>
<weixin.mp.version>4.3.0</weixin.mp.version>
<tencentcloud.sdk.version>3.1.322</tencentcloud.sdk.version>
</properties>
<dependencies>
<!-- springboot-framework -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-alibaba-cloud-client -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus.jwt.version}</version>
</dependency>
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${oauth2.version}</version>
<exclusions>
<exclusion>
<artifactId>bcpkix-jdk15on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 自定义共有内容仓库 -->
<dependency>
<groupId>cn.crtech.cloud.common</groupId>
<artifactId>Common</artifactId>
<version>${cn.crtech.cloud.common}</version>
</dependency>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 微信小程序JDK工具包 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin-java-miniapp.version}</version>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 微信公众号JDK工具包 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin.mp.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<artifactId>weixin-java-common</artifactId>
<groupId>com.github.binarywang</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 腾讯云sdk -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${tencentcloud.sdk.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<artifactId>okio</artifactId>
<groupId>com.squareup.okio</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,36 @@
package cn.crtech.cloud.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;
/**
* Author : yj
* Date : 2021-01-13
* Description:
*/
//微服务启动时使用 begin
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("cn.crtech.cloud.auth.mapper")
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
//微服务启动时使用 end
//使用launcher启动时使用 begin
//launcher.NacosConfig @Component需要放开
//@SpringBootApplication
//public class AuthApplication extends SpringBootServletInitializer {
// @Override
// protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// return application.sources(AuthApplication.class);
// }
//}
//使用launcher启动时使用 end

View File

@ -0,0 +1,36 @@
package cn.crtech.cloud.auth.component;
import cn.crtech.cloud.auth.pojo.SecurityUser;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* JWT内容增强器
* token信息的额外信息处理
*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
//把用户ID设置到JWT中
info.put("id", securityUser.getId());
info.put("company_code", securityUser.getCompanyCode());
info.put("company_name", securityUser.getCompanyName());
info.put("mobile", securityUser.getMobile());
info.put("nick_name", securityUser.getNickName());
info.put("email", securityUser.getEmail());
info.put("company_admin", securityUser.getIsCompanyAdmin());
info.put("data_source", securityUser.getDataSource());
info.put("data_source_id", securityUser.getDataSourceId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}

View File

@ -0,0 +1,42 @@
package cn.crtech.cloud.auth.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 跨域拦截器用于401处理
* <p>
* Author : yj
* Date : 2021-03-11
* Description:
*/
@Configuration
public class CrossInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String origin = request.getHeader(HttpHeaders.ORIGIN);
if (origin != null) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, HEAD");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Access-Token");
response.setHeader("Access-Control-Max-Age", "3600");
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}

View File

@ -0,0 +1,176 @@
package cn.crtech.cloud.auth.config;
import cn.crtech.cloud.auth.component.JwtTokenEnhancer;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.auth.pojo.MisApp;
import cn.crtech.cloud.auth.service.system.AuthService;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
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.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Oauth2认证服务器配置
* AuthorizationServer配置的适配类
*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer//开启认证服务
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthorizeMapper authorizeMapper;
private final static int ACCESS_TOKEN_VALIDITY_SECONDS = 3600;
private final static int REFRESH_TOKEN_VALIDITY_SECONDS = 86400;
private final PasswordEncoder passwordEncoder;
private final AuthService authService;
private final AuthenticationManager authenticationManager;
private final JwtTokenEnhancer jwtTokenEnhancer;
/**
* 配置前端应用认证
* 配置从哪里获取ClientDetails信息
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
List<MisApp> applicationList = authorizeMapper.getAll();
//将客户端信息存储在内存中
applicationList.forEach(application -> {
ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder clientBuilder = builder.withClient(application.getCode())
.secret(passwordEncoder.encode(application.getClientSecret()))
.scopes(application.getScope())// 允许的授权范围 all
.authorizedGrantTypes(application.getAuthorizedGrantTypes().split(",")) //"password", "refresh_token"
.accessTokenValiditySeconds(application.getAccessTokenValidity() == null ? ACCESS_TOKEN_VALIDITY_SECONDS : application.getAccessTokenValidity())
.refreshTokenValiditySeconds(application.getRefreshTokenValidity() == null ? REFRESH_TOKEN_VALIDITY_SECONDS : application.getRefreshTokenValidity());
if (!StrUtil.isEmpty(application.getWebServerRedirectUri())) {
clientBuilder.redirectUris(application.getWebServerRedirectUri());
}
if (!StrUtil.isEmpty(application.getResourceIds())) {
clientBuilder.resourceIds(application.getResourceIds());
}
if (!StrUtil.isEmpty(application.getAuthorities())) {
clientBuilder.authorities(application.getAuthorities());
}
if (!StrUtil.isEmpty(application.getAdditionalInformation())) {
clientBuilder.additionalInformation(application.getAdditionalInformation());
}
if (!StrUtil.isEmpty(application.getAutoApprove())) {
clientBuilder.autoApprove(application.getAutoApprove());
}
});
}
/**
* 注入相关配置:
* 1. 密码模式下配置认证管理器 AuthenticationManager
* 2. userDetailsService注入
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(authService) //配置加载用户信息的服务
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(enhancerChain)
.tokenServices(customTokenServices(endpoints)); //配置token增强
}
public UserTokenServices customTokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
UserTokenServices tokenServices = new UserTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// 设置自定义的CustomUserDetailsByNameServiceWrapper
if (authService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserByNameServiceWrapper<>(authService));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
}
return tokenServices;
}
/**
* 配置:安全检查流程,用来配置令牌端点Token Endpoint的安全与权限访问
* 默认过滤器BasicAuthenticationFilter
* 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
* 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤默认值denyAll()
* 对以下的几个端点进行权限配置:
* /oauth/authorize授权端点
* /oauth/token令牌端点
* /oauth/confirm_access用户确认授权提交端点
* /oauth/error授权服务错误信息端点
* /oauth/check_token用于资源服务访问的令牌解析端点
* /oauth/token_key提供公有密匙的端点如果使用JWT令牌的话
**/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// security//客户端校验token访问许可 /oauth/check_key公开
// .checkTokenAccess("permitAll()")
// //客户端token调用许可 /oauth/check_token公开
// .tokenKeyAccess("permitAll()")
// //表单认证,申请令牌
// .allowFormAuthenticationForClients()
// .passwordEncoder(new BCryptPasswordEncoder());
security.allowFormAuthenticationForClients();
}
/**
* jwttoken转换器
*
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
/**
* 采用非对称加密
*
* @return
*/
@Bean
public KeyPair keyPair() {
//从classpath下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
}

View File

@ -0,0 +1,32 @@
package cn.crtech.cloud.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis相关配置
*/
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@ -0,0 +1,132 @@
package cn.crtech.cloud.auth.config;
import cn.crtech.cloud.auth.service.system.AuthUserService;
import cn.hutool.core.lang.Assert;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
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.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
import java.util.Map;
public class UserAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsPasswordService userDetailsPasswordService;
@Resource
private AuthUserService authUserService;
public UserAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.authUserService, "A UserDetailsService must be set");
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
// 自定义添加
Map<String, String> map = (Map<String, String>) authentication.getDetails();
try {
// 自定义添加
UserDetails loadedUser = getAuthUserService().loadUserByUsername(username, map.get("companyCode"));
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
public String getUserNotFoundEncodedPassword() {
return userNotFoundEncodedPassword;
}
public void setUserNotFoundEncodedPassword(String userNotFoundEncodedPassword) {
this.userNotFoundEncodedPassword = userNotFoundEncodedPassword;
}
public UserDetailsPasswordService getUserDetailsPasswordService() {
return userDetailsPasswordService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
public AuthUserService getAuthUserService() {
return authUserService;
}
public void setAuthUserService(AuthUserService authUserService) {
this.authUserService = authUserService;
}
}

View File

@ -0,0 +1,41 @@
package cn.crtech.cloud.auth.config;
import cn.crtech.cloud.auth.service.system.AuthService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
import java.util.Map;
public class UserByNameServiceWrapper<T extends Authentication> implements AuthenticationUserDetailsService<T>, InitializingBean {
private AuthService authService = null;
public UserByNameServiceWrapper() {
}
public UserByNameServiceWrapper(final AuthService authService) {
Assert.notNull(authService, "userDetailsService cannot be null.");
this.authService = authService;
}
public void afterPropertiesSet() {
Assert.notNull(this.authService, "UserDetailsService must be set");
}
public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {
// ----------添加自定义的内容----------
AbstractAuthenticationToken principal = (AbstractAuthenticationToken) authentication.getPrincipal();
// ----------添加自定义的内容----------
Map<String, String> map = (Map<String, String>) principal.getDetails();
return this.authService.loadUserByUsername(authentication.getName(), map.get("companyCode")); // 使用自定义的userDetailsService
}
public void setUserDetailsService(AuthService authService) {
this.authService = authService;
}
}

View File

@ -0,0 +1,306 @@
package cn.crtech.cloud.auth.config;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.*;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
public class UserTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {
private int refreshTokenValiditySeconds = 2592000;
private int accessTokenValiditySeconds = 43200;
private boolean supportRefreshToken = false;
private boolean reuseRefreshToken = true;
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private TokenEnhancer accessTokenEnhancer;
private AuthenticationManager authenticationManager;
public UserTokenServices() {
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.tokenStore, "tokenStore must be set");
}
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (!existingAccessToken.isExpired()) {
this.tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
this.tokenStore.removeRefreshToken(refreshToken);
}
this.tokenStore.removeAccessToken(existingAccessToken);
}
if (refreshToken == null) {
refreshToken = this.createRefreshToken(authentication);
} else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = this.createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken);
this.tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
this.tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
@Transactional(
noRollbackFor = {InvalidTokenException.class, InvalidGrantException.class}
)
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
if (!this.supportRefreshToken) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
} else {
OAuth2RefreshToken refreshToken = this.tokenStore.readRefreshToken(refreshTokenValue);
if (refreshToken == null) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
} else {
OAuth2Authentication authentication = this.tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// OAuth2Authentication 中的 Authentication userAuthentication 丢失了 Detail的信息,需要补上
// 1.从tokenRequest中获取请求的信息,并重新构造成 UsernamePasswordAuthenticationToken
// 2.设置好了Detail的信息再传入构造 PreAuthenticatedAuthenticationToken 交由后面的验证
tokenRequest.getRequestParameters();
Object details = tokenRequest.getRequestParameters();
UsernamePasswordAuthenticationToken userAuthentication = (UsernamePasswordAuthenticationToken) authentication.getUserAuthentication();
userAuthentication.setDetails(details);
// 去掉原来的,使用自己重新构造的 userAuthentication
// Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
Authentication user = new PreAuthenticatedAuthenticationToken(userAuthentication, "", authentication.getAuthorities());
user = this.authenticationManager.authenticate(user);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
}
String clientId = authentication.getOAuth2Request().getClientId();
if (clientId != null && clientId.equals(tokenRequest.getClientId())) {
this.tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);
if (this.isExpired(refreshToken)) {
this.tokenStore.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
} else {
authentication = this.createRefreshedAuthentication(authentication, tokenRequest);
if (!this.reuseRefreshToken) {
this.tokenStore.removeRefreshToken(refreshToken);
refreshToken = this.createRefreshToken(authentication);
}
OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken);
this.tokenStore.storeAccessToken(accessToken, authentication);
if (!this.reuseRefreshToken) {
this.tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
}
return accessToken;
}
} else {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
}
}
}
}
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return this.tokenStore.getAccessToken(authentication);
}
private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {
Set<String> scope = request.getScope();
OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);
if (scope != null && !scope.isEmpty()) {
Set<String> originalScope = clientAuth.getScope();
if (originalScope == null || !originalScope.containsAll(scope)) {
throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope + ".", originalScope);
}
clientAuth = clientAuth.narrowScope(scope);
}
OAuth2Authentication narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());
return narrowed;
}
protected boolean isExpired(OAuth2RefreshToken refreshToken) {
if (!(refreshToken instanceof ExpiringOAuth2RefreshToken)) {
return false;
} else {
ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
return expiringToken.getExpiration() == null || System.currentTimeMillis() > expiringToken.getExpiration().getTime();
}
}
public OAuth2AccessToken readAccessToken(String accessToken) {
return this.tokenStore.readAccessToken(accessToken);
}
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else if (accessToken.isExpired()) {
this.tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
} else {
OAuth2Authentication result = this.tokenStore.readAuthentication(accessToken);
if (result == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else {
if (this.clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
this.clientDetailsService.loadClientByClientId(clientId);
} catch (ClientRegistrationException var6) {
throw new InvalidTokenException("Client not valid: " + clientId, var6);
}
}
return result;
}
}
}
public String getClientId(String tokenValue) {
OAuth2Authentication authentication = this.tokenStore.readAuthentication(tokenValue);
if (authentication == null) {
throw new InvalidTokenException("Invalid access token: " + tokenValue);
} else {
OAuth2Request clientAuth = authentication.getOAuth2Request();
if (clientAuth == null) {
throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);
} else {
return clientAuth.getClientId();
}
}
}
public boolean revokeToken(String tokenValue) {
OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(tokenValue);
if (accessToken == null) {
return false;
} else {
if (accessToken.getRefreshToken() != null) {
this.tokenStore.removeRefreshToken(accessToken.getRefreshToken());
}
this.tokenStore.removeAccessToken(accessToken);
return true;
}
}
private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
if (!this.isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
} else {
int validitySeconds = this.getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
String value = UUID.randomUUID().toString();
return (OAuth2RefreshToken) (validitySeconds > 0 ? new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() + (long) validitySeconds * 1000L)) : new DefaultOAuth2RefreshToken(value));
}
}
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (long) validitySeconds * 1000L));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return (OAuth2AccessToken) (this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
}
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (this.clientDetailsService != null) {
ClientDetails client = this.clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return this.accessTokenValiditySeconds;
}
protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {
if (this.clientDetailsService != null) {
ClientDetails client = this.clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getRefreshTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return this.refreshTokenValiditySeconds;
}
protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {
if (this.clientDetailsService != null) {
ClientDetails client = this.clientDetailsService.loadClientByClientId(clientAuth.getClientId());
return client.getAuthorizedGrantTypes().contains("refresh_token");
} else {
return this.supportRefreshToken;
}
}
public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
this.accessTokenEnhancer = accessTokenEnhancer;
}
public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {
this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;
}
public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
this.accessTokenValiditySeconds = accessTokenValiditySeconds;
}
public void setSupportRefreshToken(boolean supportRefreshToken) {
this.supportRefreshToken = supportRefreshToken;
}
public void setReuseRefreshToken(boolean reuseRefreshToken) {
this.reuseRefreshToken = reuseRefreshToken;
}
public void setTokenStore(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
}

View File

@ -0,0 +1,69 @@
package cn.crtech.cloud.auth.config;
import cn.crtech.cloud.auth.service.system.AuthService;
import cn.crtech.cloud.auth.wx.applet.WxAppletSecurityConfigurerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* SpringSecurity配置
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthService authService;
@Autowired
private WxAppletSecurityConfigurerConfig wxAppletSecurityConfigurerConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/wx/**").permitAll()
.antMatchers("/oauth/initRedis").permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/rsa/publicKey").permitAll()
.anyRequest().authenticated().and().httpBasic().and()
.apply(wxAppletSecurityConfigurerConfig).and()
.csrf().disable();// 关闭跨域
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserAuthenticationProvider userAuthenticationProvider() {
UserAuthenticationProvider userAuthenticationProvider = new UserAuthenticationProvider();
userAuthenticationProvider.setAuthUserService(authService);
userAuthenticationProvider.setHideUserNotFoundExceptions(false);
userAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return userAuthenticationProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userAuthenticationProvider());
}
}

View File

@ -0,0 +1,70 @@
package cn.crtech.cloud.auth.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.crtech.cloud.auth.mapper.WxMaAuthorizeMapper;
import cn.crtech.cloud.auth.pojo.WxAppletConfig;
import cn.crtech.cloud.auth.pojo.WxGzhConfig;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxRuntimeException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Configuration
public class WxMaConfiguration {
@Autowired
WxMaAuthorizeMapper wxMaConfigMapper;
@Bean
public WxMaService wxMaService() {
List<WxAppletConfig> configs = wxMaConfigMapper.getWxAppletConfigList();
if (configs == null) {
throw new WxRuntimeException("小程序配置出错,添加下相关配置,注意别配错了!");
}
WxMaService maService = new WxMaServiceImpl();
maService.setMultiConfigs(
configs.stream()
.map(a -> {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
// WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool());
// 使用上面的配置时需要同时引入jedis-lock的依赖否则会报类无法找到的异常
config.setAppid(a.getAppId());
config.setSecret(a.getAppSecret());
config.setToken(a.getToken());
config.setAesKey(a.getEncodingAesKey());
config.setMsgDataFormat(a.getMsgDataFormat());
return config;
}).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o)));
return maService;
}
@Bean
public WxMpService wxMpService() {
List<WxGzhConfig> configs = wxMaConfigMapper.getWxGzhConfigList();
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setMultiConfigStorages(
configs.stream()
.map(a -> {
WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
// 使用上面的配置时需要同时引入jedis-lock的依赖否则会报类无法找到的异常
wxMpConfigStorage.setAppId(a.getAppId());
wxMpConfigStorage.setSecret(a.getAppSecret());
wxMpConfigStorage.setToken(a.getAppToken());
wxMpConfigStorage.setAesKey(a.getAppAeskey());
return wxMpConfigStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return wxMpService;
}
}

View File

@ -0,0 +1,59 @@
package cn.crtech.cloud.auth.controller;
import cn.crtech.cloud.auth.pojo.Oauth2Token;
import cn.crtech.cloud.auth.service.ResourceService;
import cn.crtech.cloud.common.api.CommonResult;
import cn.crtech.cloud.common.dto.Result;
import org.springframework.beans.factory.annotation.Autowired;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
import java.util.Map;
/**
* 自定义Oauth2获取令牌接口
*/
@RestController
@RequestMapping("/oauth")
public class AuthController {
private TokenEndpoint tokenEndpoint;
private ResourceService resourceService;
@Autowired
public AuthController(TokenEndpoint tokenEndpoint, ResourceService resourceService) {
this.tokenEndpoint = tokenEndpoint;
this.resourceService = resourceService;
}
/**
* Oauth2登录认证
*/
@PostMapping("/token")
public CommonResult<Oauth2Token> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2Token oauth2TokenDto = Oauth2Token.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
return CommonResult.success(oauth2TokenDto);
}
/**
* 权限缓存内容更新
*
* @return 返回操作结果
*/
@PostMapping("/initRedis")
public Result initRedis() {
resourceService.initData();
return Result.success(true);
}
}

View File

@ -0,0 +1,32 @@
package cn.crtech.cloud.auth.controller;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
/**
* 获取RSA公钥接口
*/
@RestController
public class KeyPairController {
private KeyPair keyPair;
@Autowired
private void setKeyPair(KeyPair keyPair) {
this.keyPair = keyPair;
}
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}

View File

@ -0,0 +1,116 @@
package cn.crtech.cloud.auth.controller;
import cn.crtech.cloud.auth.pojo.RegisterUser;
import cn.crtech.cloud.auth.pojo.WechatLoginRequest;
import cn.crtech.cloud.auth.service.wx.applet.WxMaUserService;
import cn.crtech.cloud.common.dto.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 微信小程序用户接口
*/
@Slf4j
@RestController
@RequestMapping("/wx")
public class WxMaUserController {
private WxMaUserService wxMaUserService;
@Autowired
public WxMaUserController(WxMaUserService wxMaUserService) {
this.wxMaUserService = wxMaUserService;
}
/**
* 校验是否关注、绑定
*
* @param wxAppletCode 微信小程序标识
* @param code 参数标识
* @param appId 公众号AppID
* @return 返回校验结果
*/
@GetMapping("/checkUser")
public Result checkUser(String wxAppletCode, String code, String appId) {
return wxMaUserService.checkUser(wxAppletCode, code, appId);
}
/**
* 获取微信用户对应绑定openID
*
* @param wxAppletCode 微信小程序标识
* @param appId 公众号AppID
* @return 返回查询结果
*/
@GetMapping("/getWxAppletOpenId")
public Result getWxAppletOpenId(String wxAppletCode, String appId) {
return wxMaUserService.getWxAppletOpenId(wxAppletCode, appId);
}
/**
* 小程序绑定
*
* @param wechatLoginRequest 微信请求参数信息
* @return 返回结果
*/
@PostMapping("/bindWxApplet")
public Result bindWxApplet(@RequestBody WechatLoginRequest wechatLoginRequest) {
return wxMaUserService.bindWxApplet(wechatLoginRequest);
}
/**
* 实名认证
*
* @param params 参数对象
* <ul>
* <li>userName: 用户名</li>
* <li>mobile: 手机号码</li>
* <li>idCard: 身份证号码</li>
* </ul>
* @return 返回操作结果
*/
@PostMapping("/userMobileVerify")
public Result userMobileVerify(@RequestBody Map<String, Object> params) {
return wxMaUserService.userMobileVerify(params);
}
/**
* 微信用户登录
*
* @param code 登录参数
* @return 返回登录结果
*/
@GetMapping("/login")
public Result login(String code) {
return wxMaUserService.login(code);
}
/**
* 通过openId校验人员是否在该公司
*
* @param wxAppletCode 小程序标识
* @param code 登录标识
* @param appId 小程序APPId
* @param companyCode 公司标识
* @param companyName 公司名称
* @return 返回校验结果
*/
@GetMapping("/checkWxRegisterUser")
public Result checkWxRegisterUser(String wxAppletCode, String code, String appId, String companyCode, String companyName) {
return wxMaUserService.checkWxRegisterUser(wxAppletCode, code, appId, companyCode, companyName);
}
/**
* 小程序 用户注册
*
* @param registerUser 参数对象
* @return 返回操作结果
*/
@PostMapping("/registerCompanyUser")
public Result registerCompanyUser(@RequestBody RegisterUser registerUser) {
return wxMaUserService.registerCompanyUser(registerUser);
}
}

View File

@ -0,0 +1,26 @@
package cn.crtech.cloud.auth.dto;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import lombok.*;
import java.io.Serializable;
/**
* desc
*
* @author TYP
* @since 2023-08-08 10:20
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class MisComRoleDto implements Serializable {
@DataExportAnnotation("公司标识")
private String companyCode;
@DataExportAnnotation("角色标识")
private String roleCode;
}

View File

@ -0,0 +1,29 @@
package cn.crtech.cloud.auth.dto;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import lombok.*;
import java.io.Serializable;
/**
* 企业角色实体
*
* @author TYP
* @since 2023-08-01 9:24
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class MisComRoleUserDto implements Serializable {
@DataExportAnnotation("所属公司标识")
private String companyCode;
@DataExportAnnotation("角色ID")
private Integer roleId;
@DataExportAnnotation("用户ID")
private Integer userId;
}

View File

@ -0,0 +1,65 @@
package cn.crtech.cloud.auth.dto;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import lombok.*;
import java.io.Serializable;
/**
* desc
*
* @author TYP
* @since 2023-08-08 10:20
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class MisComUserDto implements Serializable {
@DataExportAnnotation("公司标识")
private String companyCode;
@DataExportAnnotation("公司名称")
private String companyName;
@DataExportAnnotation("公司图标")
private String logo;
@DataExportAnnotation("所在省地址")
private String provinceName;
@DataExportAnnotation("所在市地址")
private String cityName;
@DataExportAnnotation("所在区/县地址")
private String countyName;
@DataExportAnnotation("详细地址")
private String address;
@DataExportAnnotation("用户ID")
private Integer userId;
@DataExportAnnotation("是否公司所有人")
private Boolean isOwner;
@DataExportAnnotation("职位")
private String position;
@DataExportAnnotation("是否默认公司")
private Integer isDefault;
@DataExportAnnotation("状态")
private Integer state;
@DataExportAnnotation("职员编号")
private String serialNo;
@DataExportAnnotation("用户别名")
private String nickName;
@DataExportAnnotation("用户手机号码")
private String mobile;
}

View File

@ -0,0 +1,51 @@
package cn.crtech.cloud.auth.dto;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import lombok.*;
import java.io.Serializable;
import java.util.List;
/**
* 授权角色API权限实体DTO
*
* @author TYP
* @since 2023-08-09 11:15
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class MisRoleApiDto implements Serializable {
@DataExportAnnotation("对应产品系列ID")
private Integer seriesId;
@DataExportAnnotation("对应产品系列标识")
private String seriesCode;
@DataExportAnnotation("对应产品ID")
private Integer appId;
@DataExportAnnotation("对应产品标识")
private String appCode;
@DataExportAnnotation("对应产品版本ID")
private Integer versionId;
@DataExportAnnotation("对应产品版本")
private String version;
@DataExportAnnotation("对应产品菜单ID")
private Integer popedomId;
@DataExportAnnotation("对应API路由")
private String controller;
@DataExportAnnotation("对应角色名称")
private String role;
@DataExportAnnotation("授权权限数据集合")
private List<MisRoleApiDto> authorityList;
}

View File

@ -0,0 +1,51 @@
package cn.crtech.cloud.auth.launcher;
import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.Query;
import javax.servlet.ServletContext;
import java.lang.management.ManagementFactory;
import java.util.Set;
//使用launcher启动时使用 begin
//@Component
//使用launcher启动时使用 end
public class NacosConfig implements ApplicationRunner {
@Autowired
private ServletContext servletContext;
@Autowired
private NacosRegistration registration;
@Autowired(required = false)
private NacosAutoServiceRegistration nacosAutoServiceRegistration;
public Integer getTomcatPort() throws MalformedObjectNameException {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return new Integer(port);
}
@Override
public void run(ApplicationArguments args) throws Exception {
if (registration != null && servletContext != null) {
try {
Integer tomcatPort = getTomcatPort();
registration.setPort(tomcatPort);
registration.getMetadata().put("management.context-path", servletContext.getContextPath());
nacosAutoServiceRegistration.start();
} catch (MalformedObjectNameException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,275 @@
package cn.crtech.cloud.auth.mapper;
import cn.crtech.cloud.auth.dto.MisComRoleUserDto;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.dto.MisRoleApiDto;
import cn.crtech.cloud.auth.pojo.MisApp;
import cn.crtech.cloud.auth.pojo.MisUser;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface AuthorizeMapper {
@Select("SELECT " +
"a.id , a.nick_name as nickName , a.mobile , a.email , a.avatar , a.password , a.name , " +
"a.id_card as idCard , a.id_card_prev as idCardPrev , a.id_card_next as idCardNext , " +
"a.reg_time as regTime , a.state, a.lastModifier , a.lastModifyTime , a.real_name as realName , " +
"a.is_system_admin as isSystemAdmin " +
"FROM mis_user a " +
"where a.mobile= #{mobile} and a.state = 1 ")
@ResultType(MisUser.class)
MisUser queryUserByMobile(@Param("mobile") String mobile);
@Select("<script> " +
"select " +
"a.code as companyCode , a.name as companyName , a.logo , " +
"a.province_name as provinceName , a.city_name as cityName , a.county_name as countyName , " +
"a.street as address, b.user_id AS userId, " +
"CASE WHEN a.owner_id = #{userId} then 1 ELSE 0 end as isOwner, " +
"IFNULL(b.position,'超级管理员') as position, " +
"IFNULL(b.is_default,1) AS isDefault, " +
"IFNULL(b.state,1) as state, " +
"b.serial_no as serialNo " +
"from mis_company a " +
"inner join mis_company_user b on b.company_code = a.code " +
"where b.user_id = #{userId} and b.state = 1 " +
" <if test='companyCode != null'> and a.code = #{companyCode} </if> " +
"order by b.is_default desc " +
"</script>")
@ResultType(MisComUserDto.class)
List<MisComUserDto> queryCompanyUserByUserId(Map<String, Object> params);
@Select("select " +
"a.company_code as companyCode , a.role_id as roleId , a.user_id as userId " +
"from mis_com_role_user a " +
"where a.company_code = #{companyCode} and a.user_id = #{userId}")
@ResultType(MisComRoleUserDto.class)
List<MisComRoleUserDto> queryCompanyRoleUser(@Param("userId") int userId,
@Param("companyCode") String companyCode);
@Select("select " +
"GROUP_CONCAT(b.role_code) " +
"from mis_com_role_user a " +
"left join mis_com_role b on a.role_id = b.id " +
"where a.company_code = #{companyCode} and a.user_id = #{userId} " +
"and b.state = 1 " +
"group by a.user_id ")
@ResultType(String.class)
String queryUserRole(@Param("userId") int userId, @Param("companyCode") String companyCode);
@Select("select " +
"GROUP_CONCAT( DISTINCT CONCAT(d.code , '_' , c.code , '_' , b.code)) as roles " +
"from mis_app_defaultrole_user a " +
"inner join mis_app_defaultrole b on a.role_id = b.id " +
"inner join mis_app c on c.id = b.app_id " +
"inner join mis_app_series d on d.id = c.series_id " +
"where a.company_code = #{companyCode} and a.user_id = #{userId} " +
"and b.state = 1 and c.state = 1 and d.state = 1" +
"group by a.user_id")
@ResultType(String.class)
String queryUserDefaultRole(@Param("userId") int userId, @Param("companyCode") String companyCode);
@Insert({"insert into mis_user " +
"(nick_name, mobile, email, avatar, password, name, state, real_name, reg_time) " +
"values " +
"(#{nickName}, #{mobile}, #{email}, #{avatar}, #{password}, #{name}, " +
" #{state}, #{realName}, #{regTime, jdbcType=TIMESTAMP})"})
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insertUser(MisUser user);
@Update("update mis_user " +
"set " +
"real_name = #{realName}, " +
"name = #{name}, " +
"nick_name = #{nickName}, " +
"avatar = #{avatar}, " +
"email = #{email} " +
"where mobile = #{mobile}")
int updateRegisterUser(MisUser user);
@Insert({"insert into mis_company_user " +
"(company_code, user_id, serial_no, is_default, state,is_owner) " +
"values " +
"(#{companyCode}, #{userId}, #{serialNo}, #{isDefault}, #{state}, #{isOwner})"})
int insertCompanyUser(MisComUserDto companyUser);
@Update("update mis_company_user " +
"set " +
"serial_no = #{serialNo}, " +
"state = #{state} " +
"where company_code = #{companyCode} and user_id=#{userId}")
int updateCompanyUser(MisComUserDto companyUser);
@Update("update mis_user " +
"set id_card =#{idCard}, " +
"real_name = #{realName} " +
"where mobile = #{mobile}")
int updateUserIdCard(@Param("idCard") String idCard, @Param("realName") String realName, @Param("mobile") String mobile);
@Select("select " +
"a.id , b.type , a.code , a.name , a.description , a.logo , a.state , a.sort , " +
"a.resource_ids as resourceIds, a.client_secret as clientSecret , a.scope , " +
"a.authorized_grant_types as authorizedGrantTypes , " +
"a.access_token_validity as accessTokenValidity, " +
"a.refresh_token_validity as refreshTokenValidity, " +
"a.web_server_redirect_uri as webServerRedirectUri, " +
"a.authorities , a.auto_approve as autoApprove " +
"from mis_app a " +
"inner join mis_app_series b on a.series_id = b.id " +
"where b.type = 1 and a.state = 1 and b.state = 1")
@ResultType(MisApp.class)
List<MisApp> getAll();
@Select("select " +
"CONCAT('/' ,b.code, a.authority_code) as controller , " +
"CONCAT(c.code ,'_' , b.code) as role " +
"from mis_app_popedom_authority a " +
"inner join mis_app b on b.id = a.app_id " +
"inner join mis_app_series c on c.id = b.series_id " +
"where a.state = 1 and b.state = 1 and c.state = 1 ")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listLimitApi();
@Select("select " +
"c.id as seriesId , c.code as seriesCode , b.code as appCode , b.id as appId , " +
"a.id as versionId , a.version " +
"from mis_app_grade a " +
"inner join mis_app b on b.id = a.app_id " +
"inner join mis_app_series c on c.id = b.series_id " +
"where a.need_price = 0 and a.state = 1 and b.state = 1 and c.state = 1 ")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listFreeApp();
@Select("select " +
"e.id as seriesId , e.code as seriesCode , c.code as appCode , c.id as appId , " +
"b.id as versionId , b.version , d.id as popedomId , " +
"case when d.route = '/' then '' else CONCAT('/' ,c.code, d.route,'/**') end as controller, " +
"'ALL' as role " +
"from mis_app_grade_popedom a " +
"inner join mis_app_grade b on a.grade_id = b.id and b.need_price = 0 " +
"inner join mis_app c on c.id = b.app_id " +
"inner join mis_app_popedom d on d.id = a.popedom_id " +
"inner join mis_app_series e on e.id = c.series_id " +
"where d.route <> '/' and d.route <> '#' " +
"and b.state = 1 and c.state = 1 and d.state = 1 and e.state = 1 ")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listFreeController();
@Select("select " +
"e.id as seriesId , e.code as seriesCode , c.code as appCode , c.id as appId , " +
"b.id as versionId , b.version , d.popedom_id as popedomId, " +
"CONCAT('/' ,c.code, d.authority_code) as controller , 'ALL' as role " +
"from mis_app_grade_popedom_authority a " +
"inner join mis_app_grade b on a.grade_id = b.id and b.need_price = 0 " +
"inner join mis_app c on c.id = b.app_id " +
"inner join mis_app_popedom_authority d on d.id = a.authority_id " +
"inner join mis_app_series e on e.id = c.series_id " +
"where b.state = 1 and c.state = 1 and d.state = 1 and e.state = 1 ")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listFreeAuthority();
@Select("select " +
"CONCAT('/',code , '/' , code ,'Feign/**') as controller , 'ALL' as role " +
"from mis_app " +
"where state = 1 ")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listAppFeignController();
@Select("select " +
"case when c.route = '/' then '' else CONCAT('/' ,d.code, c.route,'/**') end as controller, " +
"GROUP_CONCAT(DISTINCT concat( b.company_code, '_', b.id )) as role, " +
"e.id as seriesId , e.code as seriesCode , d.code as appCode , d.id as appId , " +
"c.id as popedomId " +
"from mis_com_role_popedom a " +
"inner join mis_com_role b on b.id = a.role_id " +
"inner join mis_app_popedom c on a.popedom_id = c.id " +
"inner join mis_app d on d.id = c.app_id " +
"inner join mis_app_series e on e.id = d.series_id " +
"where c.route <> '/' and c.route <> '#' " +
"and b.state = 1 and c.state = 1 and d.state = 1 and e.state = 1 " +
"GROUP BY c.route")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listRoleController();
@Select("select " +
"CONCAT('/' ,d.code, c.authority_code) as controller, " +
"GROUP_CONCAT(DISTINCT concat( b.company_code, '_', b.id )) as role, " +
"e.id as seriesId , e.code as seriesCode , d.code as appCode , d.id as appId , " +
"c.id as popedomId " +
"from mis_com_role_popedom_authority a " +
"inner join mis_com_role b on b.id = a.role_id " +
"inner join mis_app_popedom_authority c on a.authority_id = c.id " +
"inner join mis_app d on d.id = c.app_id " +
"inner join mis_app_series e on e.id = d.series_id " +
"where b.state = 1 and c.state = 1 and d.state = 1 and e.state = 1 " +
"GROUP BY c.authority_code")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listRoleAuthorityController();
@Select("select " +
"case when d.route = '/' then '' else CONCAT('/' ,e.code, d.route , '/**') end as controller, " +
"GROUP_CONCAT(DISTINCT concat( b.code, '_admin' )) as role, " +
"f.id as seriesId , f.code as seriesCode , e.code as appCode , e.id as appId , " +
"d.id as popedomId " +
"from mis_com_app a " +
"inner join mis_company b on a.company_id = b.id " +
"left join mis_com_app_popedom c on c.company_id = b.id and c.app_id = a.app_id " +
"inner join mis_app_popedom d on d.id = c.popedom_id " +
"inner join mis_app e on e.id = a.app_id " +
"inner join mis_app_series f on f.id = e.series_id " +
"where d.route <> '/' and d.route <> '#' and a.state = 1 and a.expire_date >= now() " +
"and d.state = 1 and e.state = 1 and f.state = 1 " +
"group by d.route")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> getCompanyAdminController();
@Select("select " +
"CONCAT('/' ,e.code, d.authority_code ) as controller, " +
"GROUP_CONCAT(DISTINCT concat( b.code, '_admin' )) as role, " +
"f.id as seriesId , f.code as seriesCode , e.code as appCode , e.id as appId ,d.id as popedomId " +
"from mis_com_app a " +
"inner join mis_company b on a.company_id = b.id " +
"left join mis_com_app_popedom_authority c on c.company_id = b.id and c.app_id = a.app_id " +
"inner join mis_app_popedom_authority d on d.id = c.authority_id " +
"inner join mis_app e on e.id = a.app_id " +
"inner join mis_app_series f on f.id = e.series_id " +
"where a.state = 1 and a.expire_date >= now() and d.state = 1 and e.state = 1 and f.state = 1 " +
"group by d.authority_code")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> getCompanyAdminAuthorityController();
@Select("select " +
"case when e.route = '/' then '' else CONCAT('/' ,b.code, e.route , '/**') end as controller, " +
"GROUP_CONCAT(DISTINCT concat(c.code , '_' , b.code , '_' , a.code)) as role, " +
"c.id as seriesId , c.code as seriesCode , b.code as appCode , b.id as appId , " +
"e.id as popedomId " +
"from mis_app_defaultrole a " +
"inner join mis_app b on a.app_id = b.id " +
"inner join mis_app_series c on c.id = b.series_id " +
"left join mis_app_defaultrole_popedom d on d.role_id = a.id " +
"inner join mis_app_popedom e on e.id = d.popedom_id " +
"where e.route <> '/' and e.route <> '#' and a.state = 1 and b.state = 1 " +
"and c.state = 1 and e.state = 1 " +
"group by e.route")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listDefaultRoleController();
@Select("select " +
"CONCAT('/' ,b.code, e.authority_code) as controller, " +
"GROUP_CONCAT(DISTINCT concat( c.code , '_' , b.code , '_' , a.code)) as role, " +
"c.id as seriesId , c.code as seriesCode , b.code as appCode , b.id as appId , " +
"e.id as popedomId " +
"from mis_app_defaultrole a " +
"inner join mis_app b on a.app_id = b.id " +
"inner join mis_app_series c on c.id = b.series_id " +
"left join mis_app_defaultrole_popedom_authority d on d.role_id = a.id " +
"inner join mis_app_popedom_authority e on e.id = d.authority_id " +
"where a.state = 1 and b.state = 1 and c.state = 1 and e.state = 1 " +
"group by e.authority_code")
@ResultType(MisRoleApiDto.class)
List<MisRoleApiDto> listDefaultRoleAuthorityController();
}

View File

@ -0,0 +1,105 @@
package cn.crtech.cloud.auth.mapper;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.pojo.WxAppletConfig;
import cn.crtech.cloud.auth.pojo.WxAppletUser;
import cn.crtech.cloud.auth.pojo.WxGzhConfig;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface WxMaAuthorizeMapper {
@Select("SELECT " +
"a.id, a.nick_name as nickName, a.mobile as mobile, " +
"a.open_id as openId, a.wx_open_id as wxOpenId " +
"FROM wx_user a " +
"where open_id = #{openId}")
@ResultType(WxAppletUser.class)
List<WxAppletUser> getUserByWxAppletOpenId(@Param("openId") String openId);
@Select("SELECT " +
"a.id , a.app_id as appId , a.app_name as appName , a.app_secret as appSecret, " +
"a.encoding_aes_key as encodingAesKey , a.token , a.msg_data_format as msgDataFormat " +
"FROM wx_applet_config a " +
"where status = 1 ")
@ResultType(WxAppletConfig.class)
List<WxAppletConfig> getWxAppletConfigList();
@Select("SELECT " +
"a.id, a.app_id as appId, a.name, a.app_secret as appSecret, a.app_aeskey as appAeskey," +
"a.app_token as appToken " +
"FROM wx_gzh_config as a " +
"where status = 1 ")
@ResultType(WxGzhConfig.class)
List<WxGzhConfig> getWxGzhConfigList();
@Select("SELECT " +
"a.id, a.nick_name as nickName, a.mobile as mobile, a.open_id as openId, " +
"a.wx_open_id as wxOpenId " +
"FROM wx_user a " +
"where a.open_id = #{openId} " +
"and a.company_code = #{companyCode}")
@ResultType(WxAppletUser.class)
WxAppletUser getWxAppletUser(@Param("openId") String openId, @Param("companyCode") String companyCode);
@Select("SELECT " +
"a.id, a.nick_name as nickName, a.mobile as mobile, a.open_id as openId, " +
"a.wx_open_id as wxOpenId " +
"FROM wx_user a " +
"where wx_open_id = #{wxOpenId}")
@ResultType(WxAppletUser.class)
WxAppletUser getUserByWxOpenId(@Param("wxOpenId") String wxOpenId);
@Insert({"insert into wx_user " +
"(nick_name, avatar_url, gender, country,province, city, mobile,open_id, union_id, " +
"created, app_id,wx_open_id, company_code, company_name) " +
"values" +
"(#{nickName}, #{avatarUrl}, #{gender}, #{country}, #{province}, #{city}, #{mobile}, " +
"#{openId}, #{unionId}, #{created, jdbcType=TIMESTAMP}, #{appId}, #{wxOpenId}, " +
"#{companyCode}, #{companyName})"})
int insertWxAppletUser(WxAppletUser wxAppletUser);
@Update("update wx_user " +
"set " +
"nick_name = #{nickName}, " +
"avatar_url = #{avatarUrl}, " +
"gender = #{gender}, " +
"country = #{country}, " +
"province = #{province}, " +
"city = #{city}, " +
"mobile = #{mobile}, " +
"union_id = #{unionId}, " +
"updated = #{updated} " +
"where open_id = #{openId}")
int updateWxAppletUser(WxAppletUser wxAppletUser);
@Select("select " +
"a.id as userId,a.nick_name as nickName, a.mobile as mobile, " +
"b.company_code as companyCode, b.state " +
"from mis_user a " +
"inner join mis_company_user b on a.id = b.user_id " +
"where a.mobile = #{mobile} and b.company_code=#{companyCode}")
@ResultType(MisComUserDto.class)
MisComUserDto queryCompanyUser(@Param("companyCode") String companyCode,
@Param("mobile") String mobile);
@Select("SELECT " +
"a.id as userId, a.nick_name as nickName, a.mobile as mobile, " +
"b.company_code as companyCode, b.state " +
"FROM mis_user a " +
"inner join mis_company_user b on a.id = b.user_id " +
"where a.mobile = #{mobile}")
@ResultType(MisComUserDto.class)
List<MisComUserDto> getUserCompanyList(@Param("mobile") String mobile);
@Update("update mis_company_user " +
"set " +
"is_default = #{isDefault} " +
"where company_code = #{companyCode} " +
"and user_id = #{userId}")
int updateCompanyDefault(@Param("companyCode") String companyCode,
@Param("userId") int userId,
@Param("isDefault") int isDefault);
}

View File

@ -0,0 +1,50 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Data;
import java.util.List;
@Data
public class AuthorizeUser {
private Integer id;
private String userName;
private String passWord;
private Integer status;
private List<String> roles;
private String companyCode;
private String companyName;
private String mobile;
private String nickName;
private String email;
private Boolean isCompanyAdmin;
private String dataSource;
private Integer dataSourceId;
public AuthorizeUser(Integer userId, String passWord, Integer status, String mobile, String nickName, String email, String companyName, boolean isCompanyAdmin) {
this.id = userId;
this.userName = mobile;
this.passWord = passWord;
this.status = status;
this.mobile = mobile;
this.nickName = nickName;
this.email = email;
this.isCompanyAdmin = isCompanyAdmin;
this.companyName = companyName;
}
public AuthorizeUser() {
}
}

View File

@ -0,0 +1,118 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* MIS授权产品实体对象
*
* @author TYP
* @since 2023-07-13 15:34
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "mis_app")
public class MisApp implements Serializable {
@Id
@DataExportAnnotation("主键id")
private Integer id;
@Column(name = "series_id")
@DataExportAnnotation("产品所属系列ID")
private Integer seriesId;
@Column(name = "name")
@DataExportAnnotation("产品名称")
private String name;
@Column(name = "code")
@DataExportAnnotation("产品标识")
private String code;
@Column(name = "description")
@DataExportAnnotation("产品描述信息")
private String description;
@Column(name = "logo")
@DataExportAnnotation("产品图标超链接")
private String logo;
@Column(name = "sort")
@DataExportAnnotation("产品排序")
private String sort;
@Column(name = "resource_ids")
private String resourceIds;
@Column(name = "client_secret")
@DataExportAnnotation("产品秘钥")
private String clientSecret;
@Builder.Default
@Column(name = "scope")
@DataExportAnnotation("产品授权范围")
private String scope = "all";
@Column(name = "web_server_redirect_uri")
@DataExportAnnotation("")
private String webServerRedirectUri;
@Column(name = "authorities")
@DataExportAnnotation("")
private String authorities;
@Column(name = "additional_information")
@DataExportAnnotation("")
private String additionalInformation;
@Builder.Default
@Column(name = "authorized_grant_types")
@DataExportAnnotation("产品授权加密类型")
private String authorizedGrantTypes = "password,refresh_token";
@Builder.Default
@Column(name = "access_token_validity")
@DataExportAnnotation("访问授权TOKEN有效期")
private Integer accessTokenValidity = 86400;
@Builder.Default
@Column(name = "refresh_token_validity")
@DataExportAnnotation("TOKEN刷新有效期")
private Integer refreshTokenValidity = 86400;
@Column(name = "auto_approve")
@DataExportAnnotation("")
private String autoApprove;
@Column(name = "manager")
@DataExportAnnotation("产品管理产品经理ID")
private Integer manager;
@Column(name = "created")
@DataExportAnnotation("创建时间")
@JsonFormat(pattern = "YYYY-MM-dd HH:mm:ss", locale = "cn", timezone = "GMT+8")
private Date created;
@Column(name = "is_default_role")
@DataExportAnnotation("是否含有默认角色")
private Boolean isDefaultRole;
@Column(name = "is_show")
@DataExportAnnotation("是否默认展示在产品展示列表")
private Boolean isShow;
@Column(name = "state")
@DataExportAnnotation("状态 1正常 0停用 -1删除")
private Integer state;
}

View File

@ -0,0 +1,157 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Date;
/**
* 系统用户实体
*
* @author TYP
* @since 2023-07-24 16:13
*/
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "mis_user")
public class MisUser implements Serializable {
@Id
@DataExportAnnotation("主键id")
private Integer id;
@Column(name = "name")
@DataExportAnnotation("用户名")
private String name;
@Column(name = "mobile")
@DataExportAnnotation("绑定手机号码")
private String mobile;
@Column(name = "nick_name")
@DataExportAnnotation("昵称")
private String nickName;
@Column(name = "email")
@DataExportAnnotation("绑定邮箱地址")
private String email;
@Column(name = "avatar")
@DataExportAnnotation("头像地址")
private String avatar;
@Column(name = "password")
@DataExportAnnotation("密码")
private String password;
@Column(name = "id_card")
@DataExportAnnotation("身份证号码")
private String idCard;
@Column(name = "id_card_prev")
@DataExportAnnotation("身份证正面图片地址")
private String idCardPrev;
@Column(name = "id_card_next")
@DataExportAnnotation("身份证反面图片地址")
private String idCardNext;
@Column(name = "reg_time")
@DataExportAnnotation("注册时间")
@JsonFormat(pattern = "YYYY-MM-dd HH:mm:ss", locale = "cn", timezone = "GMT+8")
private Date regTime;
@Column(name = "lastModifier")
@DataExportAnnotation("最后一次修改人用户ID")
private Integer lastModifier;
@Column(name = "lastModifyTime")
@DataExportAnnotation("最后一次修改时间")
@JsonFormat(pattern = "YYYY-MM-dd HH:mm:ss", locale = "cn", timezone = "GMT+8")
private Date lastModifyTime;
@Column(name = "is_system_admin")
@DataExportAnnotation("是否系统管理员")
private Boolean isSystemAdmin;
@Column(name = "real_name")
@DataExportAnnotation("用户真实姓名")
private String realName;
@Column(name = "state")
@DataExportAnnotation("状态 1正常 0停用 -1删除 ")
private Integer state;
@Transient
@DataExportAnnotation("查询条件 分页页码")
private Integer pageNum;
@Transient
@DataExportAnnotation("查询条件 分页个数")
private Integer pageSize;
@Transient
@DataExportAnnotation("修改内容 旧密码")
private String oldPass;
@Transient
@DataExportAnnotation("修改内容 新密码")
private String newPass;
@Transient
@DataExportAnnotation("修改内容 校验密码:二次输入新密码")
private String checkPass;
@Transient
@DataExportAnnotation("返回结果 所属企业名称")
private String companyName;
@Transient
@DataExportAnnotation("查询条件 产品标识")
private String applicationCode;
@Transient
@DataExportAnnotation("查询条件 角色标识")
private String roleCode;
@Transient
@DataExportAnnotation("修改内容 旧密码")
private String oldPwd;
@Transient
@DataExportAnnotation("修改内容 新密码")
private String newPwd;
@Transient
@DataExportAnnotation("修改内容 校验密码:二次输入新密码")
private String checkNewPwd;
@Transient
@DataExportAnnotation("参数内容 公司标识")
private String companyCode;
@Transient
@DataExportAnnotation("参数内容 用户ID")
private Integer userId;
@Transient
@DataExportAnnotation("参数内容 用户名称")
private String userName;
@Transient
@DataExportAnnotation("参数内容 员工编号")
private String serialNo;
@Transient
@DataExportAnnotation("返回结果 是否已经绑定角色")
private Integer isBindRole;
}

View File

@ -0,0 +1,34 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Oauth2获取Token返回信息封装
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class Oauth2Token {
/**
* 访问令牌
*/
private String token;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 访问令牌头前缀
*/
private String tokenHead;
/**
* 有效时间(秒)
*/
private int expiresIn;
}

View File

@ -0,0 +1,20 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Data;
@Data
public class RawDataDO {
private String nickName;
private String avatarUrl;
private Integer gender;
private String city;
private String country;
private String province;
}

View File

@ -0,0 +1,17 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class RegisterUser implements Serializable {
private Integer userId;
private String nickName;
private String email;
private String serialNo;
private String mobile;
private String headImgUrl;
private String companyCode;
private String companyName;
}

View File

@ -0,0 +1,115 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
/**
* 登录用户信息
*/
@Data
public class SecurityUser implements UserDetails {
/**
* ID
*/
private Integer id;
/**
* 用户名
*/
private String userName;
private String companyCode;
private String companyName;
/**
* 用户密码
*/
private String password;
/**
* 用户状态
*/
private Boolean enabled;
private String mobile;
private String nickName;
private String email;
private Boolean isCompanyAdmin;
private String dataSource;
private Integer dataSourceId;
/**
* 权限数据
*/
private Collection<SimpleGrantedAuthority> authorities;
public SecurityUser() {
}
public SecurityUser(AuthorizeUser authorizeUser) {
this.setId(authorizeUser.getId());
this.setUserName(authorizeUser.getUserName());
this.setCompanyCode(authorizeUser.getCompanyCode());
this.setPassword(authorizeUser.getPassWord());
this.setMobile(authorizeUser.getMobile());
this.setNickName(authorizeUser.getNickName());
this.setEnabled(authorizeUser.getStatus() == 1);
this.setEmail(authorizeUser.getEmail());
this.setIsCompanyAdmin(authorizeUser.getIsCompanyAdmin());
this.setCompanyName(authorizeUser.getCompanyName());
this.setDataSource(authorizeUser.getDataSource());
this.setDataSourceId(authorizeUser.getDataSourceId());
if (authorizeUser.getRoles() != null) {
authorities = new ArrayList<>();
authorizeUser.getRoles().forEach(item -> authorities.add(new SimpleGrantedAuthority(item)));
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}

View File

@ -0,0 +1,92 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* Author : yj
* Date : 2021-01-14
* Description:
*/
@Data
@Table(name = "user")
@DataExportAnnotation("用户表")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@DataExportAnnotation("主键id")
private Integer id;
@Column(name = "name")
@DataExportAnnotation("姓名")
private String name;
@Column(name = "id_card")
@DataExportAnnotation("身份证")
private String idCard;
@Column(name = "nick_name")
@DataExportAnnotation("昵称")
private String nickName;
@Column(name = "lastModifier")
@DataExportAnnotation("最后修改人")
private Long lastModifier;
@Column(name = "lastModifyTime")
@DataExportAnnotation("最后修时间")
private Date lastModifyTime;
@Column(name = "state")
@DataExportAnnotation("状态")
private Integer state;
@Column(name = "mobile")
@DataExportAnnotation("手机号码")
private String mobile;
@Column(name = "email")
@DataExportAnnotation("邮箱")
private String email;
@Column(name = "avatar")
@DataExportAnnotation("头像")
private String avatar;
@Column(name = "password")
@DataExportAnnotation("密码")
private String password;
@Column(name = "id_card_prev")
@DataExportAnnotation("身份证正面")
private String idCardPrev;
@Column(name = "id_card_next")
@DataExportAnnotation("身份证反面")
private String idCardNext;
@Column(name = "real_name")
@DataExportAnnotation("真实姓名")
private String realName;
@Column(name = "reg_time")
@DataExportAnnotation("注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date regTime;
@Transient
private String companyCode;
@Transient
private String companyName;
public void setId(Integer id) {
this.id = id;
}
}

View File

@ -0,0 +1,16 @@
package cn.crtech.cloud.auth.pojo;
import lombok.Data;
@Data
public class WechatLoginRequest {
private String rawData;
private String signature;
private String encryptedData;
private String iv;
private String username;
private String password;
private String accessToken;
private String companyCode;
private String companyName;
}

View File

@ -0,0 +1,55 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import cn.crtech.cloud.common.utils.IDGenerator;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Data
@Table(name = "wx_applet_config")
@DataExportAnnotation("小程序配置")
public class WxAppletConfig implements Serializable {
@Id
@KeySql(genId = IDGenerator.class)
@DataExportAnnotation("主键")
private String id;
@Column(name = "app_id")
@DataExportAnnotation("应用id")
private String appId;
@Column(name = "app_name")
@DataExportAnnotation("小程序名称")
private String appName;
@Column(name = "app_secret")
@DataExportAnnotation("秘钥")
private String appSecret;
@Column(name = "encoding_aes_key")
@DataExportAnnotation("消息加密key")
private String encodingAesKey;
@Column(name = "token")
@DataExportAnnotation("消息令牌")
private String token;
@Column(name = "status")
@DataExportAnnotation("状态")
private Integer status;
/**
* 消息格式XML或者JSON
*/
@Column(name = "msg_data_format")
@DataExportAnnotation("消息格式")
private String msgDataFormat;
}

View File

@ -0,0 +1,87 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Data
@Table(name = "wx_user")
@DataExportAnnotation("小程序用户")
public class WxAppletUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@DataExportAnnotation("主键id")
private Integer id;
@Column(name = "nick_name")
@DataExportAnnotation("用户昵称")
private String nickName;
@Column(name = "avatar_url")
@DataExportAnnotation("用户头像")
private String avatarUrl;
@Column(name = "gender")
@DataExportAnnotation("性别")
private int gender;
@Column(name = "country")
@DataExportAnnotation("所在国家")
private String country;
@Column(name = "province")
@DataExportAnnotation("省份")
private String province;
@Column(name = "city")
@DataExportAnnotation("城市")
private String city;
@Column(name = "mobile")
@DataExportAnnotation("手机号码")
private String mobile;
@Column(name = "open_id")
@DataExportAnnotation("小程序openId")
private String openId;
@Column(name = "union_id")
@DataExportAnnotation("小程序unionId")
private String unionId;
@Column(name = "app_id")
@DataExportAnnotation("应用id")
private String appId;
@Column(name = "wx_open_id")
@DataExportAnnotation("微信openId")
private String wxOpenId;
@Column(name = "created")
@DataExportAnnotation("注册时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date created;
@Column(name = "updated")
@DataExportAnnotation("更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updated;
@Column(name = "qr_open_id")
@DataExportAnnotation("扫二维码openId")
private String qrOpenId;
@Column(name = "company_code")
@DataExportAnnotation("企业标识")
private String companyCode;
@Column(name = "company_name")
@DataExportAnnotation("企业标识")
private String companyName;
@Transient
private String serialNo;
}

View File

@ -0,0 +1,49 @@
package cn.crtech.cloud.auth.pojo;
import cn.crtech.cloud.common.annotation.DataExportAnnotation;
import cn.crtech.cloud.common.utils.IDGenerator;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Data
@Table(name = "wx_gzh_config")
@DataExportAnnotation("公众号号配置")
public class WxGzhConfig implements Serializable {
@Id
@KeySql(genId = IDGenerator.class)
@DataExportAnnotation("主键")
private String id;
@Column(name = "name")
@DataExportAnnotation("公众号名称")
private String name;
@Column(name = "app_id")
@DataExportAnnotation("应用id")
private String appId;
@Column(name = "app_secret")
@DataExportAnnotation("秘钥")
private String appSecret;
@Column(name = "app_token")
@DataExportAnnotation("消息令牌")
private String appToken;
@Column(name = "app_aeskey")
@DataExportAnnotation("消息加密key")
private String appAeskey;
@Column(name = "status")
@DataExportAnnotation("状态")
private Integer status;
}

View File

@ -0,0 +1,198 @@
package cn.crtech.cloud.auth.service;
import cn.crtech.cloud.auth.dto.MisRoleApiDto;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.common.constant.AuthConstant;
import cn.crtech.cloud.common.constant.RedisConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* 资源与角色匹配关系管理业务类
*/
@Component
public class ResourceService {
private RedisTemplate redisTemplate;
private AuthorizeMapper authorizeMapper;
@Autowired
public ResourceService(RedisTemplate redisTemplate, AuthorizeMapper authorizeMapper) {
this.redisTemplate = redisTemplate;
this.authorizeMapper = authorizeMapper;
}
/**
* 初始化加载并将权限信息注入缓存
*/
@PostConstruct
public void initData() {
// 特殊权限授权内容加载处理
initLimitAuthority();
// 免费角色权限数据内容加载处理
initFreeController();
// 企业角色权限数据内容加载处理
initComRoleController();
// 企业管理员权限数据内容加载处理
initComAdminController();
// 产品默认角色权限数据内容加载处理
initAppDefaultRoleController();
}
/**
* 缓存产品特殊权限内容
*/
private void initLimitAuthority() {
redisTemplate.delete(RedisConstant.LIMIT_AUTHORITY_API_MAP);
List<MisRoleApiDto> limitAuthority = authorizeMapper.listLimitApi();
if (!CollectionUtils.isEmpty(limitAuthority)) {
Map<String, String> limitAuthorityMap = new TreeMap<>();
limitAuthority.forEach(item -> {
limitAuthorityMap.put(item.getController(), item.getRole());
});
redisTemplate.opsForHash().putAll(RedisConstant.LIMIT_AUTHORITY_API_MAP, limitAuthorityMap);
}
}
/**
* 缓存免费产品及对应产品菜单权限功能
*/
private void initFreeController() {
// 缓存免费产品及对应产品菜单权限功能
List<MisRoleApiDto> freeAppList = authorizeMapper.listFreeApp();
if (!CollectionUtils.isEmpty(freeAppList)) {
// 相关菜单加载
List<MisRoleApiDto> freeControllerList = authorizeMapper.listFreeController();
// 相关菜单特殊权限加载
List<MisRoleApiDto> freeAuthorityList = authorizeMapper.listFreeAuthority();
// 相关免费内容处理缓存 (根据产品分组ID、产品ID以及对应版本ID进行分组处理)
freeAppList.forEach(item -> {
String seriesAppVersion = RedisConstant.FREE_VERSION_API
.replace("${SERIESCODE}", item.getSeriesCode())
.replace("${APPCODE}", item.getAppCode())
.replace("${VERSION}", item.getVersion());
redisTemplate.delete(seriesAppVersion);
// 数据缓存处理
Map<String, String> freeResourceMap = new TreeMap<>();
List<MisRoleApiDto> tempList = freeControllerList.stream().filter(it -> it.getSeriesCode().equals(item.getSeriesCode())
&& it.getAppCode().equals(item.getAppCode())
&& it.getVersion().equals(item.getVersion()))
.collect(Collectors.toList());
tempList.forEach(temp -> {
freeResourceMap.put(temp.getController(), temp.getRole());
});
List<MisRoleApiDto> tempAuthorityList = freeAuthorityList.stream().filter(it -> it.getSeriesCode().equals(item.getSeriesCode())
&& it.getAppCode().equals(item.getAppCode())
&& it.getVersion().equals(item.getVersion()))
.collect(Collectors.toList());
tempAuthorityList.forEach(temp -> {
freeResourceMap.put(temp.getController() + AuthConstant.APP_SPECIAL_AUTHORITY_SUFFIX, temp.getRole());
});
freeResourceMap.put("/general/**", "ALL");
freeResourceMap.put("/mns/**", "ALL");
freeResourceMap.put("/rm/**", "ALL");
freeResourceMap.put("/main/**", "ALL");
redisTemplate.opsForHash().putAll(seriesAppVersion, freeResourceMap);
});
}
// 缓存产品内服务间调用内容
redisTemplate.delete(RedisConstant.APP_FEIGN_API_MAP);
List<MisRoleApiDto> appFeignList = authorizeMapper.listAppFeignController();
if (!CollectionUtils.isEmpty(appFeignList)) {
Map<String, String> appFeignMap = new TreeMap<>();
appFeignList.forEach(item -> {
appFeignMap.put(item.getController(), item.getRole());
});
redisTemplate.opsForHash().putAll(RedisConstant.APP_FEIGN_API_MAP, appFeignMap);
}
}
/**
* 缓存企业角色对应产品菜单权限功能
*/
private void initComRoleController() {
redisTemplate.delete(RedisConstant.RESOURCE_ROLES_MAP);
// 企业角色相关菜单加载
List<MisRoleApiDto> comRoleApiList = authorizeMapper.listRoleController();
// 企业角色相关菜单特殊权限加载
List<MisRoleApiDto> comRoleAuthorityList = authorizeMapper.listRoleAuthorityController();
// 企业角色相关内容处理缓存
Map<String, String> comRoleMap = new TreeMap<>();
comRoleApiList.forEach(item -> {
comRoleMap.put(item.getController(), item.getRole());
});
comRoleAuthorityList.forEach(item -> {
comRoleMap.put(item.getController() + AuthConstant.APP_SPECIAL_AUTHORITY_SUFFIX, item.getRole());
});
comRoleMap.put("/general/**", "ALL");
comRoleMap.put("/mns/**", "ALL");
comRoleMap.put("/rm/**", "ALL");
comRoleMap.put("/main/**", "ALL");
redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, comRoleMap);
}
/**
* 缓存企业管理员对应产品菜单权限功能
*/
private void initComAdminController() {
redisTemplate.delete(RedisConstant.RESOURCE_ADMIN_MAP);
// 企业管理员相关菜单加载
List<MisRoleApiDto> comAdminController = authorizeMapper.getCompanyAdminController();
// 企业管理员相关菜单特殊权限加载
List<MisRoleApiDto> comAdminAuthorityController = authorizeMapper.getCompanyAdminAuthorityController();
// 企业管理员相关内容处理缓存
Map<String, String> comAdminMap = new TreeMap<>();
comAdminController.forEach(item -> {
comAdminMap.put(item.getController(), item.getRole());
});
comAdminAuthorityController.forEach(item -> {
comAdminMap.put(item.getController() + AuthConstant.APP_SPECIAL_AUTHORITY_SUFFIX, item.getRole());
});
comAdminMap.put("/general/**", "ALL");
comAdminMap.put("/mns/**", "ALL");
comAdminMap.put("/rm/**", "ALL");
comAdminMap.put("/main/**", "ALL");
redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ADMIN_MAP, comAdminMap);
}
/**
* 缓存产品默认角色对应产品菜单权限功能
*/
private void initAppDefaultRoleController() {
redisTemplate.delete(RedisConstant.DEFAULT_ROLES_MAP);
// 产品默认角色相关菜单加载
List<MisRoleApiDto> appDefaultRoleApiList = authorizeMapper.listDefaultRoleController();
// 产品默认角色相关菜单特殊权限加载
List<MisRoleApiDto> appDefaultRoleAuthorityApiList = authorizeMapper.listDefaultRoleAuthorityController();
// 产品默认角色关内容处理缓存
Map<String, String> appDefaultRoleMap = new TreeMap<>();
appDefaultRoleApiList.forEach(item -> {
appDefaultRoleMap.put(item.getController(), item.getRole());
});
appDefaultRoleAuthorityApiList.forEach(item -> {
appDefaultRoleMap.put(item.getController() + AuthConstant.APP_SPECIAL_AUTHORITY_SUFFIX, item.getRole());
});
appDefaultRoleMap.put("/general/**", "ALL");
appDefaultRoleMap.put("/mns/**", "ALL");
appDefaultRoleMap.put("/rm/**", "ALL");
appDefaultRoleMap.put("/main/**", "ALL");
redisTemplate.opsForHash().putAll(RedisConstant.DEFAULT_ROLES_MAP, appDefaultRoleMap);
}
}

View File

@ -0,0 +1,143 @@
package cn.crtech.cloud.auth.service.system;
import cn.crtech.cloud.auth.dto.MisComRoleUserDto;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.auth.pojo.AuthorizeUser;
import cn.crtech.cloud.auth.pojo.MisUser;
import cn.crtech.cloud.auth.pojo.SecurityUser;
import cn.crtech.cloud.common.constant.MessageConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.CollectionUtils;
import java.util.*;
/**
* Author : yj
* Date : 2021-01-14
* Description:初始化登录用户信息查询用户权限有哪些角色以及登录接口loadUserByUsername
*/
@Configuration
@Slf4j
public class AuthService implements AuthUserService {
@Autowired
AuthorizeMapper authorizeMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
/**
* 重写loadUserByUsername方法 后端登录token
*
* @param username 用户登录名称
* @return 返回登录结果
* @throws UsernameNotFoundException 用户登录名不存在异常
*/
@Override
public UserDetails loadUserByUsername(String username, String companyCode) throws UsernameNotFoundException {
// 根据登录账号获取相关用户信息
MisUser misUser = authorizeMapper.queryUserByMobile(username);
if (ObjectUtils.isEmpty(misUser)) {
throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
}
// 数据处理赋值
Map<String, Object> params = new HashMap<>();
params.put("userId", misUser.getId());
AuthorizeUser authorizeUser = new AuthorizeUser(misUser.getId(), misUser.getPassword(),
misUser.getState(), username, misUser.getName(),
misUser.getEmail(), "", false);
if (StringUtils.isNotBlank(companyCode)) {
params.put("companyCode", companyCode);
}
// 其他授权信息获取处理
getUserAuthorize(misUser, params, authorizeUser);
// 安全用户新增赋值
SecurityUser securityUser = new SecurityUser(authorizeUser);
// 登录用户异常问题处理
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
}
if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
}
if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
}
if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
}
return securityUser;
}
/**
* 用户登录授权信息补充处理
*
* @param user 用户信息对象
* @param params 其他信息
* @param authorizeUser 授权用户对象
*/
private void getUserAuthorize(MisUser user, Map<String, Object> params, AuthorizeUser authorizeUser) {
List<MisComUserDto> companyList = authorizeMapper.queryCompanyUserByUserId(params);
if (CollectionUtils.isEmpty(companyList)) {
List<String> userRoleList = new ArrayList<>();
userRoleList.add("ALL");
authorizeUser.setRoles(userRoleList);
authorizeUser.setDataSource("");
} else {
// 获取默认企业
MisComUserDto defaultCompany = companyList.get(0);
authorizeUser.setCompanyCode(defaultCompany.getCompanyCode());
authorizeUser.setCompanyName(defaultCompany.getCompanyName());
List<String> userRoleList = new ArrayList<>();
userRoleList.add("ALL");
//设置企业管理员
if (defaultCompany.getIsOwner()) {
userRoleList.add(defaultCompany.getCompanyCode() + "_admin");
authorizeUser.setIsCompanyAdmin(true);
} else {
// 查询获取授权角色数据集合
List<MisComRoleUserDto> userRoles = authorizeMapper.queryCompanyRoleUser(user.getId(), defaultCompany.getCompanyCode());
userRoles.forEach(role -> {
userRoleList.add(role.getCompanyCode() + "_" + role.getRoleId());
});
// 查询用户企业授权角色相关内容
String comRoleCodes = authorizeMapper.queryUserRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(comRoleCodes)) {
String[] roleCodeArray = comRoleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
// 查询用户默认授权角色相关内容
String defaultRoleCodes = authorizeMapper.queryUserDefaultRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(defaultRoleCodes)) {
String[] roleCodeArray = defaultRoleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
}
log.info("userRoleList ==> {}", userRoleList.toString());
authorizeUser.setRoles(userRoleList);
}
}
}

View File

@ -0,0 +1,9 @@
package cn.crtech.cloud.auth.service.system;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public interface AuthUserService extends UserDetailsService {
UserDetails loadUserByUsername(String username, String companyCode) throws UsernameNotFoundException;
}

View File

@ -0,0 +1,9 @@
package cn.crtech.cloud.auth.service.wx.applet;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public interface WxAppletUserService extends UserDetailsService {
UserDetails loadUserByUsername(String username, String companyCode) throws UsernameNotFoundException;
}

View File

@ -0,0 +1,132 @@
package cn.crtech.cloud.auth.service.wx.applet;
import cn.crtech.cloud.auth.dto.MisComRoleUserDto;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.auth.mapper.WxMaAuthorizeMapper;
import cn.crtech.cloud.auth.pojo.AuthorizeUser;
import cn.crtech.cloud.auth.pojo.MisUser;
import cn.crtech.cloud.auth.pojo.SecurityUser;
import cn.crtech.cloud.auth.pojo.WxAppletUser;
import cn.crtech.cloud.common.constant.MessageConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.CollectionUtils;
import java.util.*;
@Slf4j
@Configuration
public class WxAppletUserServiceImpl implements WxAppletUserService {
private AuthorizeMapper authorizeMapper;
private WxMaAuthorizeMapper wxMaAuthorizeMapper;
@Autowired
public WxAppletUserServiceImpl(WxMaAuthorizeMapper wxMaAuthorizeMapper, AuthorizeMapper authorizeMapper) {
this.wxMaAuthorizeMapper = wxMaAuthorizeMapper;
this.authorizeMapper = authorizeMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
/**
* 重构微信用户登录内容
*
* @param username 登录账号
* @param companyCode 公司标识
* @return 返回登录处理结果
* @throws UsernameNotFoundException 抛出异常
*/
@Override
public UserDetails loadUserByUsername(String username, String companyCode) throws UsernameNotFoundException {
// 校验非空参数
if (StringUtils.isBlank(username)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_SIGNATURE_ERROR);
}
// 查询并校验对应微信用户是否存在
WxAppletUser selectWxAppletUser = wxMaAuthorizeMapper.getUserByWxAppletOpenId(username).get(0);
if (ObjectUtils.isEmpty(selectWxAppletUser)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_ERROR);
}
// 查询并校验对应系统用户是否存在
MisUser user = authorizeMapper.queryUserByMobile(selectWxAppletUser.getMobile());
if (ObjectUtils.isEmpty(user)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_ERROR);
}
// 生成认证用户信息
AuthorizeUser authorizeUser = new AuthorizeUser(user.getId(), user.getPassword(), user.getState(), user.getMobile(), user.getName(), user.getEmail(), "", false);
// 生成用户绑定企业信息相关查询参数
Map<String, Object> params = new HashMap<>();
params.put("userId", user.getId());
if (!StringUtils.isBlank(companyCode)) {
params.put("companyCode", companyCode);
}
// 查询用户关联企业信息内容
List<MisComUserDto> comUserList = authorizeMapper.queryCompanyUserByUserId(params);
if (CollectionUtils.isEmpty(comUserList)) {
List<String> userRoleList = new ArrayList<>();
userRoleList.add("ALL");
authorizeUser.setRoles(userRoleList);
} else {
MisComUserDto defaultCompany = comUserList.get(0);
List<String> userRoleList = new ArrayList<>();
// 设置默认公司
authorizeUser.setCompanyCode(defaultCompany.getCompanyCode());
authorizeUser.setCompanyName(defaultCompany.getCompanyName());
// 判断是否企业管理员
if (defaultCompany.getIsOwner()) {
authorizeUser.setIsCompanyAdmin(true);
userRoleList.add(defaultCompany.getCompanyCode() + "_admin");
} else {
List<MisComRoleUserDto> userRoles = authorizeMapper.queryCompanyRoleUser(user.getId(), defaultCompany.getCompanyCode());
userRoles.forEach(role -> {
userRoleList.add(role.getCompanyCode() + "_" + role.getRoleId());
});
String roleCodes = authorizeMapper.queryUserRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(roleCodes)) {
String[] roleCodeArray = roleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
String defaultRoleCodes = authorizeMapper.queryUserDefaultRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(defaultRoleCodes)) {
String[] roleCodeArray = defaultRoleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
}
userRoleList.add("ALL");
authorizeUser.setRoles(userRoleList);
}
SecurityUser securityUser = new SecurityUser(authorizeUser);
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
}
if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
}
if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
}
if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
}
return securityUser;
}
}

View File

@ -0,0 +1,574 @@
package cn.crtech.cloud.auth.service.wx.applet;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.auth.mapper.WxMaAuthorizeMapper;
import cn.crtech.cloud.auth.pojo.*;
import cn.crtech.cloud.auth.utils.TencentUtils;
import cn.crtech.cloud.common.dto.Result;
import cn.crtech.cloud.common.utils.EncryptUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.*;
@Slf4j
@Configuration
public class WxMaUserService {
private static final String secretKey = "www.crtech.com";
private WxMpService wxMpService;
private WxMaService wxMaService;
private TencentUtils tencentUtils;
private RedisTemplate redisTemplate;
private PasswordEncoder passwordEncoder;
private AuthorizeMapper authorizeMapper;
private WxMaAuthorizeMapper wxMaConfigMapper;
private WxMaAuthorizeMapper wxMaAuthorizeMapper;
@Autowired
public WxMaUserService(WxMaService wxMaService, WxMaAuthorizeMapper wxMaConfigMapper,
RedisTemplate redisTemplate, AuthorizeMapper authorizeMapper,
WxMpService wxMpService, PasswordEncoder passwordEncoder,
TencentUtils tencentUtils, WxMaAuthorizeMapper wxMaAuthorizeMapper) {
this.wxMaService = wxMaService;
this.wxMaConfigMapper = wxMaConfigMapper;
this.redisTemplate = redisTemplate;
this.authorizeMapper = authorizeMapper;
this.wxMpService = wxMpService;
this.passwordEncoder = passwordEncoder;
this.tencentUtils = tencentUtils;
this.wxMaAuthorizeMapper = wxMaAuthorizeMapper;
}
/**
* 校验是否关注、绑定
*
* @param wxAppletCode 微信小程序标识
* @param code 参数标识
* @param appId 公众号AppID
* @return 返回校验结果
*/
@Transactional(readOnly = true)
public Result checkUser(String wxAppletCode, String code, String appId) {
// 参数校验
Map<String, Object> params = new HashMap<>();
if (!wxMaService.switchover(appId)) {
throw new IllegalArgumentException(String.format("未找到对应 AppID=[%s] 的配置,请核实!", appId));
}
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String wxOpenId = accessToken.getOpenId();
WxMpUser wxMpUser = wxMpService.getUserService().userInfo(wxOpenId);
boolean subscribe = wxMpUser.getSubscribe();
WxOAuth2UserInfo wxOAuth2UserInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, wxOpenId);
String headImgUrl = wxOAuth2UserInfo.getHeadImgUrl();
log.info("wxOAuth2UserInfo ===> {}", wxOAuth2UserInfo.toString());
// 用户关注公众号校验
if (!subscribe) {
params.put("code", "unSubscribe");
return Result.error(params, "未进行公众号绑定!");
}
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(wxAppletCode);
String wxAppletOpenId = session.getOpenid();
//校验用户是否注册
List<WxAppletUser> selectWxAppletUser = wxMaConfigMapper.getUserByWxAppletOpenId(wxAppletOpenId);
if (selectWxAppletUser.isEmpty()) {
//返回对应的token
String sessionKey = session.getSessionKey();
String systemAccessToken = createToken(wxAppletOpenId, sessionKey, appId, wxOpenId, headImgUrl, null, null);
params.put("accessToken", systemAccessToken);
params.put("code", "unBindApplet");
return Result.error(params, "未进行系统绑定!");
}
//校验通过 返回小程序openId
params.put("openId", wxAppletOpenId);
params.put("wxOpenId", wxOpenId);
return Result.success(params);
} catch (WxErrorException e) {
params.put("code", "systemException");
return Result.error(params, e.getError().getErrorMsg());
}
}
/**
* 获取微信用户对应绑定openID
*
* @param wxAppletCode 微信小程序标识
* @param appId 公众号AppID
* @return 返回查询结果
*/
@Transactional(readOnly = true)
public Result getWxAppletOpenId(String wxAppletCode, String appId) {
// 参数校验
Map<String, Object> params = new HashMap<>();
if (!wxMaService.switchover(appId)) {
throw new IllegalArgumentException(String.format("未找到对应 AppID=[%s] 的配置,请核实!", appId));
}
try {
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(wxAppletCode);
String wxAppletOpenId = session.getOpenid();
//校验通过 返回小程序openId
params.put("openId", wxAppletOpenId);
return Result.success(params);
} catch (WxErrorException e) {
e.printStackTrace();
params.put("code", "systemException");
return Result.error(params, "非法code!");
}
}
/**
* 小程序绑定
*
* @param request 微信请求参数信息
* @return 返回结果
*/
@Transactional(rollbackFor = Exception.class)
public Result bindWxApplet(WechatLoginRequest request) {
// 参数非空校验
if (StringUtils.isBlank(request.getUsername())) {
return Result.error("", "参数异常: 用户名不能为空!");
}
if (StringUtils.isBlank(request.getPassword())) {
return Result.error("", "参数异常: 用户密码不能为空!");
}
// 其他公用对象内容生成
String mobile = request.getUsername();
String passWord = request.getPassword();
// 校验查询绑定用户
MisUser misUser = authorizeMapper.queryUserByMobile(mobile);
if (ObjectUtils.isEmpty(misUser)) {
return Result.error("", "参数异常: 系统未查找到当前账号绑定用户!");
}
// 登录密码校验
if (!passwordEncoder.matches(passWord, misUser.getPassword())) {
return Result.error("", "参数异常: 绑定密码与系统密码不符!");
}
// 解析accessToken拆分对应授权信息
String decryptAccessToken = new String(Objects.requireNonNull(
EncryptUtil.decrypt(EncryptUtil.parseHexStr2Byte(request.getAccessToken()), secretKey)),
StandardCharsets.UTF_8);
Map accessTokenMap = JSONObject.parseObject(decryptAccessToken, Map.class);
String openId = accessTokenMap.get("openId").toString();
String sessionKey = accessTokenMap.get("sessionKey").toString();
String appId = accessTokenMap.get("appId").toString();
String wxOpenId = accessTokenMap.get("wxOpenId").toString();
String headImgUrl = accessTokenMap.get("headImgUrl").toString();
// 创建生成 AppUser
WxAppletUser insertOrUpdateDO = buildWechatUserAuthInfoDO(request.getRawData(),
request.getEncryptedData(), request.getIv(), sessionKey, openId);
insertOrUpdateDO.setMobile(mobile);
// 用户企业内容请求参数创建
Map<String, Object> params = new HashMap<>();
params.put("userId", misUser.getId());
if (!StringUtils.isBlank(request.getCompanyCode())) {
params.put("companyCode", request.getCompanyCode());
}
// 查询相关绑定企业内容并设置企业信息
List<MisComUserDto> companyList = authorizeMapper.queryCompanyUserByUserId(params);
if (CollectionUtils.isEmpty(companyList)) {
if (StringUtils.isBlank(request.getCompanyCode()) && StringUtils.isBlank(request.getCompanyName())) {
return Result.error(mobile, "绑定异常,请联系相关管理人员!");
} else {
insertOrUpdateDO.setCompanyCode(request.getCompanyCode());
insertOrUpdateDO.setCompanyName(request.getCompanyName());
insertOrUpdateDO.setSerialNo("No" + (int) ((Math.random() * 9 + 1)));
}
} else {
MisComUserDto defaultCompany = companyList.get(0);
if (!ObjectUtil.isEmpty(defaultCompany)) {
insertOrUpdateDO.setCompanyCode(defaultCompany.getCompanyCode());
insertOrUpdateDO.setCompanyName(defaultCompany.getCompanyName());
insertOrUpdateDO.setSerialNo(defaultCompany.getSerialNo());
}
}
// 校验用户是否存在 不存在则插入 存在则进行更新
int success = 0;
WxAppletUser selectWxAppletUser = wxMaAuthorizeMapper.getWxAppletUser(openId, insertOrUpdateDO.getCompanyCode());
if (ObjectUtils.isEmpty(selectWxAppletUser)) {
insertOrUpdateDO.setWxOpenId(wxOpenId);
insertOrUpdateDO.setAppId(appId);
insertOrUpdateDO.setAvatarUrl(headImgUrl);
success = wxMaAuthorizeMapper.insertWxAppletUser(insertOrUpdateDO);
} else {
selectWxAppletUser.setNickName(insertOrUpdateDO.getNickName());
selectWxAppletUser.setAvatarUrl(headImgUrl);
selectWxAppletUser.setGender(insertOrUpdateDO.getGender());
selectWxAppletUser.setCountry(insertOrUpdateDO.getCountry());
selectWxAppletUser.setProvince(insertOrUpdateDO.getProvince());
selectWxAppletUser.setCity(insertOrUpdateDO.getCity());
selectWxAppletUser.setMobile(insertOrUpdateDO.getMobile());
selectWxAppletUser.setOpenId(insertOrUpdateDO.getOpenId());
selectWxAppletUser.setUnionId(insertOrUpdateDO.getUnionId());
success = wxMaAuthorizeMapper.updateWxAppletUser(selectWxAppletUser);
}
if (success <= 0) {
return Result.error(mobile, "绑定失败,请联系相关管理人员!");
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("openId", openId);
resultMap.put("wxOpenId", wxOpenId);
resultMap.put("mobile", mobile);
resultMap.put("headImgUrl", headImgUrl);
resultMap.put("userId", misUser.getId());
resultMap.put("nickName", misUser.getNickName());
resultMap.put("serialNo", insertOrUpdateDO.getSerialNo());
resultMap.put("companyCode", insertOrUpdateDO.getCompanyCode());
resultMap.put("companyName", insertOrUpdateDO.getCompanyName());
return Result.success(resultMap, "绑定成功!");
}
/**
* 实名认证
*
* @param params 参数对象
* <ul>
* <li>userName: 用户名</li>
* <li>mobile: 手机号码</li>
* <li>idCard: 身份证号码</li>
* </ul>
* @return 返回操作结果
*/
@Transactional(rollbackFor = Exception.class)
public Result userMobileVerify(Map<String, Object> params) {
String userName = params.get("userName").toString();
String mobile = params.get("mobile").toString();
String idCard = params.get("idCard").toString();
MisUser misUser = authorizeMapper.queryUserByMobile(mobile);
if (ObjectUtil.isEmpty(misUser)) {
return Result.error(mobile, "未查找到当前用户");
} else {
if (!StringUtils.isBlank(misUser.getIdCard())) {
return Result.error(mobile, "当前已实名过,请勿重复认证,如需修改,请联系客服!");
} else {
Result result = tencentUtils.releaseMobileVerify(userName, mobile, idCard);
if (result.isSuccess()) {
authorizeMapper.updateUserIdCard(idCard, userName, mobile);
}
return result;
}
}
}
/**
* 微信用户登录
*
* @param code 登录参数
* @return 返回登录结果
*/
public Result login(String code) {
Map<String, Object> params = new HashMap<>();
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken("wx581b7b669d241777", "2d66509495044eea130e86e49d21be2d", code);
//unionId 做为查询
String unionId = accessToken.getUnionId();
String wxOpenId = accessToken.getOpenId();
WxOAuth2UserInfo wxOAuth2UserInfo = wxMpService.getOAuth2Service()
.getUserInfo(accessToken, wxOpenId);
System.out.println(wxOAuth2UserInfo.toString());
} catch (WxErrorException e) {
params.put("code", "systemException");
return Result.error(params, e.getError().getErrorMsg());
}
return Result.success(params);
}
/**
* 通过openId校验人员是否在该公司
*
* @param wxAppletCode 小程序标识
* @param code 登录标识
* @param appId 小程序APPId
* @param companyCode 公司标识
* @param companyName 公司名称
* @return 返回校验结果
*/
public Result checkWxRegisterUser(String wxAppletCode, String code, String appId, String companyCode, String companyName) {
// 参数校验
if (!wxMaService.switchover(appId)) {
throw new IllegalArgumentException(String.format("未找到对应 AppID=[%s] 的配置,请核实!", appId));
}
Map<String, Object> params = new HashMap<>();
try {
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String wxOpenId = accessToken.getOpenId();
WxMpUser wxMpUser = wxMpService.getUserService().userInfo(wxOpenId);
boolean subscribe = wxMpUser.getSubscribe();
WxOAuth2UserInfo wxOAuth2UserInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, wxOpenId);
String headImgUrl = wxOAuth2UserInfo.getHeadImgUrl();
log.info("微信用户 ===> {}", wxOAuth2UserInfo.toString());
// 公众号校验
if (!subscribe) {
params.put("code", "unSubscribe");
return Result.error(params, "未进行公众号绑定!");
}
// 登录Session
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(wxAppletCode);
String wxAppletOpenId = session.getOpenid();
// 校验用户是否绑定
WxAppletUser selectWxAppletUser = wxMaConfigMapper.getWxAppletUser(wxAppletOpenId, companyCode);
if (ObjectUtils.isEmpty(selectWxAppletUser)) {
// 返回对应的token
String sessionKey = session.getSessionKey();
String loginAccessToken = createToken(wxAppletOpenId, sessionKey, appId, wxOpenId, headImgUrl,
companyCode, companyName);
params.put("accessToken", loginAccessToken);
params.put("code", "unUserRegister");
return Result.error(params, "未注册!");
}
// 校验是否存在对应注册用户
MisComUserDto misComUserDto = wxMaAuthorizeMapper.queryCompanyUser(companyCode, selectWxAppletUser.getMobile());
if (ObjectUtils.isEmpty(misComUserDto)) {
// 返回对应的token
String sessionKey = session.getSessionKey();
String loginAccessToken = createToken(wxAppletOpenId, sessionKey, appId, wxOpenId, headImgUrl,
companyCode, companyName);
params.put("accessToken", loginAccessToken);
params.put("code", "unUserRegister");
return Result.error(params, "未注册!");
}
// 已注册 状态为禁用 需提示
if (misComUserDto.getState() == 0) {
params.put("code", "userException");
return Result.error(null, "您已注册过,请勿重复注册,当前账号异常,请联系相关人员!");
}
//校验通过 返回小程序openId
params.put("openId", wxAppletOpenId);
params.put("wxOpenId", wxOpenId);
params.put("companyCode", companyCode);
return Result.success(params);
} catch (WxErrorException e) {
params.put("code", "systemException");
return Result.error(params, e.getError().getErrorMsg());
}
}
/**
* 小程序 用户注册
*
* @param registerUser 参数对象
* @return 返回操作结果
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Result registerCompanyUser(RegisterUser registerUser) {
// 校验对应手机号码是否存在绑定用户
MisUser misUser = authorizeMapper.queryUserByMobile(registerUser.getMobile());
Integer userId = null;
if (ObjectUtils.isEmpty(misUser)) {
MisUser user = new MisUser();
user.setRealName(registerUser.getNickName());
user.setNickName(registerUser.getNickName());
user.setName(registerUser.getNickName());
user.setEmail(StringUtils.isBlank(registerUser.getEmail()) ? "" : registerUser.getEmail());
user.setMobile(registerUser.getMobile());
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String password = passwordEncoder.encode(registerUser.getMobile());
user.setPassword(password.substring(8));
user.setState(1);
user.setRegTime(new Date());
authorizeMapper.insertUser(user);
userId = user.getId();
} else {
misUser.setRealName(registerUser.getNickName());
misUser.setNickName(registerUser.getNickName());
misUser.setName(registerUser.getNickName());
if (!StringUtils.isBlank(registerUser.getEmail())) {
misUser.setEmail(registerUser.getEmail());
}
//更新
userId = misUser.getId();
authorizeMapper.updateRegisterUser(misUser);
}
// 查询相关公司用户数据并进行处理更新
List<MisComUserDto> comUserList = wxMaAuthorizeMapper.getUserCompanyList(registerUser.getMobile());
if (!CollectionUtils.isEmpty(comUserList)) {
for (MisComUserDto companyUser : comUserList) {
wxMaAuthorizeMapper.updateCompanyDefault(companyUser.getCompanyCode(), userId, 0);
}
}
// 查询当前登录公司用户对象
MisComUserDto selectCompanyUser = wxMaAuthorizeMapper.queryCompanyUser(registerUser.getCompanyCode(), registerUser.getMobile());
String serialNo = StringUtils.isBlank(registerUser.getSerialNo()) ? ("No" + (int) ((Math.random() * 9 + 1) * 100000)) : registerUser.getSerialNo();
if (ObjectUtils.isEmpty(selectCompanyUser)) {
MisComUserDto companyUser = new MisComUserDto();
companyUser.setUserId(userId);
companyUser.setCompanyCode(registerUser.getCompanyCode());
companyUser.setSerialNo(serialNo);
companyUser.setState(1);
companyUser.setIsDefault(1);
companyUser.setIsOwner(false);
authorizeMapper.insertCompanyUser(companyUser);
} else {
selectCompanyUser.setSerialNo(serialNo);
selectCompanyUser.setState(1);
selectCompanyUser.setIsDefault(1);
authorizeMapper.updateCompanyUser(selectCompanyUser);
}
//补足用户信息
registerUser.setUserId(userId);
registerUser.setSerialNo(serialNo);
return Result.success(registerUser, "注册成功!");
}
/**
* 创建访问Token
*
* @param openId 小程序用户openId
* @param sessionKey session关键词
* @param appId 小程序APPID
* @param wxOpenId 微信用户OpenId
* @param headImgUrl 头像地址
* @param companyCode 公司标识
* @param companyName 公司名称
* @return 返回创建结果
*/
private static String createToken(String openId, String sessionKey, String appId, String wxOpenId,
String headImgUrl, String companyCode, String companyName) {
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("openId", openId);
objectMap.put("sessionKey", sessionKey);
objectMap.put("appId", appId);
objectMap.put("wxOpenId", wxOpenId);
objectMap.put("headImgUrl", headImgUrl);
if (StringUtils.isNotEmpty(companyCode)) {
objectMap.put("companyCode", companyCode);
}
if (StringUtils.isNotEmpty(companyName)) {
objectMap.put("companyName", companyName);
}
return EncryptUtil.parseByte2HexStr(Objects.requireNonNull(EncryptUtil.encrypt(JSON.toJSONString(objectMap), secretKey)));
}
/**
* 创建AppUser
*
* @param rawData 行数据
* @param encryptData 加密数据
* @param iv 参数IV
* @param sessionKey session关键词
* @param openId 用户openId
* @return 返回创建对象
*/
private WxAppletUser buildWechatUserAuthInfoDO(String rawData, String encryptData, String iv, String sessionKey, String openId) {
WxAppletUser wxAppletUser = new WxAppletUser();
wxAppletUser.setOpenId(openId);
if (!StringUtils.isBlank(rawData)) {
RawDataDO rawDataDO = JSON.parseObject(rawData, RawDataDO.class);
wxAppletUser.setNickName(rawDataDO.getNickName());
wxAppletUser.setGender(rawDataDO.getGender());
wxAppletUser.setCity(rawDataDO.getCity());
wxAppletUser.setCountry(rawDataDO.getCountry());
wxAppletUser.setProvince(rawDataDO.getProvince());
}
// 解密加密信息获取unionID
if (!StringUtils.isBlank(encryptData)) {
JSONObject encryptedData = getEncryptedData(encryptData, sessionKey, iv);
if (encryptedData != null) {
String unionId = encryptedData.getString("unionId");
wxAppletUser.setUnionId(unionId);
}
}
return wxAppletUser;
}
/**
* 加密
*
* @param encryptedData 加密数据
* @param sessionKey session关键词
* @param iv 参数IV
* @return 返回加密结果
*/
private JSONObject getEncryptedData(String encryptedData, String sessionKey, String iv) {
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionKey);
// 偏移量
byte[] ivByte = Base64.decode(iv);
try {
// 如果密钥不足16位那么就补足.这个if中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + 1;
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, StandardCharsets.UTF_8);
return JSONObject.parseObject(result);
}
} catch (Exception e) {
log.error("解密加密信息报错 ==> {}", e.getMessage());
}
return null;
}
}

View File

@ -0,0 +1,6 @@
package cn.crtech.cloud.auth.service.wx.chat;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface WxChatUserService extends UserDetailsService {
}

View File

@ -0,0 +1,131 @@
package cn.crtech.cloud.auth.service.wx.chat;
import cn.crtech.cloud.auth.dto.MisComRoleUserDto;
import cn.crtech.cloud.auth.dto.MisComUserDto;
import cn.crtech.cloud.auth.mapper.AuthorizeMapper;
import cn.crtech.cloud.auth.mapper.WxMaAuthorizeMapper;
import cn.crtech.cloud.auth.pojo.*;
import cn.crtech.cloud.common.constant.MessageConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.CollectionUtils;
import java.util.*;
@Slf4j
@Configuration
public class WxChatUserServiceImpl implements WxChatUserService {
private AuthorizeMapper authorizeMapper;
private WxMaAuthorizeMapper wxMaAuthorizeMapper;
@Autowired
public WxChatUserServiceImpl(AuthorizeMapper authorizeMapper, WxMaAuthorizeMapper wxMaAuthorizeMapper) {
this.authorizeMapper = authorizeMapper;
this.wxMaAuthorizeMapper = wxMaAuthorizeMapper;
}
/**
* 重构微信用户登录内容
*
* @param username 登录账号
* @return 返回登录处理结果
* @throws UsernameNotFoundException 抛出异常
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 校验非空参数
if (StringUtils.isBlank(username)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_SIGNATURE_ERROR);
}
// 查询并校验对应微信用户是否存在
WxAppletUser selectWxAppletUser = wxMaAuthorizeMapper.getUserByWxOpenId(username);
if (ObjectUtils.isEmpty(selectWxAppletUser)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_ERROR);
}
// 查询并校验对应系统用户是否存在
MisUser user = authorizeMapper.queryUserByMobile(selectWxAppletUser.getMobile());
if (ObjectUtils.isEmpty(user)) {
throw new UsernameNotFoundException(MessageConstant.ACCOUNT_ERROR);
}
// 生成用户绑定企业信息相关查询参数
Map<String, Object> params = new HashMap<>();
params.put("userId", user.getId());
//生成认证用户信息
AuthorizeUser authorizeUser = new AuthorizeUser(user.getId(), user.getPassword(), user.getState(), user.getMobile(), user.getName(), user.getEmail(), "", false);
// getUserAuthorize(user, params, authorizeUser);
SecurityUser securityUser = new SecurityUser(authorizeUser);
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
}
if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
}
if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
}
if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
}
return securityUser;
}
/**
* 用户角色绑定信息补充
*
* @param user 用户对象
* @param params 参数对象
* @param authorizeUser 授权用户对象
*/
private void getUserAuthorize(User user, Map<String, Object> params, AuthorizeUser authorizeUser) {
List<MisComUserDto> comUserList = authorizeMapper.queryCompanyUserByUserId(params);
if (CollectionUtils.isEmpty(comUserList)) {
List<String> userRoleList = new ArrayList<>();
userRoleList.add("ALL");
authorizeUser.setRoles(userRoleList);
authorizeUser.setDataSource("");
} else {
// 获取当前企业
MisComUserDto defaultCompany = comUserList.get(0);
authorizeUser.setCompanyCode(defaultCompany.getCompanyCode());
authorizeUser.setCompanyName(defaultCompany.getCompanyName());
List<String> userRoleList = new ArrayList<>();
userRoleList.add("ALL");
// 设置企业管理员
if (defaultCompany.getIsOwner()) {
userRoleList.add(defaultCompany.getCompanyCode() + "_admin");
authorizeUser.setIsCompanyAdmin(true);
} else {
List<MisComRoleUserDto> userRoles = authorizeMapper.queryCompanyRoleUser(user.getId(), defaultCompany.getCompanyCode());
userRoles.forEach(role -> {
userRoleList.add(role.getCompanyCode() + "_" + role.getRoleId());
});
String roleCodes = authorizeMapper.queryUserRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(roleCodes)) {
String[] roleCodeArray = roleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
String defaultRoleCodes = authorizeMapper.queryUserDefaultRole(user.getId(), defaultCompany.getCompanyCode());
if (!StringUtils.isBlank(defaultRoleCodes)) {
String[] roleCodeArray = defaultRoleCodes.split(",");
userRoleList.addAll(Arrays.asList(roleCodeArray));
}
}
authorizeUser.setRoles(userRoleList);
}
}
}

View File

@ -0,0 +1,65 @@
package cn.crtech.cloud.auth.utils;
import cn.crtech.cloud.common.dto.Result;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.faceid.v20180301.FaceidClient;
import com.tencentcloudapi.faceid.v20180301.models.PhoneVerificationRequest;
import com.tencentcloudapi.faceid.v20180301.models.PhoneVerificationResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TencentUtils {
@Value("${tencent.release.mobile.secretId}")
private String secretId;
@Value("${tencent.release.mobile.secretKey}")
private String secretKey;
public Result releaseMobileVerify(String userName, String mobile, String idCard) {
try {
// 实例化一个认证对象入参需要传入腾讯云账户secretIdsecretKey,此处还需注意密钥对的保密
Credential cred = new Credential(secretId, secretKey);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("faceid.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
FaceidClient client = new FaceidClient(cred, "ap-guangzhou", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
PhoneVerificationRequest req = new PhoneVerificationRequest();
req.setName(userName);
req.setIdCard(idCard);
req.setPhone(mobile);
// 返回的resp是一个PhoneVerificationResponse的实例与请求对象对应
PhoneVerificationResponse resp = client.PhoneVerification(req);
// 输出json格式的字符串回包
log.info("三要素核验结果 ==> {} ", PhoneVerificationResponse.toJsonString(resp));
int result = Integer.parseInt(resp.getResult());
if (result == 0) {
//认证通过
return Result.success(null, resp.getDescription());
} else if (result == -4) {
return Result.error(null, "手机号码未匹配!");
} else {
return Result.error(null, resp.getDescription());
}
} catch (TencentCloudSDKException e) {
log.info("手机三要素核验异常,错误代码 ==> {} , 错误内容 ==> {}", e.getErrorCode(), e.getMessage());
return Result.error(null, e.getMessage());
}
}
}

View File

@ -0,0 +1,17 @@
package cn.crtech.cloud.auth.wx.applet;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WxAppletAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("token失效!");
}
}

View File

@ -0,0 +1,71 @@
package cn.crtech.cloud.auth.wx.applet;
import com.sun.istack.Nullable;
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 javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WxAppletAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 设置拦截/sms/login短信登录接口
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/wx/applet/oauth/token", "POST");
// 认证 请求参数
private static final String accessTokenParam = "openId";
private static final String companyCode = "companyCode";
private boolean postOnly = true;
public WxAppletAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (this.postOnly && !"POST".equals(request.getMethod())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String accessToken = this.obtainAccessToken(request);
accessToken = accessToken != null ? accessToken : "";
accessToken = accessToken.trim();
String companyCode = this.obtainCompanyCode(request);
companyCode = (companyCode != null) ? companyCode : "";
WxAppletAuthenticationToken authRequest = new WxAppletAuthenticationToken(accessToken, companyCode);
this.setDetails(request, authRequest);
// 认证信息
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected String obtainCompanyCode(HttpServletRequest request) {
return request.getParameter(this.companyCode);
}
@Nullable
protected String obtainAccessToken(HttpServletRequest request) {
return request.getParameter(this.accessTokenParam);
}
protected void setDetails(HttpServletRequest request, WxAppletAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.accessTokenParam;
}
public final String getPasswordParameter() {
return this.companyCode;
}
}

View File

@ -0,0 +1,50 @@
package cn.crtech.cloud.auth.wx.applet;
import cn.crtech.cloud.auth.service.wx.applet.WxAppletUserServiceImpl;
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.stereotype.Component;
@Component
public class WxAppletAuthenticationProvider implements AuthenticationProvider {
private final WxAppletUserServiceImpl wxAppletUserService;
public WxAppletAuthenticationProvider(WxAppletUserServiceImpl wxAppletUserServiceImpl) {
this.wxAppletUserService = wxAppletUserServiceImpl;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxAppletAuthenticationToken wxAppletAuthenticationToken = (WxAppletAuthenticationToken) authentication;
Object principal = authentication.getPrincipal();//token
Object credentials = authentication.getCredentials();
String accessToken = "";
if (principal instanceof String) {
accessToken = (String) principal;
}
String companyCode = "";
if (credentials != null) {
if (credentials instanceof String) {
companyCode = (String) credentials;
}
}
UserDetails userDetails = wxAppletUserService.loadUserByUsername(accessToken, companyCode);
if (userDetails == null) {
throw new InternalAuthenticationServiceException("认证失败!");
}
//重新创建已认证对象,
WxAppletAuthenticationToken authenticationResult = new WxAppletAuthenticationToken(userDetails, companyCode, userDetails.getAuthorities());
authenticationResult.setDetails(wxAppletAuthenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return WxAppletAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,66 @@
package cn.crtech.cloud.auth.wx.applet;
import cn.crtech.cloud.auth.component.JwtTokenEnhancer;
import cn.crtech.cloud.auth.pojo.Oauth2Token;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
@Component
public class WxAppletAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
ClientDetailsService clientDetailsService = (ClientDetailsService) factory.getBean("clientDetailsService");
AuthorizationServerTokenServices authorizationServerTokenServices = (AuthorizationServerTokenServices) factory.getBean("defaultAuthorizationServerTokenServices");
JwtTokenEnhancer jwtTokenEnhancer = (JwtTokenEnhancer) factory.getBean("jwtTokenEnhancer");
PasswordEncoder passwordEncoder = (PasswordEncoder) factory.getBean("passwordEncoder");
// 1. 从请求头中获取 ClientId
String clientId = request.getParameter("client_id");
String clientSecret = request.getParameter("client_secret");
// 2. 通过 ClientDetailsService 获取 ClientDetails
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
TokenRequest tokenRequest = null;
// 3. 校验 ClientId和 ClientSecret的正确性
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在");
} else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("clientSecret不正确");
} else {
// 4. 通过 TokenRequest构造器生成 TokenRequest
tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
}
// 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
// 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication
OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
// 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication);
OAuth2AccessToken resultToken = jwtTokenEnhancer.enhance(token, auth2Authentication);
Oauth2Token oauth2TokenDto = Oauth2Token.builder()
.token(resultToken.getValue())
.refreshToken(resultToken.getRefreshToken().getValue())
.expiresIn(resultToken.getExpiresIn())
.tokenHead("Bearer ").build();
// 8. 返回 Token
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(oauth2TokenDto));
}
}

View File

@ -0,0 +1,60 @@
package cn.crtech.cloud.auth.wx.applet;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class WxAppletAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
//小程序openId
private final Object principal;
//企业code
private Object credentials;
/**
* Creates a token with the supplied array of authorities.
*
* @param principal
*/
public WxAppletAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public WxAppletAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
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();
this.credentials = null;
}
}

View File

@ -0,0 +1,30 @@
package cn.crtech.cloud.auth.wx.applet;
import cn.crtech.cloud.auth.service.wx.applet.WxAppletUserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Component
public class WxAppletSecurityConfigurerConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private WxAppletUserServiceImpl wxAppletUserServiceImpl;
@Override
public void configure(HttpSecurity http) throws Exception {
//自定义SmsCodeAuthenticationFilter过滤器
WxAppletAuthenticationFilter wxAppletAuthenticationFilter = new WxAppletAuthenticationFilter();
wxAppletAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
wxAppletAuthenticationFilter.setAuthenticationSuccessHandler(new WxAppletAuthenticationSuccessHandler());
// 设置认证失败处理Handler
wxAppletAuthenticationFilter.setAuthenticationFailureHandler(new WxAppletAuthenticationFailureHandler());
WxAppletAuthenticationProvider wxAppletAuthenticationProvider = new WxAppletAuthenticationProvider(wxAppletUserServiceImpl);
//在UsernamePasswordAuthenticationFilter过滤前执行
http.authenticationProvider(wxAppletAuthenticationProvider)
.addFilterAfter(wxAppletAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -0,0 +1,17 @@
package cn.crtech.cloud.auth.wx.chat;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WxChatAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("token失效!");
}
}

View File

@ -0,0 +1,59 @@
package cn.crtech.cloud.auth.wx.chat;
import com.sun.istack.Nullable;
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 javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WxChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 设置拦截/sms/login短信登录接口
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/wx/chat/oauth/token", "POST");
// 认证 请求参数
private static final String accessTokenParam = "openId";
private boolean postOnly = true;
public WxChatAuthenticationFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (this.postOnly && !"POST".equals(request.getMethod())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String accessToken = this.obtainAccessToken(request);
accessToken = accessToken != null ? accessToken : "";
accessToken = accessToken.trim();
WxChatAuthenticationToken authRequest = new WxChatAuthenticationToken(accessToken);
this.setDetails(request, authRequest);
// 认证信息
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Nullable
protected String obtainAccessToken(HttpServletRequest request) {
return request.getParameter(this.accessTokenParam);
}
protected void setDetails(HttpServletRequest request, WxChatAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.accessTokenParam;
}
}

View File

@ -0,0 +1,44 @@
package cn.crtech.cloud.auth.wx.chat;
import cn.crtech.cloud.auth.service.wx.chat.WxChatUserServiceImpl;
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.stereotype.Component;
@Component
public class WxChatAuthenticationProvider implements AuthenticationProvider {
private final WxChatUserServiceImpl wxChatUserService;
public WxChatAuthenticationProvider(WxChatUserServiceImpl wxChatUserService) {
this.wxChatUserService = wxChatUserService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxChatAuthenticationToken wxAppletAuthenticationToken = (WxChatAuthenticationToken) authentication;
Object principal = authentication.getPrincipal();//openId
String accessToken = "";
if (principal instanceof String) {
accessToken = (String) principal;
}
UserDetails userDetails = wxChatUserService.loadUserByUsername(accessToken);
if (userDetails == null) {
throw new InternalAuthenticationServiceException("认证失败!");
}
//重新创建已认证对象,
WxChatAuthenticationToken authenticationResult = new WxChatAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(wxAppletAuthenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return WxChatAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,66 @@
package cn.crtech.cloud.auth.wx.chat;
import cn.crtech.cloud.auth.component.JwtTokenEnhancer;
import cn.crtech.cloud.auth.pojo.Oauth2Token;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
@Component
public class WxChatAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
ClientDetailsService clientDetailsService = (ClientDetailsService) factory.getBean("clientDetailsService");
AuthorizationServerTokenServices authorizationServerTokenServices = (AuthorizationServerTokenServices) factory.getBean("defaultAuthorizationServerTokenServices");
JwtTokenEnhancer jwtTokenEnhancer = (JwtTokenEnhancer) factory.getBean("jwtTokenEnhancer");
PasswordEncoder passwordEncoder = (PasswordEncoder) factory.getBean("passwordEncoder");
// 1. 从请求头中获取 ClientId
String clientId = request.getParameter("client_id");
String clientSecret = request.getParameter("client_secret");
// 2. 通过 ClientDetailsService 获取 ClientDetails
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
TokenRequest tokenRequest = null;
// 3. 校验 ClientId和 ClientSecret的正确性
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在");
} else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("clientSecret不正确");
} else {
// 4. 通过 TokenRequest构造器生成 TokenRequest
tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
}
// 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
// 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication
OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
// 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication);
OAuth2AccessToken resultToken = jwtTokenEnhancer.enhance(token, auth2Authentication);
Oauth2Token oauth2TokenDto = Oauth2Token.builder()
.token(resultToken.getValue())
.refreshToken(resultToken.getRefreshToken().getValue())
.expiresIn(resultToken.getExpiresIn())
.tokenHead("Bearer ").build();
// 8. 返回 Token
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(oauth2TokenDto));
}
}

View File

@ -0,0 +1,55 @@
package cn.crtech.cloud.auth.wx.chat;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class WxChatAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
//openId
private final Object principal;
/**
* Creates a token with the supplied array of authorities.
*
* @param principal
*/
public WxChatAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
public WxChatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
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();
}
}

View File

@ -0,0 +1,30 @@
package cn.crtech.cloud.auth.wx.chat;
import cn.crtech.cloud.auth.service.wx.chat.WxChatUserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Component
public class WxChatSecurityConfigurerConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private WxChatUserServiceImpl wxChatUserService;
@Override
public void configure(HttpSecurity http) throws Exception {
//自定义SmsCodeAuthenticationFilter过滤器
WxChatAuthenticationFilter wxAppletAuthenticationFilter = new WxChatAuthenticationFilter();
wxAppletAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
wxAppletAuthenticationFilter.setAuthenticationSuccessHandler(new WxChatAuthenticationSuccessHandler());
// 设置认证失败处理Handler
wxAppletAuthenticationFilter.setAuthenticationFailureHandler(new WxChatAuthenticationFailureHandler());
WxChatAuthenticationProvider wxAppletAuthenticationProvider = new WxChatAuthenticationProvider(wxChatUserService);
//在UsernamePasswordAuthenticationFilter过滤前执行
http.authenticationProvider(wxAppletAuthenticationProvider)
.addFilterAfter(wxAppletAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -0,0 +1,28 @@
server:
port: 9401
servlet:
encoding:
charset: utf-8
enabled: true
force: true
tomcat:
uri-encoding: UTF-8
management:
endpoints:
web:
exposure:
include: "*"
mybatis:
mapper-locations: classpath*:/mapping/*Mapper.xml
type-aliases-package: cn.crtech.cloud.auth.pojo,cn.crtech.cloud.dto
logging:
config: classpath:logback.xml
file:
path: logs/crtech-cloud-auth.log
level:
cn.crtech.cloud.auth: debug
tencent:
release:
mobile:
secretId: AKIDZ5PL3jgQFnshIoD6wpPr32vaptkHDsmY
secretKey: mQu42mHSP4hvsgXAVbYbIgnsC7l8NZLv

View File

@ -0,0 +1,17 @@
spring:
application:
name: crtech-cloud-auth # 项目名称尽量用小写
cloud:
nacos:
discovery:
server-addr: localhost:8848
redis:
database: 4
port: 6379
host: localhost
password:
datasource:
url: jdbc:mysql://chaoran.crtech.cn:9803/cr_cloud_auth_back?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: crtech
password: www.server41.com

View File

@ -0,0 +1,6 @@
spring:
profiles:
active: dev

Binary file not shown.

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/crtech-cloud-auth.%d{yyyy-MM-dd}.log
</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd_HH:mm:ss} %logger{18} -%msg%n
</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>1000</queueSize>
<appender-ref ref="FILE" />
</appender>
<logger name="org" level="info" additivity="false">
<appender-ref ref="FILE"></appender-ref>
<appender-ref ref="STDOUT"></appender-ref>
</logger>
<logger name="com" level="info" additivity="false">
<appender-ref ref="FILE"></appender-ref>
<appender-ref ref="STDOUT"></appender-ref>
</logger>
<logger name="net" level="info" additivity="false">
<appender-ref ref="FILE"></appender-ref>
<appender-ref ref="STDOUT"></appender-ref>
</logger>
<logger name="com.netflix" level="debug" additivity="false">
<appender-ref ref="STDOUT"></appender-ref>
<appender-ref ref="FILE"></appender-ref>
</logger>
<logger name="cn.crtech.cloud.auth" level="debug" additivity="false">
<appender-ref ref="STDOUT"></appender-ref>
<appender-ref ref="FILE"></appender-ref>
</logger>
<root level="INFO">
<appender-ref ref="ASYNC" />
</root>
</configuration>