redisTemplate;
+
+
+ public boolean deleteKey(String key) {
+ return redisTemplate.delete(key);
+ }
+
+ public void setValue(String key, Object object, Long expire) {
+ redisTemplate.opsForValue().set(key, object, expire);
+ }
+
+}
diff --git a/summer-ospp/2023/pig/pig-auth/src/main/java/com/pig4cloud/pig/auth/utils/TokenManager.java b/summer-ospp/2023/pig/pig-auth/src/main/java/com/pig4cloud/pig/auth/utils/TokenManager.java
new file mode 100644
index 000000000..6a2c1ada1
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-auth/src/main/java/com/pig4cloud/pig/auth/utils/TokenManager.java
@@ -0,0 +1,36 @@
+package com.pig4cloud.pig.auth.utils;
+
+import io.jsonwebtoken.CompressionCodecs;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+@Component
+public class TokenManager {
+ private long tokenExpiration = 24 * 60 * 60 * 1000;
+ private final static String TOKEN_SIGN_KEY = "MAKKEY_PIG";
+
+ public String createToken(String subject, String username, String id) {
+ String token = Jwts.builder()
+ .setSubject(subject)
+ .claim("nickname", username)
+ .claim("id", id)
+ .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
+ .signWith(SignatureAlgorithm.HS512, TOKEN_SIGN_KEY)
+ .compressWith(CompressionCodecs.GZIP).compact();
+ return token;
+ }
+
+
+ public String getUserFromToken(String token) {
+ String user = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token).getBody().getSubject();
+ return user;
+ }
+
+ public void removeToken(String token) {
+ //jwttoken无需删除,客户端扔掉即可。
+ }
+
+}
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/pom.xml b/summer-ospp/2023/pig/pig-common/pig-common-security/pom.xml
new file mode 100644
index 000000000..7045c8287
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pig-common
+ 3.6.7
+
+
+ pig-common-security
+ jar
+
+ pig 安全工具类
+
+
+
+
+ org.springframework.session
+ spring-session-data-redis
+
+
+
+ org.springframework.security
+ spring-security-cas
+
+
+
+ com.pig4cloud
+ pig-common-core
+
+
+ cn.hutool
+ hutool-extra
+
+
+
+ com.pig4cloud
+ pig-upms-api
+
+
+
+ org.springframework.cloud
+ spring-cloud-commons
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+ org.springframework.security
+ spring-security-oauth2-jose
+
+
+ org.springframework.security
+ spring-security-oauth2-authorization-server
+ ${spring.authorization.version}
+
+
+ org.springframework
+ spring-webmvc
+
+
+
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/EnablePigResourceServer.java b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/EnablePigResourceServer.java
new file mode 100644
index 000000000..7ee86239a
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/EnablePigResourceServer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.security.annotation;
+
+import com.pig4cloud.pig.common.security.component.PigResourceServerAutoConfiguration;
+import com.pig4cloud.pig.common.security.component.PigResourceServerConfiguration;
+import com.pig4cloud.pig.common.security.component.ResourceAuthExceptionEntryPoint;
+import com.pig4cloud.pig.common.security.config.*;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author lengleng
+ * @date 2022-06-04
+ *
+ * 资源服务注解
+ */
+@Documented
+@Inherited
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+//@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class })
+@Import({PigResourceServerAutoConfiguration.class, CasProperties.class, SecurityConfig.class})
+public @interface EnablePigResourceServer {
+
+}
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/CasProperties.java b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/CasProperties.java
new file mode 100644
index 000000000..1eda0a6f0
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/CasProperties.java
@@ -0,0 +1,59 @@
+package com.pig4cloud.pig.common.security.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+public class CasProperties {
+
+ /**
+ * 秘钥
+ */
+ @Value("${cas.key}")
+ private String casKey;
+
+ /**
+ * cas服务端地址
+ */
+ @Value("${cas.server.host.url}")
+ private String casServerUrl;
+
+ /**
+ * cas服务端地址
+ */
+ @Value("${cas.server.host.grant_url}")
+ private String casGrantingUrl;
+
+ /**
+ * cas服务端登录地址
+ */
+ @Value("${cas.server.host.login_url}")
+ private String casServerLoginUrl;
+
+ /**
+ * cas服务端登出地址 并回跳到制定页面
+ */
+ @Value("${cas.server.host.logout_url}")
+ private String casServerLogoutUrl;
+
+ /**
+ * cas客户端地址
+ */
+ @Value("${cas.service.host.url}")
+ private String casServiceUrl;
+
+ /**
+ * cas客户端地址登录地址
+ */
+ @Value("${cas.service.host.login_url}")
+ private String casServiceLoginUrl;
+
+ /**
+ * cas客户端地址登出地址
+ */
+ @Value("${cas.service.host.logout_url}")
+ private String casServiceLogoutUrl;
+
+}
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/SecurityConfig.java b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/SecurityConfig.java
new file mode 100644
index 000000000..8ed246682
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/config/SecurityConfig.java
@@ -0,0 +1,156 @@
+package com.pig4cloud.pig.common.security.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.pig4cloud.pig.common.security.component.PermitAllUrlProperties;
+import com.pig4cloud.pig.common.security.component.PigBearerTokenExtractor;
+import com.pig4cloud.pig.common.security.component.ResourceAuthExceptionEntryPoint;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jasig.cas.client.session.SingleSignOutFilter;
+import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.cas.ServiceProperties;
+import org.springframework.security.cas.authentication.CasAuthenticationProvider;
+import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
+import org.springframework.security.cas.web.CasAuthenticationFilter;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+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.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+
+@Slf4j
+@Configuration
+@EnableWebSecurity // 启用web权限
+@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法验证
+@RequiredArgsConstructor
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private CasProperties casProperties;
+
+ @Autowired
+ private AuthenticationUserDetailsService casUserDetailService;
+
+ private final PermitAllUrlProperties permitAllUrl;
+
+
+ /**
+ * 定义认证用户信息获取来源,密码校验规则等
+ */
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ super.configure(auth);
+ auth.authenticationProvider(casAuthenticationProvider());
+ }
+
+ /**
+ * 定义安全策略
+ */
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http.authorizeRequests()// 配置安全策略
+ .antMatchers(ArrayUtil.toArray(permitAllUrl.getUrls(), String.class)).permitAll()
+ .anyRequest().authenticated()// 其余的所有请求都需要验证
+ .and().logout().permitAll()// 定义logout不需要验证
+ .and().formLogin();// 使用form表单登录
+
+ http.exceptionHandling()
+ .authenticationEntryPoint(casAuthenticationEntryPoint())
+ .and()
+ .addFilter(casAuthenticationFilter())
+ .addFilterBefore(casLogoutFilter(), LogoutFilter.class)
+ .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);
+ // 取消跨站请求伪造防护
+ http.csrf().disable();
+// // 防止iframe 造成跨域
+ http.headers().frameOptions().disable();
+ // http.csrf().disable(); //禁用CSRF
+ }
+
+ /**
+ * 认证的入口
+ */
+ @Bean
+ public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
+ CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
+ casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
+ casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
+ return casAuthenticationEntryPoint;
+ }
+
+ /**
+ * 指定service相关信息
+ */
+ @Bean
+ public ServiceProperties serviceProperties() {
+ ServiceProperties serviceProperties = new ServiceProperties();
+ //设置cas客户端登录完整的url
+ serviceProperties.setService(casProperties.getCasServiceUrl() + casProperties.getCasServiceLoginUrl());
+ serviceProperties.setSendRenew(false);
+ serviceProperties.setAuthenticateAllArtifacts(true);
+ return serviceProperties;
+ }
+
+ /**
+ * CAS认证过滤器
+ */
+ @Bean
+ public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
+ CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
+ casAuthenticationFilter.setAuthenticationManager(authenticationManager());
+ casAuthenticationFilter.setFilterProcessesUrl(casProperties.getCasServiceUrl() + casProperties.getCasServiceLoginUrl());
+ casAuthenticationFilter.setServiceProperties(serviceProperties());
+ return casAuthenticationFilter;
+ }
+
+ /**
+ * cas 认证 Provider
+ */
+ @Bean
+ public CasAuthenticationProvider casAuthenticationProvider() {
+ CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
+ casAuthenticationProvider.setAuthenticationUserDetailsService(casUserDetailService);
+ // //这里只是接口类型,实现的接口不一样,都可以的。
+ casAuthenticationProvider.setServiceProperties(serviceProperties());
+ casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
+ casAuthenticationProvider.setKey("casAuthenticationProviderKey");
+ return casAuthenticationProvider;
+ }
+
+
+
+
+ @Bean
+ public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
+ return new Cas20ServiceTicketValidator(casProperties.getCasGrantingUrl());
+ }
+
+ /**
+ * 单点登出过滤器
+ */
+ @Bean
+ public SingleSignOutFilter singleSignOutFilter() {
+ SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
+ singleSignOutFilter.setLogoutCallbackPath(casProperties.getCasServerUrl());
+ singleSignOutFilter.setIgnoreInitConfiguration(true);
+ return singleSignOutFilter;
+ }
+
+ /**
+ * 请求单点退出过滤器
+ */
+ @Bean
+ public LogoutFilter casLogoutFilter() {
+ LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());
+ logoutFilter.setFilterProcessesUrl(casProperties.getCasServiceLogoutUrl());
+ return logoutFilter;
+ }
+}
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsServiceImpl.java b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsServiceImpl.java
new file mode 100644
index 000000000..35f8b1b40
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsServiceImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.security.service;
+
+import com.pig4cloud.pig.admin.api.dto.UserInfo;
+import com.pig4cloud.pig.admin.api.feign.RemoteUserService;
+import com.pig4cloud.pig.common.core.constant.CacheConstants;
+import com.pig4cloud.pig.common.core.util.R;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Primary;
+import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import java.util.*;
+
+/**
+ * 用户详细信息
+ *
+ * @author lengleng hccake
+ */
+@Slf4j
+@Primary
+@RequiredArgsConstructor
+public class PigUserDetailsServiceImpl implements PigUserDetailsService, AuthenticationUserDetailsService {
+
+ private final RemoteUserService remoteUserService;
+
+ private final CacheManager cacheManager;
+
+ /**
+ * 用户名密码登录
+ *
+ * @param username 用户名
+ * @return
+ */
+ @Override
+ @SneakyThrows
+ public UserDetails loadUserByUsername(String username) {
+ Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
+ if (cache != null && cache.get(username) != null) {
+ return (PigUser) cache.get(username).get();
+ }
+ return getUserDetails(remoteUserService.info(username), username);
+ }
+
+ @Override
+ public int getOrder() {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
+ log.info("getCredentials:{}", token.getCredentials());
+ String username = token.getName();
+ Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
+ if (cache != null && cache.get(username) != null) {
+ return (PigUser) cache.get(username).get();
+ }
+ R result = remoteUserService.saveIfNotExist(token.getAssertion().getPrincipal().getAttributes());
+ return getUserDetails(result, username);
+ }
+
+ private UserDetails getUserDetails(R result, String username) {
+ Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
+ UserDetails userDetails = getUserDetails(result);
+ if (cache != null) {
+ cache.put(username, userDetails);
+ }
+ return userDetails;
+ }
+
+
+}
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..41365bcb5
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,8 @@
+com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl
+com.pig4cloud.pig.common.security.service.PigAppUserDetailsServiceImpl
+com.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationService
+com.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationConsentService
+com.pig4cloud.pig.common.security.component.PigSecurityInnerAspect
+com.pig4cloud.pig.common.security.component.PigSecurityMessageSourceConfiguration
+com.pig4cloud.pig.common.security.service.PigRemoteRegisteredClientRepository
+
diff --git a/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/i18n/errors/messages_zh_CN.properties b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/i18n/errors/messages_zh_CN.properties
new file mode 100644
index 000000000..86668a5d8
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pig-common-security/src/main/resources/i18n/errors/messages_zh_CN.properties
@@ -0,0 +1,50 @@
+AbstractAccessDecisionManager.accessDenied=\u4E0D\u5141\u8BB8\u8BBF\u95EE
+AbstractLdapAuthenticationProvider.emptyPassword=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61
+OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired=\u8BF7\u6C42\u4EE4\u724C\u5DF2\u8FC7\u671F
+AbstractUserDetailsAuthenticationProvider.smsBadCredentials=\u624B\u673A\u53F7\u4E0D\u5B58\u5728\u767B\u5F55\u5931\u8D25
+AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
+AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
+AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
+AbstractUserDetailsAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
+AbstractUserDetailsAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
+AccountStatusUserDetailsChecker.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
+AccountStatusUserDetailsChecker.disabled=\u7528\u6237\u5DF2\u5931\u6548
+AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
+AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
+AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1})
+AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
+BindAuthenticator.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+BindAuthenticator.emptyPassword=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
+CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839
+ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u5DF2\u7ECF\u8D85\u8FC7\u4E86\u5F53\u524D\u4E3B\u4F53({0})\u88AB\u5141\u8BB8\u7684\u6700\u5927\u4F1A\u8BDD\u6570\u91CF
+DigestAuthenticationFilter.incorrectRealm=\u54CD\u5E94\u7ED3\u679C\u4E2D\u7684Realm\u540D\u5B57({0})\u540C\u7CFB\u7EDF\u6307\u5B9A\u7684Realm\u540D\u5B57({1})\u4E0D\u543B\u5408
+DigestAuthenticationFilter.incorrectResponse=\u9519\u8BEF\u7684\u54CD\u5E94\u7ED3\u679C
+DigestAuthenticationFilter.missingAuth=\u9057\u6F0F\u4E86\u9488\u5BF9'auth' QOP\u7684\u3001\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
+DigestAuthenticationFilter.missingMandatory=\u9057\u6F0F\u4E86\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0}
+DigestAuthenticationFilter.nonceCompromised=Nonce\u4EE4\u724C\u5DF2\u7ECF\u5B58\u5728\u95EE\u9898\u4E86\uFF0C{0}
+DigestAuthenticationFilter.nonceEncoding=Nonce\u672A\u7ECF\u8FC7Base64\u7F16\u7801; \u76F8\u5E94\u7684nonce\u53D6\u503C\u4E3A {0}
+DigestAuthenticationFilter.nonceExpired=Nonce\u5DF2\u7ECF\u8FC7\u671F/\u8D85\u65F6
+DigestAuthenticationFilter.nonceNotNumeric=Nonce\u4EE4\u724C\u7684\u7B2C1\u90E8\u5206\u5E94\u8BE5\u662F\u6570\u5B57\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
+DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8\u5206\u53D6\u503C\u6784\u6210\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0}
+DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230
+ExceptionTranslationFilter.insufficientAuthentication=\u8BBF\u95EE\u6B64\u8D44\u6E90\u9700\u8981\u5B8C\u5168\u8EAB\u4EFD\u9A8C\u8BC1
+JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272
+JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0}
+LdapAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F
+LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548
+LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F
+LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A
+LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A
+LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken
+PasswordComparisonAuthenticator.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
+#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
+ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider
+RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
+RunAsImplAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684RunAsUserToken\u4E0D\u542B\u6709\u9884\u671F\u7684key
+SubjectDnX509PrincipalExtractor.noMatching=\u672A\u5728subjectDN\: {0}\u4E2D\u627E\u5230\u5339\u914D\u7684\u6A21\u5F0F
+SwitchUserFilter.noCurrentUser=\u4E0D\u5B58\u5728\u5F53\u524D\u7528\u6237
+SwitchUserFilter.noOriginalAuthentication=\u4E0D\u80FD\u591F\u67E5\u627E\u5230\u539F\u5148\u7684\u5DF2\u8BA4\u8BC1\u5BF9\u8C61
diff --git a/summer-ospp/2023/pig/pig-common/pom.xml b/summer-ospp/2023/pig/pig-common/pom.xml
new file mode 100644
index 000000000..15908dee5
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-common/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pig
+ 3.6.7
+
+
+ pig-common
+ pom
+
+ pig 公共聚合模块
+
+
+ pig-common-bom
+ pig-common-core
+ pig-common-datasource
+ pig-common-job
+ pig-common-log
+ pig-common-mybatis
+ pig-common-seata
+ pig-common-security
+ pig-common-feign
+ pig-common-swagger
+ pig-common-xss
+
+
diff --git a/summer-ospp/2023/pig/pig-upms/pig-upms-biz/pom.xml b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/pom.xml
new file mode 100644
index 000000000..3a5deef56
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/pom.xml
@@ -0,0 +1,135 @@
+
+
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pig-upms
+ 3.6.7
+
+
+ pig-upms-biz
+ jar
+
+ pig 通用用户权限管理系统业务处理模块
+
+
+
+ com.google.guava
+ guava
+ 29.0-jre
+
+
+
+ com.pig4cloud
+ pig-upms-api
+
+
+
+ com.pig4cloud.plugin
+ oss-spring-boot-starter
+
+
+
+ com.pig4cloud
+ pig-common-feign
+
+
+
+ com.pig4cloud
+ pig-common-security
+
+
+
+ com.pig4cloud
+ pig-common-log
+
+
+
+ com.pig4cloud
+ pig-common-swagger
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ io.springboot.sms
+ aliyun-sms-spring-boot-starter
+
+
+
+ com.pig4cloud
+ pig-common-xss
+
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+
+ src/main/resources
+ true
+
+ **/*.xlsx
+ **/*.xls
+
+
+
+ src/main/resources
+ false
+
+ **/*.xlsx
+ **/*.xls
+
+
+
+
+
+
diff --git a/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysUserController.java b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysUserController.java
new file mode 100644
index 000000000..670f47896
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysUserController.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.admin.controller;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Lists;
+import com.pig4cloud.pig.admin.api.dto.UserDTO;
+import com.pig4cloud.pig.admin.api.dto.UserInfo;
+import com.pig4cloud.pig.admin.api.entity.SysUser;
+import com.pig4cloud.pig.admin.api.vo.UserExcelVO;
+import com.pig4cloud.pig.admin.api.vo.UserInfoVO;
+import com.pig4cloud.pig.admin.api.vo.UserVO;
+import com.pig4cloud.pig.admin.service.SysUserService;
+import com.pig4cloud.pig.admin.utils.BeanCreator;
+import com.pig4cloud.pig.common.core.exception.ErrorCodes;
+import com.pig4cloud.pig.common.core.util.MsgUtils;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.common.log.annotation.SysLog;
+import com.pig4cloud.pig.common.security.annotation.Inner;
+import com.pig4cloud.pig.common.security.util.SecurityUtils;
+import com.pig4cloud.pig.common.xss.core.XssCleanIgnore;
+import com.pig4cloud.plugin.excel.annotation.RequestExcel;
+import com.pig4cloud.plugin.excel.annotation.ResponseExcel;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lengleng
+ * @date 2019/2/1
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/user")
+@Tag(name = "用户管理模块")
+@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
+public class SysUserController {
+
+ private final SysUserService userService;
+
+ /**
+ * 获取当前用户全部信息
+ *
+ * @return 用户信息
+ */
+ @GetMapping(value = {"/info"})
+ public R info() {
+ String username = SecurityUtils.getUser().getUsername();
+ SysUser user = userService.getOne(Wrappers.query().lambda().eq(SysUser::getUsername, username));
+ if (user == null) {
+ return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_QUERY_ERROR));
+ }
+ UserInfo userInfo = userService.getUserInfo(user);
+ UserInfoVO vo = new UserInfoVO();
+ vo.setSysUser(userInfo.getSysUser());
+ vo.setRoles(userInfo.getRoles());
+ vo.setPermissions(userInfo.getPermissions());
+ return R.ok(vo);
+ }
+
+ /**
+ * 获取指定用户全部信息
+ *
+ * @return 用户信息
+ */
+ @Inner
+ @GetMapping("/info/{username}")
+ public R info(@PathVariable String username) {
+ SysUser user = userService.getOne(Wrappers.query().lambda().eq(SysUser::getUsername, username));
+ if (user == null) {
+ return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERINFO_EMPTY, username));
+ }
+ return R.ok(userService.getUserInfo(user));
+ }
+
+ @Inner
+ @PostMapping("/sso_save")
+ public R save_sso(@RequestBody Map attributes) {
+ String username = (String) attributes.getOrDefault("username", "pig");
+ // 判断用户名是否存在
+ SysUser sysUser = userService.getOne(Wrappers.lambdaQuery().eq(SysUser::getUsername, username));
+ if (sysUser == null) {
+ SysUser sysUserByMap = BeanCreator.createSysUserByMap(attributes);
+ UserDTO userDTO = new UserDTO();
+ BeanUtils.copyProperties(sysUserByMap,userDTO);
+
+ userDTO.setPost(Lists.newArrayList(1l));
+ userDTO.setDeptId(6l);
+ userDTO.setRole(Lists.newArrayList(2l));
+ // 添加用户
+ userService.saveUser(userDTO);
+ }
+ return info(username);
+ }
+
+ /**
+ * 根据部门id,查询对应的用户 id 集合
+ *
+ * @param deptIds 部门id 集合
+ * @return 用户 id 集合
+ */
+ @Inner
+ @GetMapping("/ids")
+ public R> listUserIdByDeptIds(@RequestParam("deptIds") Set deptIds) {
+ return R.ok(userService.listUserIdByDeptIds(deptIds));
+ }
+
+ /**
+ * 通过ID查询用户信息
+ *
+ * @param id ID
+ * @return 用户信息
+ */
+ @GetMapping("/{id:\\d+}")
+ public R user(@PathVariable Long id) {
+ return R.ok(userService.getUserVoById(id));
+ }
+
+ /**
+ * 判断用户是否存在
+ *
+ * @param userDTO 查询条件
+ * @return
+ */
+ @Inner(false)
+ @GetMapping("/check/exist")
+ public R isExist(UserDTO userDTO) {
+ List sysUserList = userService.list(new QueryWrapper<>(userDTO));
+ if (CollUtil.isNotEmpty(sysUserList)) {
+ return R.ok(Boolean.TRUE, MsgUtils.getMessage(ErrorCodes.SYS_USER_EXISTING));
+ }
+ return R.ok(Boolean.FALSE);
+ }
+
+ /**
+ * 删除用户信息
+ *
+ * @param id ID
+ * @return R
+ */
+ @SysLog("删除用户信息")
+ @DeleteMapping("/{id:\\d+}")
+ @PreAuthorize("@pms.hasPermission('sys_user_del')")
+ public R userDel(@PathVariable Long id) {
+ SysUser sysUser = userService.getById(id);
+ return R.ok(userService.removeUserById(sysUser));
+ }
+
+ /**
+ * 添加用户
+ *
+ * @param userDto 用户信息
+ * @return success/false
+ */
+ @SysLog("添加用户")
+ @PostMapping
+ @XssCleanIgnore({"password"})
+ @PreAuthorize("@pms.hasPermission('sys_user_add')")
+ public R user(@RequestBody UserDTO userDto) {
+ return R.ok(userService.saveUser(userDto));
+ }
+
+ /**
+ * 管理员更新用户信息
+ *
+ * @param userDto 用户信息
+ * @return R
+ */
+ @SysLog("更新用户信息")
+ @PutMapping
+ @XssCleanIgnore({"password"})
+ @PreAuthorize("@pms.hasPermission('sys_user_edit')")
+ public R updateUser(@Valid @RequestBody UserDTO userDto) {
+ return userService.updateUser(userDto);
+ }
+
+ /**
+ * 分页查询用户
+ *
+ * @param page 参数集
+ * @param userDTO 查询参数列表
+ * @return 用户集合
+ */
+ @GetMapping("/page")
+ public R> getUserPage(Page page, UserDTO userDTO) {
+ return R.ok(userService.getUserWithRolePage(page, userDTO));
+ }
+
+ /**
+ * 个人修改个人信息
+ *
+ * @param userDto userDto
+ * @return success/false
+ */
+ @SysLog("修改个人信息")
+ @PutMapping("/edit")
+ @XssCleanIgnore({"password", "newpassword1"})
+ public R updateUserInfo(@Valid @RequestBody UserDTO userDto) {
+ userDto.setUsername(SecurityUtils.getUser().getUsername());
+ return userService.updateUserInfo(userDto);
+ }
+
+ /**
+ * @param username 用户名称
+ * @return 上级部门用户列表
+ */
+ @GetMapping("/ancestor/{username}")
+ public R> listAncestorUsers(@PathVariable String username) {
+ return R.ok(userService.listAncestorUsersByUsername(username));
+ }
+
+ /**
+ * 导出excel 表格
+ *
+ * @param userDTO 查询条件
+ * @return
+ */
+ @ResponseExcel
+ @GetMapping("/export")
+ @PreAuthorize("@pms.hasPermission('sys_user_import_export')")
+ public List export(UserDTO userDTO) {
+ return userService.listUser(userDTO);
+ }
+
+ /**
+ * 导入用户
+ *
+ * @param excelVOList 用户列表
+ * @param bindingResult 错误信息列表
+ * @return R
+ */
+ @PostMapping("/import")
+ @PreAuthorize("@pms.hasPermission('sys_user_import_export')")
+ public R importUser(@RequestExcel List excelVOList, BindingResult bindingResult) {
+ return userService.importUser(excelVOList, bindingResult);
+ }
+
+}
diff --git a/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/utils/BeanCreator.java b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/utils/BeanCreator.java
new file mode 100644
index 000000000..e0ea3dca6
--- /dev/null
+++ b/summer-ospp/2023/pig/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/utils/BeanCreator.java
@@ -0,0 +1,23 @@
+package com.pig4cloud.pig.admin.utils;
+
+import com.pig4cloud.pig.admin.api.entity.SysUser;
+
+import java.util.Map;
+
+public class BeanCreator {
+
+ private static final String DEFAULT_PASSWORD = "pigmax123456";
+
+ public static SysUser createSysUserByMap(Map map) {
+ SysUser sysUser = new SysUser();
+ String username = (String) map.get("username");
+ String phone = (String) map.get("mobile");
+ String deptId = (String) map.get("departmentId");
+ sysUser.setUsername(username);
+ sysUser.setPhone(phone);
+ sysUser.setDeptId(Long.parseLong(deptId));
+ sysUser.setPassword(DEFAULT_PASSWORD);
+ return sysUser;
+ }
+
+}
diff --git a/summer-ospp/2023/pig/vue/src/api/login.js b/summer-ospp/2023/pig/vue/src/api/login.js
new file mode 100644
index 000000000..62f9b69d4
--- /dev/null
+++ b/summer-ospp/2023/pig/vue/src/api/login.js
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng (wangiegie@gmail.com)
+ */
+import { validatenull } from '@/util/validate'
+import request from '@/router/axios'
+import store from '@/store'
+import qs from 'qs'
+import { getStore, setStore } from '@/util/store.js'
+import website from '@/config/website'
+
+
+const scope = 'server'
+
+export const loginByUsername = (username, password, code, randomStr) => {
+ const grant_type = 'password'
+ const dataObj = qs.stringify({ 'username': username, 'password': password })
+
+ const basicAuth = 'Basic ' + window.btoa(website.formLoginClient)
+
+ // 保存当前选中的 basic 认证信息
+ setStore({
+ name: 'basicAuth',
+ content: basicAuth,
+ type: 'session'
+ })
+
+ return request({
+ url: '/auth/oauth2/token',
+ headers: {
+ isToken: false,
+ Authorization: basicAuth
+ },
+ method: 'post',
+ params: { randomStr, code, grant_type, scope },
+ data: dataObj
+ })
+}
+
+export const loginByMobile = (smsForm) => {
+ const grant_type = 'app'
+
+ const basicAuth = 'Basic ' + window.btoa(website.smsLoginClient)
+
+ // 保存当前选中的 basic 认证信息
+ setStore({
+ name: 'basicAuth',
+ content: basicAuth,
+ type: 'session'
+ })
+
+ return request({
+ url: '/auth/oauth2/token',
+ headers: {
+ isToken: false,
+ 'Authorization': basicAuth
+ },
+ method: 'post',
+ params: { phone: smsForm.phone, code: smsForm.code, grant_type, scope }
+ })
+}
+
+export const ssoLogin = (ticket,service) => {
+ return request({
+ url: '/auth/token/sso_login_get_token',
+ method: 'get',
+ params: { ticket, service }
+ })
+}
+
+export const refreshToken = refresh_token => {
+ const grant_type = 'refresh_token'
+ // 获取当前选中的 basic 认证信息
+ const basicAuth = getStore({ name: 'basicAuth' })
+
+ return request({
+ url: '/auth/oauth2/token',
+ headers: {
+ isToken: false,
+ Authorization: basicAuth
+ },
+ method: 'post',
+ params: { refresh_token, grant_type, scope }
+ })
+}
+
+export const getUserInfo = () => {
+ return request({
+ url: '/admin/user/info',
+ method: 'get'
+ })
+}
+
+export const logout = () => {
+ return request({
+ url: '/auth/token/logout',
+ method: 'delete'
+ })
+}
+
+/**
+ * 校验令牌,若有效期小于半小时自动续期
+ *
+ * 定时任务请求后端接口返回实际的有效时间,不进行本地计算避免 客户端和服务器机器时钟不一致
+ * @param refreshLock
+ */
+export const checkToken = (refreshLock, $store) => {
+ const token = store.getters.access_token
+ // 获取当前选中的 basic 认证信息
+ const basicAuth = getStore({ name: 'basicAuth' })
+
+ if (validatenull(token) || validatenull(basicAuth)) {
+ return
+ }
+
+ request({
+ url: '/auth/token/check_token',
+ headers: {
+ isToken: false,
+ Authorization: basicAuth
+ },
+ method: 'get',
+ params: { token }
+ }).then(response => {
+ const expire = response && response.data && response.data.exp
+ if (expire) {
+ const expiredPeriod = expire * 1000 - new Date().getTime()
+ console.log('当前token过期时间', expiredPeriod, '毫秒')
+ //小于半小时自动续约
+ if (expiredPeriod <= website.remainingTime) {
+ if (!refreshLock) {
+ refreshLock = true
+ $store.dispatch('RefreshToken')
+ .catch(() => {
+ clearInterval(this.refreshTime)
+ })
+ refreshLock = false
+ }
+ }
+ }
+ }).catch(error => {
+ console.error(error)
+ })
+}
+
+/**
+ * 注册用户
+ */
+export const registerUser = (userInfo) => {
+ return request({
+ url: '/admin/register/user',
+ method: 'post',
+ data: userInfo
+ })
+}
+
+
+/**
+ * 发送短信
+ */
+export const sendSmsCode = (form) => {
+ return request({
+ url: '/admin/app/sms',
+ method: 'post',
+ data: form
+ })
+}
diff --git a/summer-ospp/2023/pig/vue/src/page/login/userlogin.vue b/summer-ospp/2023/pig/vue/src/page/login/userlogin.vue
new file mode 100644
index 000000000..09036637d
--- /dev/null
+++ b/summer-ospp/2023/pig/vue/src/page/login/userlogin.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ code.value }}
+
![]()
+
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
diff --git a/summer-ospp/2023/pig/vue/src/router/axios.js b/summer-ospp/2023/pig/vue/src/router/axios.js
new file mode 100644
index 000000000..5ea468c8c
--- /dev/null
+++ b/summer-ospp/2023/pig/vue/src/router/axios.js
@@ -0,0 +1,99 @@
+
+import axios from 'axios'
+import { serialize } from '@/util'
+import NProgress from 'nprogress' // progress bar
+import errorCode from '@/const/errorCode'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import 'nprogress/nprogress.css'
+import qs from 'qs'
+import store from '@/store'
+import router from '@/router/index.js'
+import { baseUrl } from '@/config/env' // progress bar style
+axios.defaults.timeout = 30000
+// 返回其他状态吗
+axios.defaults.validateStatus = function(status) {
+ return status >= 200 && status <= 500 // 默认的
+}
+// 跨域请求,允许保存cookie
+axios.defaults.withCredentials = true
+// NProgress Configuration
+NProgress.configure({
+ showSpinner: false
+})
+
+// HTTPrequest拦截
+axios.defaults.baseURL = baseUrl
+axios.interceptors.request.use(config => {
+ NProgress.start() // start progress bar
+ const isToken = (config.headers || {}).isToken === false
+ const token = store.getters.access_token
+
+ if (token && !isToken) {
+ config.headers['Authorization'] = 'Bearer ' + token// token
+ }
+
+ // headers中配置serialize为true开启序列化
+ if (config.method === 'post' && config.headers.serialize) {
+ config.data = serialize(config.data)
+ delete config.data.serialize
+ }
+
+ if (config.method === 'get') {
+ config.paramsSerializer = function(params) {
+ return qs.stringify(params, { arrayFormat: 'repeat' })
+ }
+ }
+ return config
+}, error => {
+ return Promise.reject(error)
+})
+
+// HTTPresponse拦截
+axios.interceptors.response.use(res => {
+ NProgress.done()
+ const status = Number(res.status) || 200
+ const message = res.data.msg || errorCode[status] || errorCode['default']
+ if (status == 401){
+ window.open("http://localhost:3000/")
+ }
+
+ // 后台定义 424 针对令牌过去的特殊响应码
+ if (status === 424) {
+ ElMessageBox.confirm('令牌状态已过期,请点击重新登录', '系统提示', {
+ confirmButtonText: '重新登录',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }
+ ).then(() => {
+ store.dispatch('LogOut').then(() => {
+ // 刷新登录页面,避免多次弹框
+ window.location.reload()
+ })
+ }).catch(() => {
+ })
+ return
+ }
+
+ if (status !== 200 || res.data.code === 1) {
+ ElMessage({
+ message: message,
+ type: 'error'
+ })
+ return Promise.reject(new Error(message))
+ }
+
+ return res
+}, error => {
+ // 处理 503 网络异常
+ console.log(error)
+ if (error.response.status === 503) {
+ ElMessage({
+ message: error.response.data.msg,
+ type: 'error'
+ })
+ }
+ NProgress.done()
+ return Promise.reject(new Error(error))
+})
+
+export default axios
diff --git a/summer-ospp/2023/pig/vue/src/store/modules/user.js b/summer-ospp/2023/pig/vue/src/store/modules/user.js
new file mode 100644
index 000000000..142ae1891
--- /dev/null
+++ b/summer-ospp/2023/pig/vue/src/store/modules/user.js
@@ -0,0 +1,225 @@
+import { setToken, setRefreshToken } from '@/util/auth'
+import { getStore, setStore } from '@/util/store'
+import { ssoLogin,loginByMobile, loginByUsername, getUserInfo, logout, refreshToken } from '@/api/login'
+import { deepClone, encryption } from '@/util'
+import { formatPath } from '@/router/avue-router'
+import { getMenu } from '@/api/admin/menu'
+const user = {
+ state: {
+ userInfo: getStore({
+ name: 'userInfo'
+ }) || {},
+ permissions: getStore({
+ name: 'permissions'
+ }) || [],
+ roles: [],
+ menu: getStore({
+ name: 'menu'
+ }) || [],
+ menuAll: getStore({ name: 'menuAll' }) || [],
+ access_token: getStore({
+ name: 'access_token'
+ }) || '',
+ refresh_token: getStore({
+ name: 'refresh_token'
+ }) || ''
+ },
+ actions: {
+ // SSO单点登陆
+ SSOLogin({ commit }, myParam,service) {
+ return new Promise((resolve, reject) => {
+ ssoLogin(myParam,service).then(response => {
+ const data = response.data.data
+ console.log(data)
+ commit('SET_ACCESS_TOKEN', data.access_token)
+ commit('SET_REFRESH_TOKEN', data.refresh_token)
+ commit('CLEAR_LOCK')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+ // 根据用户名登录
+ LoginByUsername({ commit }, userInfo) {
+ const user = encryption({
+ data: userInfo,
+ key: 'thanks,pig4cloud',
+ param: ['password']
+ })
+ return new Promise((resolve, reject) => {
+ loginByUsername(user.username, user.password, user.code, user.randomStr).then(response => {
+ const data = response.data
+ commit('SET_ACCESS_TOKEN', data.access_token)
+ commit('SET_REFRESH_TOKEN', data.refresh_token)
+ commit('CLEAR_LOCK')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+ // 根据手机号登录
+ LoginByPhone({ commit }, smsForm) {
+ return new Promise((resolve, reject) => {
+ loginByMobile(smsForm).then(response => {
+ const data = response.data
+ commit('SET_ACCESS_TOKEN', data.access_token)
+ commit('SET_REFRESH_TOKEN', data.refresh_token)
+ commit('CLEAR_LOCK')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // 刷新token
+ RefreshToken({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ refreshToken(state.refresh_token).then(response => {
+ const data = response.data
+ commit('SET_ACCESS_TOKEN', data.access_token)
+ commit('SET_REFRESH_TOKEN', data.refresh_token)
+ commit('CLEAR_LOCK')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+ // 查询用户信息
+ GetUserInfo({ commit }) {
+ return new Promise((resolve, reject) => {
+ getUserInfo().then((res) => {
+ const data = res.data.data || {}
+ commit('SET_USER_INFO', data.sysUser)
+ commit('SET_ROLES', data.roles || [])
+ commit('SET_PERMISSIONS', data.permissions || [])
+ resolve(data)
+ }).catch(() => {
+ reject()
+ })
+ })
+ },
+ // 登出
+ LogOut({ commit }) {
+ return new Promise((resolve, reject) => {
+ logout().then(() => {
+ commit('SET_MENUALL_NULL', [])
+ commit('SET_MENU', [])
+ commit('SET_PERMISSIONS', [])
+ commit('SET_USER_INFO', {})
+ commit('SET_ACCESS_TOKEN', '')
+ commit('SET_REFRESH_TOKEN', '')
+ commit('SET_ROLES', [])
+ commit('DEL_ALL_TAG')
+ commit('CLEAR_LOCK')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+ // 注销session
+ FedLogOut({ commit }) {
+ return new Promise(resolve => {
+ commit('SET_MENU', [])
+ commit('SET_MENUALL_NULL', [])
+ commit('SET_PERMISSIONS', [])
+ commit('SET_USER_INFO', {})
+ commit('SET_ACCESS_TOKEN', '')
+ commit('SET_REFRESH_TOKEN', '')
+ commit('SET_ROLES', [])
+ commit('DEL_ALL_TAG')
+ commit('CLEAR_LOCK')
+ resolve()
+ })
+ },
+ // 获取系统菜单
+ GetMenu({ commit }, obj = {}) {
+ // 记录用户点击顶部信息,保证刷新的时候不丢失
+ commit('LIKE_TOP_MENUID', obj)
+ return new Promise(resolve => {
+ getMenu(obj.id).then((res) => {
+ const data = res.data.data
+ const menu = deepClone(data)
+ menu.forEach(ele => formatPath(ele, true))
+ commit('SET_MENUALL', menu)
+ commit('SET_MENU', menu)
+ resolve(menu)
+ })
+ })
+ },
+ //顶部菜单
+ GetTopMenu() {
+ return new Promise(resolve => {
+ resolve([])
+ })
+ }
+ },
+ mutations: {
+ SET_ACCESS_TOKEN: (state, access_token) => {
+ state.access_token = access_token
+ setToken(access_token)
+ setStore({
+ name: 'access_token',
+ content: state.access_token,
+ type: 'session'
+ })
+ },
+ SET_REFRESH_TOKEN: (state, rfToken) => {
+ state.refresh_token = rfToken
+ setRefreshToken(rfToken)
+ setStore({
+ name: 'refresh_token',
+ content: state.refresh_token,
+ type: 'session'
+ })
+ },
+ SET_USER_INFO: (state, userInfo) => {
+ state.userInfo = userInfo
+ setStore({
+ name: 'userInfo',
+ content: userInfo,
+ type: 'session'
+ })
+ },
+ SET_MENUALL: (state, menuAll) => {
+ const menu = state.menuAll
+ menuAll.forEach(ele => {
+ if (!menu.find(item => item.label === ele.label && item.path === ele.path)) {
+ menu.push(ele)
+ }
+ })
+ state.menuAll = menu
+ setStore({ name: 'menuAll', content: state.menuAll })
+ },
+ SET_MENUALL_NULL: (state) => {
+ state.menuAll = []
+ setStore({ name: 'menuAll', content: state.menuAll })
+ },
+ SET_MENU: (state, menu) => {
+ state.menu = menu
+ setStore({ name: 'menu', content: state.menu })
+ },
+ SET_ROLES: (state, roles) => {
+ state.roles = roles
+ },
+ SET_PERMISSIONS: (state, permissions) => {
+ const list = {}
+ for (let i = 0; i < permissions.length; i++) {
+ list[permissions[i]] = true
+ }
+
+ state.permissions = list
+ setStore({
+ name: 'permissions',
+ content: list,
+ type: 'session'
+ })
+ }
+ }
+
+}
+export default user
From 07cc160a8662b5ba565ba51240de151b7921d75a Mon Sep 17 00:00:00 2001
From: RJ <1768976999@qq.com>
Date: Tue, 26 Sep 2023 15:32:00 +0800
Subject: [PATCH 2/4] =?UTF-8?q?pig=E6=95=B4=E5=90=88MAXKEY=E6=96=B9?=
=?UTF-8?q?=E6=A1=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
summer-ospp/2023/pig/readme.md | 2222 ++++++++++++++++++++++++++++++++
1 file changed, 2222 insertions(+)
create mode 100644 summer-ospp/2023/pig/readme.md
diff --git a/summer-ospp/2023/pig/readme.md b/summer-ospp/2023/pig/readme.md
new file mode 100644
index 000000000..44c704bfb
--- /dev/null
+++ b/summer-ospp/2023/pig/readme.md
@@ -0,0 +1,2222 @@
+# Pig整合MaxKey流程整理
+
+## 主要工作介紹
+
+1.pig集成maxkey中CAS的单点登录
+2.pig集成maxke的组织架构信息等
+
+## pig介绍
+
+### pig版本
+
+#### pig后端版本:3.6
+
+gitee地址:https://gitee.com/log4j/pig.git
+
+#### pig前端版本:最新代码
+
+gitee地址:https://gitee.com/log4j/pig-ui.git
+
+## 流程梳理
+
+### 1.pig-auth模块
+
+#### 1.pom.xml的更改覆盖了pig的Oauth2的token生成方案
+
+```xml
+
+
+
+
+ 4.0.0
+
+ com.pig4cloud
+ pig
+ 3.6.7
+
+
+ pig-auth
+ jar
+
+ pig 认证授权中心,基于 spring security oAuth2
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.7.0
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+ com.pig4cloud
+ pig-common-feign
+
+
+
+ com.pig4cloud
+ pig-upms-api
+
+
+ com.pig4cloud
+ pig-common-security
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+ com.pig4cloud
+ pig-common-log
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+
+
+
+```
+
+#### PigTokenEndpoint中新增获取token的方法
+
+```java
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.endpoint;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.TemporalAccessorUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
+import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;
+import com.pig4cloud.pig.admin.api.vo.TokenVo;
+import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;
+import com.pig4cloud.pig.auth.utils.RedisUtils;
+import com.pig4cloud.pig.auth.utils.TokenManager;
+import com.pig4cloud.pig.common.core.constant.CacheConstants;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.common.core.util.RetOps;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.security.annotation.Inner;
+import com.pig4cloud.pig.common.security.service.PigUser;
+import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
+import com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand;
+import com.pig4cloud.pig.common.security.util.OAuthClientException;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.CacheManager;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.authentication.event.LogoutSuccessEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author lengleng
+ * @date 2019/2/1 删除token端点
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/token")
+public class PigTokenEndpoint {
+
+ private final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
+
+ private final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler();
+
+ private final OAuth2AuthorizationService authorizationService;
+
+ private final RemoteClientDetailsService clientDetailsService;
+
+ private final RedisTemplate redisTemplate;
+
+ private final CacheManager cacheManager;
+
+ @Resource
+ private RedisUtils redisUtils;
+
+
+ @Resource
+ private TokenManager tokenManager;
+
+ private final static String SPRING_SESSION_PREFIX = "spring:session:sessions:%s";
+ private final static String PIG_TOKEN_PREFIX = "pig:token:%s:%s";
+ private final static String ASSCEE_TOKEN = "access_token";
+ private final static String REFRESH_TOKEN = "refresh_token";
+
+ private long tokenExpiration = 24 * 60 * 60 * 1000;
+
+
+ /**
+ * 认证页面
+ *
+ * @param modelAndView
+ * @param error 表单登录失败处理回调的错误信息
+ * @return ModelAndView
+ */
+ @GetMapping("/login")
+ public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
+ modelAndView.setViewName("ftl/login");
+ modelAndView.addObject("error", error);
+ return modelAndView;
+ }
+
+ @GetMapping("/confirm_access")
+ public ModelAndView confirm(Principal principal, ModelAndView modelAndView,
+ @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
+ @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
+ @RequestParam(OAuth2ParameterNames.STATE) String state) {
+ SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))
+ .getData()
+ .orElseThrow(() -> new OAuthClientException("clientId 不合法"));
+
+ Set authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());
+ modelAndView.addObject("clientId", clientId);
+ modelAndView.addObject("state", state);
+ modelAndView.addObject("scopeList", authorizedScopes);
+ modelAndView.addObject("principalName", principal.getName());
+ modelAndView.setViewName("ftl/confirm");
+ return modelAndView;
+ }
+
+ /**
+ * 退出并删除token
+ *
+ * @param authHeader Authorization
+ */
+ @DeleteMapping("/logout")
+ public R logout(HttpServletRequest request, @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
+ if (StrUtil.isBlank(authHeader)) {
+ return R.ok();
+ }
+ String sessonId = request.getSession().getId();
+ if (StrUtil.isBlank(sessonId)) {
+ return R.ok();
+ }
+ boolean isSuccess = redisUtils.deleteKey(generateSessionId(sessonId));
+ if (isSuccess) {
+ return R.ok();
+ } else {
+ return R.failed();
+ }
+
+ }
+
+ /**
+ * 校验token
+ *
+ * @param token 令牌
+ */
+ @SneakyThrows
+ @GetMapping("/check_token")
+ public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) {
+
+ ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+
+ if (StrUtil.isBlank(token)) {
+ httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
+ this.authenticationFailureHandler.onAuthenticationFailure(request, response,
+ new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING));
+ return;
+ }
+ OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
+
+ // 如果令牌不存在 返回401
+ if (authorization == null || authorization.getAccessToken() == null) {
+ this.authenticationFailureHandler.onAuthenticationFailure(request, response,
+ new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN));
+ return;
+ }
+
+ Map claims = authorization.getAccessToken().getClaims();
+ OAuth2AccessTokenResponse sendAccessTokenResponse = OAuth2EndpointUtils.sendAccessTokenResponse(authorization,
+ claims);
+ this.accessTokenHttpResponseConverter.write(sendAccessTokenResponse, MediaType.APPLICATION_JSON, httpResponse);
+ }
+
+ /**
+ * 令牌管理调用
+ *
+ * @param token token
+ */
+ @Inner
+ @DeleteMapping("/{token}")
+ public R removeToken(@PathVariable("token") String token) {
+ OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
+ if (authorization == null) {
+ return R.ok();
+ }
+
+ OAuth2Authorization.Token accessToken = authorization.getAccessToken();
+ if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) {
+ return R.ok();
+ }
+ // 清空用户信息
+ cacheManager.getCache(CacheConstants.USER_DETAILS).evict(authorization.getPrincipalName());
+ // 清空access token
+ authorizationService.remove(authorization);
+ // 处理自定义退出事件,保存相关日志
+ SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(
+ authorization.getPrincipalName(), authorization.getRegisteredClientId())));
+ return R.ok();
+ }
+
+ /**
+ * 查询token
+ *
+ * @param params 分页参数
+ * @return
+ */
+ @Inner
+ @PostMapping("/page")
+ public R tokenList(@RequestBody Map params) {
+ // 根据分页参数获取对应数据
+ String key = String.format("%s::*", CacheConstants.PROJECT_OAUTH_ACCESS);
+ int current = MapUtil.getInt(params, CommonConstants.CURRENT);
+ int size = MapUtil.getInt(params, CommonConstants.SIZE);
+ Set keys = redisTemplate.keys(key);
+ List pages = keys.stream().skip((current - 1) * size).limit(size).collect(Collectors.toList());
+ Page result = new Page(current, size);
+
+ List tokenVoList = redisTemplate.opsForValue().multiGet(pages).stream().map(obj -> {
+ OAuth2Authorization authorization = (OAuth2Authorization) obj;
+ TokenVo tokenVo = new TokenVo();
+ tokenVo.setClientId(authorization.getRegisteredClientId());
+ tokenVo.setId(authorization.getId());
+ tokenVo.setUsername(authorization.getPrincipalName());
+ OAuth2Authorization.Token accessToken = authorization.getAccessToken();
+ tokenVo.setAccessToken(accessToken.getToken().getTokenValue());
+
+ String expiresAt = TemporalAccessorUtil.format(accessToken.getToken().getExpiresAt(),
+ DatePattern.NORM_DATETIME_PATTERN);
+ tokenVo.setExpiresAt(expiresAt);
+
+ String issuedAt = TemporalAccessorUtil.format(accessToken.getToken().getIssuedAt(),
+ DatePattern.NORM_DATETIME_PATTERN);
+ tokenVo.setIssuedAt(issuedAt);
+ return tokenVo;
+ }).collect(Collectors.toList());
+ result.setRecords(tokenVoList);
+ result.setTotal(keys.size());
+ return R.ok(result);
+ }
+
+ @GetMapping("sso_login_get_token")
+ public R