Selaa lähdekoodia

pig 集成 readme

MaxKey 1 vuosi sitten
vanhempi
commit
ce6d4b38d2
1 muutettua tiedostoa jossa 2222 lisäystä ja 0 poistoa
  1. 2222 0
      summer-ospp/2023/pig/README.md

+ 2222 - 0
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
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig</artifactId>
+        <version>3.6.7</version>
+    </parent>
+
+    <artifactId>pig-auth</artifactId>
+    <packaging>jar</packaging>
+
+    <description>pig 认证授权中心,基于 spring security oAuth2</description>
+
+    <dependencies>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+			<version>0.7.0</version>
+		</dependency>
+        <!--注册中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <!--配置中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <!--断路器依赖-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-feign</artifactId>
+        </dependency>
+        <!--upms api、model 模块-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-upms-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <!--freemarker-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <!--undertow容器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+        <!-- log -->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-log</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>io.fabric8</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
+
+```
+
+#### 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<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
+
+	private final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler();
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	private final RemoteClientDetailsService clientDetailsService;
+
+	private final RedisTemplate<String, Object> 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<String> 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<Boolean> 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<String, Object> 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<Boolean> removeToken(@PathVariable("token") String token) {
+		OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
+		if (authorization == null) {
+			return R.ok();
+		}
+
+		OAuth2Authorization.Token<OAuth2AccessToken> 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<Page> tokenList(@RequestBody Map<String, Object> 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<String> keys = redisTemplate.keys(key);
+		List<String> pages = keys.stream().skip((current - 1) * size).limit(size).collect(Collectors.toList());
+		Page result = new Page(current, size);
+
+		List<TokenVo> 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<OAuth2AccessToken> 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<Map<String, String>> getToken(String ticket, String service) {
+		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+		PigUser pigUser = (PigUser) authentication.getPrincipal();
+		Map<String, String> ans = new HashMap<>();
+		String access_token = tokenManager.createToken(ASSCEE_TOKEN, pigUser.getName(), pigUser.getId().toString());
+		String refresh_token = tokenManager.createToken(REFRESH_TOKEN, pigUser.getName(), pigUser.getId().toString());
+		redisUtils.setValue(generateTokenKey(ASSCEE_TOKEN, pigUser.getId().toString()), access_token, tokenExpiration);
+		redisUtils.setValue(generateTokenKey(REFRESH_TOKEN, pigUser.getId().toString()), refresh_token, tokenExpiration);
+		ans.put("access_token", access_token);
+		ans.put("refresh_token", refresh_token);
+		return R.ok(ans);
+	}
+
+
+	private String generateSessionId(String sessionId) {
+		return String.format(SPRING_SESSION_PREFIX, sessionId);
+	}
+
+	private String generateTokenKey(String type, String userId) {
+		return String.format(PIG_TOKEN_PREFIX, type, userId);
+	}
+
+
+}
+
+```
+
+#### 3.新增utils类
+
+##### RedisUtils中操作缓存的工具类
+
+```java
+package com.pig4cloud.pig.auth.utils;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+@Component
+public class RedisUtils {
+
+	@Resource
+	private RedisTemplate<String, Object> 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);
+	}
+
+}
+
+```
+
+##### TokenMananer工具类
+
+```java
+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无需删除,客户端扔掉即可。
+	}
+
+}
+
+```
+
+### 2.pig-common
+
+#### pom文件的更改
+
+主要新增 CAS的MAVEN包
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig-common</artifactId>
+        <version>3.6.7</version>
+    </parent>
+
+    <artifactId>pig-common-security</artifactId>
+    <packaging>jar</packaging>
+
+    <description>pig 安全工具类</description>
+
+
+    <dependencies>
+		<dependency>
+			<groupId>org.springframework.session</groupId>
+			<artifactId>spring-session-data-redis</artifactId>
+		</dependency>
+		<!--        spring security cas-->
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-cas</artifactId>
+		</dependency>
+        <!--工具类核心包-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+        <!--UPMS API-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-upms-api</artifactId>
+        </dependency>
+        <!--common utils-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+        <!--feign 工具类-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-jose</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-authorization-server</artifactId>
+            <version>${spring.authorization.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+    </dependencies>
+</project>
+
+```
+
+#### annotation包下面
+
+更改EnablePigResourceServer
+
+```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.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
+ * <p>
+ * 资源服务注解
+ */
+@Documented
+@Inherited
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+//@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class })
+@Import({PigResourceServerAutoConfiguration.class, CasProperties.class, SecurityConfig.class})
+public @interface EnablePigResourceServer {
+
+}
+
+```
+
+#### Config包下
+
+##### 新增CasProperties主要是CAS得配置信息配置在NACOS中
+
+```java
+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;
+
+}
+
+```
+
+##### SecurityConfig类主要是CAS的认证流程并且覆盖原本pig的认证流程
+
+```java
+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;
+	}
+}
+
+```
+
+#### service包
+
+新增PigUserDetailsServiceImpl类主要功能为CAS服务认证成功方法,判断用户是否存在,存在获取用户信息,不存在调用远程接口新增用户并同步组织架构信息
+
+```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.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<CasAssertionAuthenticationToken> {
+
+	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<UserInfo> result = remoteUserService.saveIfNotExist(token.getAssertion().getPrincipal().getAttributes());
+		return getUserDetails(result, username);
+	}
+
+	private UserDetails getUserDetails(R<UserInfo> result, String username) {
+		Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
+		UserDetails userDetails = getUserDetails(result);
+		if (cache != null) {
+			cache.put(username, userDetails);
+		}
+		return userDetails;
+	}
+
+
+}
+
+```
+
+### pig-upms包
+
+#### pom的更改
+
+新增guava工具类
+
+```java
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig-upms</artifactId>
+        <version>3.6.7</version>
+    </parent>
+
+    <artifactId>pig-upms-biz</artifactId>
+    <packaging>jar</packaging>
+
+    <description>pig 通用用户权限管理系统业务处理模块</description>
+
+    <dependencies>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>29.0-jre</version>
+		</dependency>
+        <!--upms api、model 模块-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-upms-api</artifactId>
+        </dependency>
+        <!--文件管理-->
+        <dependency>
+            <groupId>com.pig4cloud.plugin</groupId>
+            <artifactId>oss-spring-boot-starter</artifactId>
+        </dependency>
+        <!--feign 调用-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-feign</artifactId>
+        </dependency>
+        <!--安全模块-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-security</artifactId>
+        </dependency>
+        <!--日志处理-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-log</artifactId>
+        </dependency>
+        <!--接口文档-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-swagger</artifactId>
+        </dependency>
+        <!-- orm 模块-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <!--注册中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <!--配置中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <!-- 阿里云短信下发 -->
+        <dependency>
+            <groupId>io.springboot.sms</groupId>
+            <artifactId>aliyun-sms-spring-boot-starter</artifactId>
+        </dependency>
+        <!--xss 过滤-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-xss</artifactId>
+        </dependency>
+        <!--undertow容器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>io.fabric8</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <excludes>
+                    <exclude>**/*.xlsx</exclude>
+                    <exclude>**/*.xls</exclude>
+                </excludes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>false</filtering>
+                <includes>
+                    <include>**/*.xlsx</include>
+                    <include>**/*.xls</include>
+                </includes>
+            </resource>
+        </resources>
+    </build>
+
+</project>
+
+```
+
+#### controller包
+
+在SysUserController中新增方法主要为提供远程调用方法,判断用户是否存在,添加用户信息等
+
+```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.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<UserInfoVO> info() {
+		String username = SecurityUtils.getUser().getUsername();
+		SysUser user = userService.getOne(Wrappers.<SysUser>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<UserInfo> info(@PathVariable String username) {
+		SysUser user = userService.getOne(Wrappers.<SysUser>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<UserInfo> save_sso(@RequestBody Map<String, Object> attributes) {
+		String username = (String) attributes.getOrDefault("username", "pig");
+		// 判断用户名是否存在
+		SysUser sysUser = userService.getOne(Wrappers.<SysUser>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<List<Long>> listUserIdByDeptIds(@RequestParam("deptIds") Set<Long> deptIds) {
+		return R.ok(userService.listUserIdByDeptIds(deptIds));
+	}
+
+	/**
+	 * 通过ID查询用户信息
+	 *
+	 * @param id ID
+	 * @return 用户信息
+	 */
+	@GetMapping("/{id:\\d+}")
+	public R<UserVO> user(@PathVariable Long id) {
+		return R.ok(userService.getUserVoById(id));
+	}
+
+	/**
+	 * 判断用户是否存在
+	 *
+	 * @param userDTO 查询条件
+	 * @return
+	 */
+	@Inner(false)
+	@GetMapping("/check/exist")
+	public R<Boolean> isExist(UserDTO userDTO) {
+		List<SysUser> 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<Boolean> 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<Boolean> 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<Boolean> updateUser(@Valid @RequestBody UserDTO userDto) {
+		return userService.updateUser(userDto);
+	}
+
+	/**
+	 * 分页查询用户
+	 *
+	 * @param page    参数集
+	 * @param userDTO 查询参数列表
+	 * @return 用户集合
+	 */
+	@GetMapping("/page")
+	public R<IPage<UserVO>> 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<Boolean> updateUserInfo(@Valid @RequestBody UserDTO userDto) {
+		userDto.setUsername(SecurityUtils.getUser().getUsername());
+		return userService.updateUserInfo(userDto);
+	}
+
+	/**
+	 * @param username 用户名称
+	 * @return 上级部门用户列表
+	 */
+	@GetMapping("/ancestor/{username}")
+	public R<List<SysUser>> 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<UserExcelVO> 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<UserExcelVO> excelVOList, BindingResult bindingResult) {
+		return userService.importUser(excelVOList, bindingResult);
+	}
+
+}
+
+```
+
+#### utils包
+
+新增BeanCreator方法,主要创建SysUser对象工具类
+
+```java
+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<String, Object> 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;
+	}
+
+}
+
+```
+
+### vue包
+
+主要是对pig前端页面的修改
+
+#### api包
+
+新增获取token的方法
+
+```js
+/*
+ *    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
+  })
+}
+
+```
+
+#### userlogin改造
+
+```html
+<template>
+  <el-form
+    ref="loginForm"
+    class="login-form"
+    status-icon
+    :rules="loginRules"
+    :model="loginForm"
+    label-width="0"
+  >
+    <el-form-item prop="username">
+      <el-input
+        v-model="loginForm.username"
+        auto-complete="off"
+        placeholder="请输入用户名"
+        @keyup.enter.native="handleLogin"
+      >
+        <template #prefix>
+          <i class="icon-yonghu"></i>
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item prop="password">
+      <el-input
+        v-model="loginForm.password"
+        size="small"
+        type="password"
+        auto-complete="off"
+        show-password
+        placeholder="请输入密码"
+        @keyup.enter.native="handleLogin"
+      >
+        <template #prefix>
+          <i class="icon-mima"></i>
+        </template>
+
+      </el-input>
+    </el-form-item>
+    <el-form-item v-if="website.validateCode" prop="code">
+      <el-input
+        v-model="loginForm.code"
+        :maxlength="code.len"
+        auto-complete="off"
+        placeholder="请输入验证码"
+        @keyup.enter.native="handleLogin"
+      >
+        <template #prefix>
+          <i class="icon-yanzhengma"></i>
+        </template>
+        <template #append>
+          <div class="login-code">
+            <span
+              v-if="code.type === 'text'"
+              class="login-code-img"
+              @click="refreshCode"
+            >{{ code.value }}</span
+            >
+            <img
+              v-else
+              :src="code.src"
+              class="login-code-img"
+              @click="refreshCode"
+            />
+          </div>
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item>
+      <el-button
+        type="primary"
+        class="login-submit"
+        @click.native.prevent="handleLogin"
+      >登录
+      </el-button
+      >
+    </el-form-item>
+
+  </el-form>
+</template>
+
+<script>
+import { randomLenNum } from '@/util'
+import { mapGetters } from 'vuex'
+// import {ssoLogin} from '@/api/login'
+export default {
+  name: 'userlogin',
+  data() {
+    return {
+      loginForm: {
+        username: 'admin',
+        password: '123456',
+        code: '',
+        randomStr: ''
+      },
+      checked: false,
+      code: {
+        src: '/code',
+        value: '',
+        len: 4,
+        type: 'image'
+      },
+      loginRules: {
+        username: [
+          { required: true, message: '请输入用户名', trigger: 'blur' },
+          { pattern: /^([a-z\u4e00-\u9fa5\d]*?)$/, message: '请输入小写字母', trigger: 'blur' }
+        ],
+        password: [
+          { required: true, message: '请输入密码', trigger: 'blur' },
+          { min: 6, message: '密码长度最少为6位', trigger: 'blur' }
+        ],
+        code: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
+      }
+    }
+  },
+  created() {
+    var params = new URLSearchParams(window.location.search);
+    var myParam = params.get('ticket');
+    var service = params.get('service');
+    if(myParam!=''&&myParam!=null){
+        this.ssoLogin(myParam,service)
+    }
+  },
+  updated(){
+    var params = new URLSearchParams(window.location.search);
+    var myParam = params.get('ticket');
+    var service = params.get('service');
+    if(myParam!=''&&myParam!=null){
+        this.ssoLogin(myParam,service)
+    }
+  },
+  computed: {
+    ...mapGetters(['tagWel', 'website'])
+  },
+  methods: {
+    async ssoLogin(myParam,service){
+      this.$store
+            .dispatch('SSOLogin', myParam,service)
+            .then(() => {
+              this.$router.push({ path: this.tagWel.value })
+            })
+            .catch(() => {
+              this.refreshCode()
+            })
+    },
+    refreshCode() {
+      this.loginForm.code = ''
+      this.loginForm.randomStr = randomLenNum(this.code.len, true)
+      this.code.type === 'text'
+        ? (this.code.value = randomLenNum(this.code.len))
+        : (this.code.src = `${this.baseUrl}/code?randomStr=${this.loginForm.randomStr}`)
+    },
+    handleLogin() {
+      this.$refs.loginForm.validate(valid => {
+        if (valid) {
+          this.$store
+            .dispatch('LoginByUsername', this.loginForm)
+            .then(() => {
+              this.$router.push({ path: this.tagWel.value })
+            })
+            .catch(() => {
+              this.refreshCode()
+            })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style></style>
+
+```
+
+#### router改造
+
+如果没权限返回登录页面
+
+```js
+
+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
+
+```
+
+#### store的user改造
+
+```js
+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
+
+```
+
+### Nacos配置文件新增
+
+在application-dev.yml新增配置信息
+
+```yml
+# 配置文件加密根密码
+jasypt:
+  encryptor:
+    password: pig
+    algorithm: PBEWithMD5AndDES
+    iv-generator-classname: org.jasypt.iv.NoIvGenerator
+    
+# Spring 相关
+spring:
+  cache:
+    type: redis
+  redis:
+    host: pig-redis
+  cloud:
+    sentinel:
+      eager: true
+      transport:
+        dashboard: pig-sentinel:5003
+
+# 暴露监控端点
+management:
+  endpoints:
+    web:
+      exposure:
+        include: "*"  
+  endpoint:
+    health:
+      show-details: ALWAYS
+
+
+# feign 配置
+feign:
+  sentinel:
+    enabled: true
+  okhttp:
+    enabled: true
+  httpclient:
+    enabled: false
+  client:
+    config:
+      default:
+        connectTimeout: 10000
+        readTimeout: 10000
+  compression:
+    request:
+      enabled: true
+    response:
+      enabled: true
+
+# mybaits-plus配置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*Mapper.xml
+  global-config:
+    banner: false
+    db-config:
+      id-type: auto
+      table-underline: true
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+  configuration:
+    map-underscore-to-camel-case: true
+
+# swagger 配置
+swagger:
+  enabled: true
+  title: Pig Swagger API
+  gateway: http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}
+  token-url: ${swagger.gateway}/auth/oauth2/token
+  scope: server
+  services:
+    pig-upms-biz: admin
+    pig-codegen: gen
+#cas配置
+cas:
+  #秘钥
+  key: n0c9MTcwMjIwMjMxNzE2NDMwOTAskV
+  server:
+    host:
+      grant_url: http://sso.maxkey.top/sign/authz/cas
+      #cas服务端地址 这是我的cas服务端地址 需要修改成你们的cas服务端地址
+      url: http://sso.maxkey.top/maxkey/authz/cas
+      #cas服务端登录地址
+      login_url: http://sso.maxkey.top/maxkey/#/passport/login?redirect_uri=aHR0cDovL3Nzby5tYXhrZXkudG9wL3NpZ24vYXV0aHovY2FzLzQxMDY1ZmUzLWFlNjctNDE3Mi1hNDYwLWZkMDA3OWU4ODI5NA
+      #cas服务端登出地址 service参数后面跟就是需要跳转的页面/接口 这里指定的是cas客户端登录接口
+      logout_url: ${cas.server.host.url}/logout?service=${cas.service.host.url}${cas.service.host.login_url}
+  service:
+    host:
+      #cas客户端地址
+      url: http://localhost:8080
+      #cas客户端地址登录地址
+      login_url: /login
+      #cas客户端地址登出地址
+      logout_url: /logout    
+```