Commit 28a94138 by 袁伟铭

添加单点登录服务

parent 6fbbb1ff
......@@ -43,6 +43,10 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
......
......@@ -25,6 +25,8 @@ import com.zq.admin.exception.BadRequestException;
import com.zq.admin.modules.security.service.OnlineUserService;
import com.zq.admin.modules.security.service.dto.AuthUserDto;
import com.zq.admin.modules.security.service.dto.JwtUserDto;
import com.zq.admin.modules.system.service.UserManager;
import com.zq.admin.modules.system.service.UserService;
import com.zq.admin.utils.RsaUtils;
import com.zq.admin.utils.SecurityUtils;
import com.zq.common.annotation.rest.AnonymousDeleteMapping;
......@@ -32,6 +34,7 @@ import com.zq.common.annotation.rest.AnonymousGetMapping;
import com.zq.common.annotation.rest.AnonymousPostMapping;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.vo.ResultVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
......@@ -43,6 +46,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
......@@ -72,6 +77,9 @@ public class AuthorizationController {
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final UserService userService;
private final UserManager userManager;
@Resource
private LoginProperties loginProperties;
......@@ -148,4 +156,31 @@ public class AuthorizationController {
return new ResponseEntity<>(HttpStatus.OK);
}
@ApiOperation("cas登录")
@AnonymousPostMapping(value = "/casLogin")
public ResultVo<Map<String, Object>> casLogin(@RequestBody AuthUserDto authUser, HttpServletRequest request) {
Boolean existName = userService.isExistName(authUser.getUsername());
if (!existName) {
userManager.addUser(authUser.getUsername());
} else {
userManager.updateUser(authUser.getUsername());
}
UserDetails userDetails = userDetailsService.loadUserByUsername(authUser.getUsername());
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌
String token = TokenProvider.createToken(authentication);
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 保存在线信息
onlineUserService.save(jwtUserDto, token, request);
// 返回 token 与 用户信息
Map<String, Object> authInfo = new HashMap<String, Object>(1) {{
put("token", token);
}};
return ResultVo.success(authInfo);
}
}
......@@ -67,6 +67,15 @@ public class Dept extends BaseEntity implements Serializable {
@ApiModelProperty(value = "子节点数目", hidden = true)
private Integer subCount = 0;
@ApiModelProperty("法院分级码")
private String gradingCode;
@ApiModelProperty("机构代码")
private String orgCode;
@ApiModelProperty(value = "简称")
private String abbreviation;
@Override
public boolean equals(Object o) {
if (this == o) {
......
......@@ -106,6 +106,15 @@ public class User extends BaseEntity implements Serializable {
@ApiModelProperty(value = "最后修改密码的时间", hidden = true)
private Date pwdResetTime;
@ApiModelProperty("法院分级码")
private String gradingCode;
@ApiModelProperty("用户代码")
private String userCode;
@ApiModelProperty("身份证")
private String idCard;
@Override
public boolean equals(Object o) {
if (this == o) {
......
......@@ -135,4 +135,6 @@ public interface DeptService {
*/
void verification(Set<DeptDto> deptDtos);
Dept getById(Long orgId);
}
package com.zq.admin.modules.system.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.admin.modules.system.domain.Dept;
import com.zq.admin.modules.system.domain.Job;
import com.zq.admin.modules.system.domain.Role;
import com.zq.admin.modules.system.domain.User;
import com.zq.admin.modules.system.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* @author wilmiam
* @since 2022/10/11 18:54
*/
@RequiredArgsConstructor
@Service
public class UserManager {
private final DeptService deptService;
private final UserService userService;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Value("${spring.profiles.active:dev}")
private String active;
public void addUser(String username) {
if ("dev".equals(active)) {
return;
}
String resultStr = HttpUtil.get("http://147.1.6.23/updatelist/getUserInfo/" + username.split("@")[0]);
JSONObject userObj = JSONUtil.parseObj(resultStr);
Integer xb = userObj.getInt("XB", 0);
Dept dept = deptService.getById(userObj.getLong("orgId"));
if (dept == null) {
dept = addDept(userObj.getStr("JGBS"));
}
// 默认角色
Set<Role> roleSet = new HashSet<>();
Role role = new Role();
role.setId(3L);
roleSet.add(role);
// 默认岗位
Set<Job> jobSet = new HashSet<>();
Job job = new Job();
job.setId(3L);
jobSet.add(job);
User user = new User();
user.setId(userObj.getLong("userId"));
user.setDept(dept);
user.setRoles(roleSet);
user.setJobs(jobSet);
user.setUsername(userObj.getStr("YOUXIANG"));
user.setNickName(userObj.getStr("XM"));
user.setEmail(userObj.getStr("YOUXIANG"));
user.setPhone(userObj.getStr("SJHM"));
user.setGender(xb == 1 ? "男" : xb == 2 ? "女" : "未知");
user.setPassword(passwordEncoder.encode("Gxfy2022"));
user.setEnabled(userObj.getInt("YX") == 1);
user.setIsAdmin(false);
user.setGradingCode(userObj.getStr("FY")); //法院分级码
user.setUserCode(userObj.getStr("RYBS")); // 人员标识
user.setIdCard(userObj.getStr("SFZHM")); // 身份证号码
user.setCreateTime(new Timestamp(userObj.getDate("CJSJ", new Date()).getTime()));
user.setUpdateTime(new Timestamp(userObj.getDate("GXSJ", new Date()).getTime()));
userService.create(user);
}
private Dept addDept(String orgBs) {
String resultStr = HttpUtil.get("http://147.1.6.23/updatelist/getOrgInfo/" + orgBs);
JSONObject orgObj = JSONUtil.parseObj(resultStr);
//获取部门id(机构id)
Long deptId = orgObj.getLong("orgId");
//获取上级部门id
Long pid = orgObj.getLong("parentOrgId");
//获取机构标识
String orgCodes = orgObj.getStr("JGBS");
//获取法院代码
String gradingCode = orgObj.getStr("FY");
Dept dept = new Dept();
dept.setId(deptId);
dept.setDeptSort(orgObj.getInt("PXH"));
dept.setName(orgObj.getStr("MC"));
dept.setEnabled(orgObj.getInt("YX") == 1);
dept.setPid(pid);
dept.setGradingCode(gradingCode); // 法院分级码
dept.setOrgCode(orgCodes); // 机构标识
dept.setAbbreviation(orgObj.getStr("JC")); // 机构简称
dept.setCreateTime(new Timestamp(orgObj.getDate("CJSJ", new Date()).getTime()));
dept.setUpdateTime(new Timestamp(orgObj.getDate("GXSJ", new Date()).getTime()));
deptService.create(dept);
return dept;
}
public void updateUser(String username) {
if ("dev".equals(active)) {
return;
}
String resultStr = HttpUtil.get("http://147.1.6.23/updatelist/getUserInfo/" + username.split("@")[0]);
JSONObject userObj = JSONUtil.parseObj(resultStr);
Integer xb = userObj.getInt("XB", 0);
User user = userRepository.findByUsername(username);
user.setNickName(userObj.getStr("XM"));
user.setPhone(userObj.getStr("SJHM"));
user.setGender(xb == 1 ? "男" : xb == 2 ? "女" : "未知");
user.setEnabled(userObj.getInt("YX") == 1);
user.setGradingCode(userObj.getStr("FY"));
user.setUserCode(userObj.getStr("RYBS"));
user.setIdCard(userObj.getStr("SFZHM"));
user.setUpdateTime(new Timestamp(userObj.getDate("GXSJ", new Date()).getTime()));
if (StrUtil.isBlank(user.getPassword())) {
user.setPassword(passwordEncoder.encode("Gxfy2022"));
}
userRepository.save(user);
}
/**
* 添加默认角色
*
* @param userId
*/
public boolean addDefaultRoleAndJob(Long userId) {
User user = userRepository.findById(userId).orElse(null);
if (user != null) {
Set<Role> roleSet = new HashSet<>();
Role role = new Role();
role.setId(3L); // 公共角色ID
roleSet.add(role);
Set<Job> jobSet = new HashSet<>();
Job job = new Job();
job.setId(3L); // 其他岗位ID
jobSet.add(job);
Set<Role> roles = user.getRoles();
if (CollUtil.isNotEmpty(roles) || !roles.contains(role)) {
roleSet.addAll(roles);
}
Set<Job> jobs = user.getJobs();
if (CollUtil.isNotEmpty(jobs) || !jobs.contains(job)) {
jobSet.addAll(jobs);
}
user.setRoles(roleSet);
user.setJobs(jobSet);
userRepository.save(user);
return true;
}
return false;
}
}
......@@ -137,4 +137,6 @@ public interface UserService {
*/
void updateCenter(User resources);
Boolean isExistName(String username);
}
......@@ -252,6 +252,12 @@ public class DeptServiceImpl implements DeptService {
}
}
@Override
public Dept getById(Long orgId) {
Optional<Dept> deptOptional = deptRepository.findById(deptId);
return deptOptional.orElse(null);
}
private void updateSubCnt(Long deptId) {
if (deptId != null) {
int count = deptRepository.countByPid(deptId);
......
......@@ -157,6 +157,12 @@ public class UserServiceImpl implements UserService {
}
@Override
public Boolean isExistName(String username) {
User user = userRepository.findByUsername(username);
return user != null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Set<Long> ids) {
for (Long id : ids) {
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-backend</artifactId>
<groupId>com.zq</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cas-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.zq</groupId>
<artifactId>xxx-common-utils</artifactId>
<version>1.0.0</version>
</dependency>
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 连接配置中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--Spring boot Web容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring boot 测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Spring boot 安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Spring boot Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--CAS单点登陆-->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>2.1.0-GA</version>
</dependency>
</dependencies>
</project>
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* //@EnableEurekaClient
//@EnableDiscoveryClient
*/
package com.zq.cas;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author Zheng Jie
* @date 2018/11/15 9:20:19
*/
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.zq.auth", "com.zq.common.config"},
exclude = DataSourceAutoConfiguration.class)
public class OauthApplication {
public static void main(String[] args) {
SpringApplication.run(OauthApplication.class, args);
}
/**
* 访问首页提示
*
* @return /
*/
@GetMapping("/")
public String index() {
return "Backend service started successfully";
}
}
package com.zq.cas.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.AssertionThreadLocalFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;
@Component
@Configuration
public class CasConfig {
@Autowired
private SpringCasAutoconfig autoconfig;
private static boolean casEnabled = true;
public CasConfig() {
}
@Bean
public SpringCasAutoconfig getSpringCasAutoconfig() {
return new SpringCasAutoconfig();
}
/**
* 添加监听器
*/
@Bean
public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>();
listener.setEnabled(casEnabled);
listener.setListener(new SingleSignOutHttpSessionListener());
listener.setOrder(1);
return listener;
}
/**
* 登出过滤器
* (这个过滤器不知道有什么作用)
*/
// 没用到security 可以尝试这样写,或者使用自己项目的安全LogoutFilter
// @Bean
// public FilterRegistrationBean filterSingleRegistration() {
// FilterRegistrationBean registration = new FilterRegistrationBean();
// registration.setFilter(new SingleSignOutFilter());
// // 设定匹配的路径
// registration.addUrlPatterns("/logout");
// Map<String,String> initParameters = new HashMap<String, String>();
// initParameters.put("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
// registration.setInitParameters(initParameters);
// // 设定加载的顺序
// registration.setOrder(2);
// return registration;
// }
@Bean
public FilterRegistrationBean<LogoutFilter> logOutFilter() {
FilterRegistrationBean<LogoutFilter> filterRegistration = new FilterRegistrationBean<>();
LogoutFilter logoutFilter = new LogoutFilter(autoconfig.getCasServerUrlPrefix() + "/logout?service=" + autoconfig.getServerName(), new SecurityContextLogoutHandler());
filterRegistration.setFilter(logoutFilter);
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getSignOutFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getSignOutFilters());
else
filterRegistration.addUrlPatterns("/logout");
filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
filterRegistration.setOrder(2);
return filterRegistration;
}
/**
* 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
*/
@Bean
public FilterRegistrationBean<SingleSignOutFilter> singleSignOutFilter() {
FilterRegistrationBean<SingleSignOutFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new SingleSignOutFilter());
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getSignOutFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getSignOutFilters());
else
filterRegistration.addUrlPatterns("/*");
filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
filterRegistration.setOrder(3);
return filterRegistration;
}
/**
* 授权过滤器,用于登录
* 该过滤器负责用户的认证工作
*/
@Bean
public FilterRegistrationBean<MyAuthenticationFilter> authenticationFilter() {
FilterRegistrationBean<MyAuthenticationFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new MyAuthenticationFilter());
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getAuthFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getAuthFilters());
else
filterRegistration.addUrlPatterns("/*");
//casServerLoginUrl:cas服务的登陆url
filterRegistration.addInitParameter("casServerLoginUrl", autoconfig.getCasServerLoginUrl());
//本项目登录ip+port
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
filterRegistration.addInitParameter("useSession", autoconfig.isUseSession() ? "true" : "false");
filterRegistration.addInitParameter("redirectAfterValidation", autoconfig.isRedirectAfterValidation() ? "true" : "false");
filterRegistration.setOrder(4);
return filterRegistration;
}
/**
* ticke验证器
* 该过滤器负责对Ticket的校验工作
*/
@Bean
public FilterRegistrationBean<Cas20ProxyReceivingTicketValidationFilter> cas20ProxyReceivingTicketValidationFilter() {
FilterRegistrationBean<Cas20ProxyReceivingTicketValidationFilter> filterRegistration = new FilterRegistrationBean<>();
Cas20ProxyReceivingTicketValidationFilter cas20ProxyReceivingTicketValidationFilter = new Cas20ProxyReceivingTicketValidationFilter();
cas20ProxyReceivingTicketValidationFilter.setServerName(autoconfig.getServerName());
filterRegistration.setFilter(cas20ProxyReceivingTicketValidationFilter);
filterRegistration.setEnabled(casEnabled);
if (autoconfig.getValidateFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getValidateFilters());
else
filterRegistration.addUrlPatterns("/*");
filterRegistration.addInitParameter("casServerUrlPrefix", autoconfig.getCasServerUrlPrefix());
filterRegistration.addInitParameter("serverName", autoconfig.getServerName());
filterRegistration.setOrder(5);
return filterRegistration;
}
/**
* wraper过滤器
* 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名
*/
@Bean
public FilterRegistrationBean<HttpServletRequestWrapperFilter> httpServletRequestWrapperFilter() {
FilterRegistrationBean<HttpServletRequestWrapperFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new HttpServletRequestWrapperFilter());
filterRegistration.setEnabled(true);
if (autoconfig.getRequestWrapperFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getRequestWrapperFilters());
else
filterRegistration.addUrlPatterns("/login");
filterRegistration.setOrder(6);
return filterRegistration;
}
/**
* 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
* 比如AssertionHolder.getAssertion().getPrincipal().getName()。
* 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息
*/
@Bean
public FilterRegistrationBean<AssertionThreadLocalFilter> assertionThreadLocalFilter() {
FilterRegistrationBean<AssertionThreadLocalFilter> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setFilter(new AssertionThreadLocalFilter());
filterRegistration.setEnabled(true);
if (autoconfig.getAssertionFilters().size() > 0)
filterRegistration.setUrlPatterns(autoconfig.getAssertionFilters());
else
filterRegistration.addUrlPatterns("/*");
filterRegistration.setOrder(7);
return filterRegistration;
}
}
package com.zq.cas.config;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @author wilmiam
* @since 2022/10/13 18:09
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jump")
public class JumpConfig {
private Map<String, String> domainMap;
public String getDomain(String systemTag) {
String url = domainMap.get(systemTag);
if (StrUtil.isBlank(url)) {
url = domainMap.get("default");
}
return url;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.config;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}
package com.zq.cas.config;
import cn.hutool.json.JSONUtil;
import com.zq.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl;
import org.jasig.cas.client.authentication.GatewayResolver;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.validation.Assertion;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@Slf4j
public class MyAuthenticationFilter extends AbstractCasFilter {
private String casServerLoginUrl;
private boolean renew = false;
private boolean gateway = false;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
public MyAuthenticationFilter() {
}
protected void initInternal(FilterConfig filterConfig) throws ServletException {
if (!this.isIgnoreInitConfiguration()) {
super.initInternal(filterConfig);
this.setCasServerLoginUrl(this.getPropertyFromInitParams(filterConfig, "casServerLoginUrl", (String) null));
this.log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
this.setRenew(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "renew", "false")));
this.log.trace("Loaded renew parameter: " + this.renew);
this.setGateway(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "gateway", "false")));
this.log.trace("Loaded gateway parameter: " + this.gateway);
String gatewayStorageClass = this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String) null);
if (gatewayStorageClass != null) {
try {
this.gatewayStorage = (GatewayResolver) Class.forName(gatewayStorageClass).newInstance();
} catch (Exception var4) {
this.log.error(String.valueOf(var4), var4);
throw new ServletException(var4);
}
}
}
}
public void init() {
super.init();
CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
}
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
PrintWriter out = null;
HttpSession session = request.getSession(false);
Assertion assertion = session != null ? (Assertion) session.getAttribute("_const_cas_assertion_") : null;
if (assertion != null) {
filterChain.doFilter(request, response);
} else {
String serviceUrl = this.constructServiceUrl(request, response);
String ticket = CommonUtils.safeGetParameter(request, this.getArtifactParameterName());
boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
this.log.debug("no ticket and no assertion found");
String modifiedServiceUrl;
if (this.gateway) {
this.log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
if (this.log.isDebugEnabled()) {
this.log.debug("Constructed service url: " + modifiedServiceUrl);
}
String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
if (urlToRedirectTo.contains("&")) {
String[] split = urlToRedirectTo.split("&");
urlToRedirectTo = split[0];
}
if (this.log.isDebugEnabled()) {
this.log.debug("redirecting to \"" + urlToRedirectTo + "\"");
}
//不适用它原来的,换成自定义重定向类型
//response.sendRedirect(urlToRedirectTo);
//这个的话无法使用 ip直接跳转登陆 必须使用 ip/oauth/login 才能登陆
/*if(serviceUrl.contains("logout") || serviceUrl.contains("login")){
response.sendRedirect(urlToRedirectTo);
}else {
response.setContentType("application/json;charset=UTF-8");
out = response.getWriter();
out.println(JSONUtil.toJsonStr(ResultVo.fail(401,"登陆身份已失效")));
out.flush();
out.close();
}*/
//不能使用 ip+/oauth/login 跳转登陆 接口200返回401,可直接ip和页面路由进行拦截跳转
response.setContentType("application/json;charset=UTF-8");
out = response.getWriter();
out.println(JSONUtil.toJsonStr(ResultVo.fail(401, "登陆身份已失效")));
out.flush();
out.close();
} else {
filterChain.doFilter(request, response);
}
}
}
public final void setRenew(boolean renew) {
this.renew = renew;
}
public final void setGateway(boolean gateway) {
this.gateway = gateway;
}
public final void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public final void setGatewayStorage(GatewayResolver gatewayStorage) {
this.gatewayStorage = gatewayStorage;
}
}
package com.zq.cas.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import java.util.Arrays;
import java.util.List;
@EnableConfigurationProperties(SpringCasAutoconfig.class)
@ConfigurationProperties(prefix = "spring.cas")
public class SpringCasAutoconfig {
private static final String separator = ",";
private String validateFilters;
private String signOutFilters;
private String authFilters;
private String assertionFilters;
private String requestWrapperFilters;
private String casServerUrlPrefix;
private String casServerLoginUrl;
private String serverName;
private boolean useSession = true;
private boolean redirectAfterValidation = true;
public List<String> getValidateFilters() {
return Arrays.asList(validateFilters.split(separator));
}
public void setValidateFilters(String validateFilters) {
this.validateFilters = validateFilters;
}
public List<String> getSignOutFilters() {
return Arrays.asList(signOutFilters.split(separator));
}
public void setSignOutFilters(String signOutFilters) {
this.signOutFilters = signOutFilters;
}
public List<String> getAuthFilters() {
return Arrays.asList(authFilters.split(separator));
}
public void setAuthFilters(String authFilters) {
this.authFilters = authFilters;
}
public List<String> getAssertionFilters() {
return Arrays.asList(assertionFilters.split(separator));
}
public void setAssertionFilters(String assertionFilters) {
this.assertionFilters = assertionFilters;
}
public List<String> getRequestWrapperFilters() {
return Arrays.asList(requestWrapperFilters.split(separator));
}
public void setRequestWrapperFilters(String requestWrapperFilters) {
this.requestWrapperFilters = requestWrapperFilters;
}
public String getCasServerUrlPrefix() {
return casServerUrlPrefix;
}
public void setCasServerUrlPrefix(String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
public String getCasServerLoginUrl() {
return casServerLoginUrl;
}
public void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public boolean isRedirectAfterValidation() {
return redirectAfterValidation;
}
public void setRedirectAfterValidation(boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
}
public boolean isUseSession() {
return useSession;
}
public void setUseSession(boolean useSession) {
this.useSession = useSession;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.config;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.utils.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
* @author Zheng Jie
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
Set<String> allType = anonymousUrls.get(RequestMethodEnum.ALL.getType());
//不使用注解的时候在这添加url放行
// allType.add("/user/api/**");
httpSecurity
// 禁用 CSRF
.csrf().disable()
// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(allType.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, redisUtils);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.config;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, redisUtils);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.config;
import cn.hutool.core.util.StrUtil;
import com.zq.common.config.redis.BaseCacheKeys;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.context.ContextUtils;
import com.zq.common.vo.OnlineUserDto;
import io.jsonwebtoken.ExpiredJwtException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
/**
* @author /
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
/**
* @param tokenProvider Token
* @param properties JWT
* @param redisUtils redis
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.tokenProvider = tokenProvider;
this.redisUtils = redisUtils;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = tokenProvider.getToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
onlineUserDto = redisUtils.getObj(properties.getOnlineKey() + token, OnlineUserDto.class);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
String username = String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY));
redisUtils.hdel(BaseCacheKeys.USER_DATA_MAP_KEY, username);
}
}
if (onlineUserDto != null && StringUtils.isNotBlank(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
// 设置当前用户
ContextUtils.setAdminContext(onlineUserDto);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
package com.zq.cas.config;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
public static final String AUTHORITIES_KEY = "auth";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.compact();
}
public Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.isBlank(bearerToken)) {
return null;
}
if (bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}
package com.zq.cas.config.feign;
import com.zq.common.utils.HttpRequestUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* @author wilmiam
* @since 2021-07-09 10:34
*/
@Configuration
public class FeignConfig {
private final ObjectFactory<HttpMessageConverters> messageConverters;
public FeignConfig(ObjectFactory<HttpMessageConverters> messageConverters) {
this.messageConverters = messageConverters;
}
/**
* 转发请求头
*/
private static final List<String> FORWARD_HEADERS = Arrays.asList(
"AUTHORIZATION",
"X-FORWARDED-FOR",
"X-FORWARDED-PROTO",
"X-FORWARDED-PORT",
"X-FORWARDED-HOST",
"FORWARDED",
"PROXY-CLIENT-IP",
"WL-PROXY-CLIENT-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-REAL-IP",
"HOST"
);
/**
* 解决fein远程调用丢失请求头
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest request = HttpRequestUtils.getRequest();
if (request == null) {
return;
}
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
// 不要设置content-length
if ("content-length".equals(name)) {
continue;
}
if (FORWARD_HEADERS.contains(name.toUpperCase())) {
String values = request.getHeader(name);
template.header(name, values);
}
}
}
}
};
}
@Bean
public Encoder feignEncoder() {
return new FeignSpringFormEncoder(new SpringEncoder(messageConverters));
}
}
package com.zq.cas.config.feign;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.ContentType;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringManyMultipartFilesWriter;
import feign.form.spring.SpringSingleMultipartFileWriter;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
/**
* feign 多文件上传配置
*
* @author Lander
*/
public class FeignSpringFormEncoder extends FormEncoder {
public FeignSpringFormEncoder() {
this(new Default());
}
public FeignSpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) this.getContentProcessor(ContentType.MULTIPART);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType != null && bodyType.equals(MultipartFile[].class)) {
MultipartFile[] file = (MultipartFile[]) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
} else if (bodyType != null && bodyType.equals(MultipartFile.class)) {
MultipartFile file = (MultipartFile) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
}
super.encode(object, bodyType, template);
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.cas.controller;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zq.cas.config.JumpConfig;
import com.zq.cas.config.SpringCasAutoconfig;
import com.zq.cas.service.FyUserManager;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.annotation.rest.AnonymousPostMapping;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.vo.ResultVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.security.Principal;
/**
* @author Zheng Jie
* @date 2018-11-23
* 授权、根据token获取用户详细信息
*/
@Slf4j
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor
@Api(tags = "系统:系统授权接口")
public class AuthorizationController {
private final JumpConfig jumpConfig;
private final SpringCasAutoconfig casAutoconfig;
private final SecurityProperties properties;
private final FyUserManager fyUserManager;
@RequestMapping("/login")
@AnonymousAccess
public void casLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
Principal principal = request.getUserPrincipal();
String username = principal.getName();
if (!username.contains("@")) {
username = username + "@gxfy.com";
}
log.info("登录用户:" + username);
ResultVo resultVo = fyUserManager.casLogin(username);
if (resultVo.isSuccess()) {
}
JSONObject object = JSON.parseObject(JSON.toJSONString(resultVo.getData()));
String sessionId = request.getRequestedSessionId();
String systemTag = request.getParameter("systemTag");
String domain = jumpConfig.getDomain(systemTag);
response.sendRedirect(domain + "/#/verifyLogin?" + properties.getHeader() + "=" + object.getString("token") + "&JSESSIONID=" + sessionId);
}
@ApiOperation("退出登录")
@RequestMapping(value = "/logout")
@ResponseBody
@AnonymousAccess
public ResultVo<String> logout(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
fyUserManager.logout(request.getHeader(properties.getHeader()));
String systemTag = request.getParameter("systemTag");
// String ticket = request.getParameter("ticket");
request.removeAttribute("ticket");
// if (StrUtil.isNotBlank(ticket)) {
// request.getParameter("ticket").trim();
// }
session.invalidate();
request.getSession().invalidate();
String service = URLUtil.encode(casAutoconfig.getCasServerLoginUrl() + "?systemTag=" + systemTag);
String logoutUrl = casAutoconfig.getCasServerUrlPrefix() + "/logout?service=" + service;
return ResultVo.success(logoutUrl);
}
//用于旧系统退出单点登录
@ApiOperation("旧系统退出单点登录")
@RequestMapping(value = "/cas/logout")
@AnonymousAccess
public void casLogout(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException {
fyUserManager.logout(request.getHeader(properties.getHeader()));
request.removeAttribute("ticket");
String service = request.getParameter("service");
session.invalidate();
request.getSession().invalidate();
String logoutUrl = casAutoconfig.getCasServerUrlPrefix() + "/logout?service=" + service;
response.sendRedirect(logoutUrl);
}
@ApiOperation("销毁token")
@RequestMapping(value = "/removeToken")
@ResponseBody
@AnonymousAccess
public ResultVo removeToken(HttpServletRequest request) {
fyUserManager.logout(request.getHeader(properties.getHeader()));
return ResultVo.success();
}
@ApiOperation("获取token")
@AnonymousPostMapping("/getToken")
public ResultVo removeToken(@RequestParam("username") String username) {
if (!username.contains("@")) {
username = username + "@gxfy.com";
}
ResultVo resultVo = fyUserManager.casLogin(username);
JSONObject object = JSON.parseObject(JSON.toJSONString(resultVo.getData()));
return ResultVo.success(object.getString("token"));
}
}
package com.zq.cas.feignclient;
import com.zq.cas.config.feign.FeignConfig;
import com.zq.common.vo.ResultVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Map;
/**
* @author wilmiam
* @since 2022/10/11 17:19
*/
@FeignClient(name = "ADMIN-SERVER", path = "/admin", configuration = FeignConfig.class)
public interface AdminFeignClient {
@PostMapping(value = "/auth/casLogin")
ResultVo casLogin(@RequestBody Map<String, Object> params);
@DeleteMapping(value = "/auth/logout")
ResultVo logout();
}
package com.zq.cas.service;
import cn.hutool.core.util.StrUtil;
import com.zq.cas.feignclient.AdminFeignClient;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class FyUserManager {
@Autowired
private AdminFeignClient feignClient;
/**
* fy用户单点登陆
*
* @param username
* @return
*/
public ResultVo casLogin(String username) {
Map<String, Object> params = new HashMap<>();
params.put("username", username);
ResultVo resultVo = feignClient.casLogin(params);
AssertUtils.isTrue(resultVo.isSuccess(), resultVo.getErrMsg());
return resultVo;
}
/**
* fy用户登出
*
* @param token
*/
public void logout(String token) {
if (StrUtil.isNotBlank(token) && !"undefined".equals(token)) {
feignClient.logout();
}
}
}
# 系统跳转配置
jump:
domain-map:
default: http://172.20.10.5:8026
oa-document-zat: http://192.168.0.29:8026/
default-ld: http://192.168.0.162:8026
spring:
#单点登陆配置
cas:
sign-out-filters: /oauth/logout
auth-filters: /oauth/login
validate-filters: /*
request-wrapper-filters: /*
assertion-filters: /*
redirect-after-validation: true
use-session: true
cas-server-login-url: http://175.178.197.14:8081/cas/login/login?service=http://172.20.10.5:9888/oauth/login
cas-server-url-prefix: http://175.178.197.14:8081/cas
server-name: 172.20.10.5:9888
# 系统跳转配置
jump:
domain-map:
default: http://147.1.5.135:8090
invoice: http://147.1.4.67:8060
archive: http://147.1.5.135:8090
spring:
#单点登陆配置
cas:
sign-out-filters: /oauth/logout
auth-filters: /oauth/login
validate-filters: /*
request-wrapper-filters: /*
assertion-filters: /*
redirect-after-validation: true
use-session: true
cas-server-login-url: http://147.1.6.16:8080/cas/login?service=http://147.1.5.135/oauth/login
cas-server-url-prefix: http://147.1.6.16:8080/cas
server-name: http://147.1.5.135
# 系统跳转配置
jump:
domain-map:
default: http://147.1.4.67:8090
invoice: http://147.1.4.67:8060
archive: http://147.1.4.67:8090
spring:
#单点登陆配置
cas:
sign-out-filters: /oauth/logout
auth-filters: /oauth/login
validate-filters: /*
request-wrapper-filters: /*
assertion-filters: /*
redirect-after-validation: true
use-session: true
cas-server-login-url: http://172.18.3.137:8080/cas/login?service=http://147.1.4.67/oauth/login
cas-server-url-prefix: http://172.18.3.137:8080/cas
server-name: http://147.1.4.67
server:
port: ${auth.port}
#配置数据源
spring:
application:
name: ${auth.name}
redis:
#数据库索引
host: ${redis.url}
port: ${redis.port}
password: ${redis.password}
database: 0
#连接超时时间
timeout: 5000
#登录配置
login:
single: true
spring:
profiles:
active: @profiles.active@
cloud:
config:
name: config
profile: ${spring.profiles.active}
discovery:
enabled: true
service-id: CONFIG-SERVER
eureka:
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 #每间隔1s,向服务端发送一次心跳,证明自己依然"存活"
lease-expiration-duration-in-seconds: 6 #告诉服务端,如果我2s之内没有给你发心跳,就代表我"死"了,将我踢出掉。
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
service-url:
defaultZone: @eureka.server.url@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!-- 项目名称也是主要日志文件名 -->
<property name="PROJECT_NAME" value="oauth"/>
<!-- 日志目录 -->
<property name="LOG_PATH" value="/data/logs/${LOG_PATH:-${PROJECT_NAME}}"/>
<!-- 日志格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:-} [%15.15t] %-40.40logger{39} : %m%n"/>
<!-- the name of the application's logging context -->
<!-- by default each JMXConfigurator instance will be registered under the same name in the same JVM -->
<!-- we need to set the contextName for different apps, so that the jmxconfigurator won't collide -->
<contextName>${PROJECT_NAME}</contextName>
<jmxConfigurator/>
<!--主要日志配置 开始-->
<appender name="SIZED_ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步输出 -->
<appender name="MAIN-LOGGER-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="SIZED_ROLLING_FILE"/>
</appender>
<!--主要日志配置 结束-->
<!--DEBUG日志配置 开始-->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_PATH}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/debug/debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="DEBUG-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="DEBUG_FILE"/>
</appender>
<!--DEBUG日志配置 结束-->
<!--INFO日志配置 开始-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_PATH}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="INFO-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="INFO_FILE"/>
</appender>
<!--INFO日志配置 结束-->
<!--WARN日志配置 开始-->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_PATH}/warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/warn/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="WARN-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="WARN_FILE"/>
</appender>
<!--WARN日志配置 结束-->
<!--ERROR错误日志配置 开始-->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="ERROR_FILE"/>
</appender>
<!--ERROR错误日志配置 结束-->
<root level="INFO">
<!-- 控制台日志输出 -->
<appender-ref ref="CONSOLE"/>
<!-- 全部日志不区分级别 -->
<appender-ref ref="MAIN-LOGGER-APPENDER"/>
<!-- 区分日志级别 -->
<appender-ref ref="DEBUG-APPENDER"/>
<appender-ref ref="INFO-APPENDER"/>
<appender-ref ref="WARN-APPENDER"/>
<appender-ref ref="ERROR-APPENDER"/>
</root>
<logger name="com.zq.cas" level="DEBUG"/>
<!--开发环境:打印控制台-->
<springProfile name="!dev">
<logger name="com.zq.cas.feignclient" level="INFO"/>
</springProfile>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-backend</artifactId>
<groupId>com.zq</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
......@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<version>2.5.8</version>
</parent>
<properties>
......@@ -42,6 +42,8 @@
<module>user-server</module>
<module>admin-server</module>
<module>logging-server</module>
<module>oauth-server</module>
<module>cas-server</module>
</modules>
<dependencies>
......@@ -73,7 +75,7 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<version>2020.0.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment