This commit is contained in:
付庆吉
2021-10-25 10:21:29 +08:00
parent a51797267a
commit ba708a0845
16 changed files with 279 additions and 158 deletions

View File

@ -1,27 +0,0 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.client;
import com.chinaunicom.mall.ebtp.cloud.security.starter.config.FeignClientConfiguration;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityUser;
import com.chinaunicom.mall.ebtp.cloud.security.starter.fallback.HystrixClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 连接山分的文档中心服务
*
* @author Ajaxfan
*/
@FeignClient(name = "${mall-ebtp.userinfo.id}", fallbackFactory = HystrixClientFallbackFactory.class, configuration = FeignClientConfiguration.class)
public interface UserCenterClient {
/**
* 通过附件id查询明细
*
* @param fileId
* @return
*/
@RequestMapping(method = RequestMethod.GET, value = "v1/userinfo/get")
SecurityUser getUserInfo();
}

View File

@ -9,8 +9,10 @@ public interface Constants {
public static final String TOKEN_PREFIX = "Bearer ";
public static final String CURRENT_ROLE_CODE = "currentRoleCode";
public static final String COOKIE_TOKEN_CODE = "mall3_token";
public static final String NO_AUTH = "0klui7pj90000bx04lxf";
public static final String TOKEN_EXPIRED = "90401";
public static final String REMOTE_ACCESS_FAILURE = "90500";
public static final String NO_AUTH_TOKEN = "0kluipxcr0000831ztrl";
}

View File

@ -1,65 +0,0 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.config;
import com.chinaunicom.mall.ebtp.common.config.FeignConfig;
import feign.FeignException;
import feign.Logger;
import feign.RequestTemplate;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
import java.util.GregorianCalendar;
import static feign.FeignException.errorStatus;
@Slf4j
@Configuration
public class FeignClientConfiguration extends FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
@Bean
public ErrorDecoder errorDecoder() {
return (methodKey, response) -> {
FeignException exception = errorStatus(methodKey, response);
log.error("error message: {}", exception.getMessage());
int status = response.status();
if (status >= 400 && status < 500) {// 客户端异常启用feign的重试机制
try {
Thread.sleep(3000);// 设定重试延时
} catch (InterruptedException e) {
log.error(e.getMessage());
}
return new RetryableException(response.status(), exception.getMessage(),
response.request().httpMethod(), exception, retryAfter(), response.request());
}
return exception;
};
}
/**
* @param template
*/
@Override
public void apply(RequestTemplate template) {
super.apply(template);
}
/**
* @return 请求重试
*/
private Date retryAfter() {
return GregorianCalendar.getInstance().getTime();
}
}

View File

@ -1,10 +1,12 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class AuthorityEntity {
private String roleId;

View File

@ -1,10 +1,12 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class SecurityEntity {
private String city;
@ -14,6 +16,7 @@ public class SecurityEntity {
private String userid;
private List<String> authorities;
private String orgId;
private String orgName;
private String ouName;
private String province;
private String staffOrgId;
@ -21,5 +24,9 @@ public class SecurityEntity {
private String tenantId;
private String staffId;
private String username;
private String partnerId;
private String partnerName;
private String userSource;
private String exp;
}

View File

@ -1,18 +0,0 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.fallback;
import com.chinaunicom.mall.ebtp.cloud.security.starter.client.UserCenterClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class HystrixClientFallbackFactory implements FallbackFactory<UserCenterClient> {
@Override
public UserCenterClient create(Throwable cause) {
log.error(cause.getMessage());
return new UserCenterClientFallback();
}
}

View File

@ -1,16 +0,0 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.fallback;
import com.chinaunicom.mall.ebtp.cloud.security.starter.client.UserCenterClient;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityUser;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserCenterClientFallback implements UserCenterClient {
@Override
public SecurityUser getUserInfo() {
log.info("remote access timeout");
return null;
}
}

View File

@ -1,9 +1,11 @@
package com.chinaunicom.mall.ebtp.cloud.security.starter.filter;
import com.chinaunicom.mall.ebtp.cloud.security.starter.client.UserCenterClient;
import cn.hutool.core.bean.BeanUtil;
import com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.RoleCodeAuthority;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityUser;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.service.UserInfoService;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseCacheUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
@ -38,7 +40,7 @@ import static com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants.
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private @Autowired
UserCenterClient client;
UserInfoService client;
/**
* @param request
@ -50,28 +52,35 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
log.info("--------" + request.getMethod() + "请求url- " + request.getRequestURI() + "?" + request.getQueryString());
if (!StringUtils.contains(request.getRequestURI(), "actuator/prometheus")) {
log.info("--------" + request.getMethod() + " - " + request.getRequestURI() + "?" + Optional.ofNullable(request.getQueryString()).orElse(""));
}
// 清空上下文中的缓存信息, 防止二次请求时数据异常 (如此, 每次有新的请求进入都会进行token的验证)
SecurityContextHolder.getContext().setAuthentication(null);
// 提取request头信息
final String header = request.getHeader(Constants.AUTHORIZATION_HEADER);
final String currentRoleCode = request.getHeader(Constants.CURRENT_ROLE_CODE);
final String header = request.getHeader(AUTHORIZATION_HEADER);
final String currentRoleCode = request.getHeader(CURRENT_ROLE_CODE);
boolean skip = !StringUtils.equals(request.getHeader(NO_AUTH), NO_AUTH_TOKEN);
try {
// 检查请求头是否包含 Bearer 前缀
if (StringUtils.startsWith(header, Constants.TOKEN_PREFIX)) {
setAuthentication(currentRoleCode, RegExUtils.replaceAll(header, Constants.TOKEN_PREFIX, ""));// 移除header的前缀提取出token字串
}
// 检查cookie
else {
Optional.ofNullable(request.getCookies())
.ifPresent(cookies -> Stream.of(cookies)
.filter(item -> StringUtils.equals(item.getName(), COOKIE_TOKEN_CODE)).findFirst()
.ifPresent(cookie -> setAuthentication(currentRoleCode, cookie.getValue())));
if (skip) {
// 检查请求头是否包含 Bearer 前缀
if (StringUtils.startsWith(header, Constants.TOKEN_PREFIX)) {
setAuthentication(currentRoleCode, RegExUtils.replaceAll(header, Constants.TOKEN_PREFIX, ""));// 移除header的前缀提取出token字串
}
// 检查cookie
else {
Optional.ofNullable(request.getCookies())
.flatMap(cookies ->
Stream.of(cookies)
.filter(item -> StringUtils.equals(item.getName(), COOKIE_TOKEN_CODE))
.findFirst())
.ifPresent(cookie -> setAuthentication(currentRoleCode, cookie.getValue()));
}
}
// TODO 临时放行未传递token且session中未包含access token信息的服务调用
isNullThenAssignDefault();
} catch (Exception e) {
@ -98,16 +107,17 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
* @return
*/
private Authentication getAuthentication(final String token, final String currentRoleCode) {
SecurityUser securityUser = client.getUserInfo();
BaseCacheUser userInfo = client.getUserInfo(token);
if (Objects.isNull(securityUser)) {// 对象为空, 则说明网络异常feign已熔断
if (Objects.isNull(userInfo)) {// 对象为空, 则说明网络异常feign已熔断
throw new RemoteTimeoutException(REMOTE_ACCESS_FAILURE);
}
if (Objects.isNull(securityUser.getUserId())) {// userid 为空则访问山分认证服务返回信息为null
if (Objects.isNull(userInfo.getUserId())) {// userid 为空则访问山分认证服务返回信息为null
throw new AccessDeniedException(TOKEN_EXPIRED);
}
SecurityUser securityUser = BeanUtil.toBean(userInfo, SecurityUser.class);
// 根据当前角色设定权限列表
List<RoleCodeAuthority> authorities = Optional.ofNullable(securityUser.getAuthorityList()).map(list -> {
return list.stream().filter(auth -> StringUtils.equals(auth.getRoleCode(), currentRoleCode))

View File

@ -0,0 +1,25 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.client.RestTemplate;
/**
* security 自动装配
*
* @author ajaxfan
*/
@Configuration
@EnableFeignClients(basePackages = "com.chinaunicom.mall.ebtp.cloud.userinfo.starter")
@ComponentScan(basePackages = "com.chinaunicom.mall.ebtp.cloud.userinfo.starter")
public class UserinfoStarterConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,22 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.client;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityEntity;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.config.UnifastOAuthFeignConfig;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.fallback.UnifastOAuthClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 文档中心数据服务客户端
*
* @author Ajaxfan
*/
@FeignClient(value = "${user.auth.resource.serviceId:mall-auth}",
fallbackFactory = UnifastOAuthClientFallbackFactory.class)
public interface UnifastOAuthClient {
@GetMapping("oauth/check_token")
SecurityEntity get(@RequestParam("token") String token);
}

View File

@ -0,0 +1,29 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* 通过拦截器来为header注入client id
*/
@Configuration
public class UnifastOAuthFeignConfig implements RequestInterceptor {
/* 认证中心请求客户端标识 */
private @Value("${user.auth.resource.clientId:bVS46ElU}")
String clientId;
/**
* @param template
*/
@Override
public void apply(RequestTemplate template) {
if (StringUtils.contains(template.url(), "oauth/check_token")) {
template.header("clientId", clientId);
}
}
}

View File

@ -0,0 +1,15 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.fallback;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityEntity;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.client.UnifastOAuthClient;
import org.springframework.stereotype.Component;
@Component
public class UnifastOAuthClientFallback implements UnifastOAuthClient {
@Override
public SecurityEntity get(String token) {
return null;
}
}

View File

@ -0,0 +1,18 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.fallback;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.client.UnifastOAuthClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class UnifastOAuthClientFallbackFactory implements FallbackFactory<UnifastOAuthClient> {
@Override
public UnifastOAuthClient create(Throwable cause) {
log.error("UnifastOAuthClient error : " +cause.getMessage());
return new UnifastOAuthClientFallback();
}
}

View File

@ -0,0 +1,10 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.service;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseCacheUser;
public interface UserInfoService {
BaseCacheUser getUserInfo(String token);
}

View File

@ -0,0 +1,107 @@
package com.chinaunicom.mall.ebtp.cloud.userinfo.starter.service.impl;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.AuthorityEntity;
import com.chinaunicom.mall.ebtp.cloud.security.starter.entity.SecurityEntity;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.client.UnifastOAuthClient;
import com.chinaunicom.mall.ebtp.cloud.userinfo.starter.service.UserInfoService;
import com.chinaunicom.mall.ebtp.common.base.entity.BaseCacheUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@Service
public class UserInfoServiceImpl implements UserInfoService {
private @Autowired
UnifastOAuthClient client;
// private @Autowired ObjectMapper userInfoObjectMapper;
@Override
public BaseCacheUser getUserInfo(String token) {
return convertToBusinessModel(getAuthentication(token.replaceAll("Bearer ", "")));
}
/**
* 构建用户对象模型
*
* @param raw
* @return
*/
private BaseCacheUser convertToBusinessModel(SecurityEntity raw) {
log.debug("userinfo: {}", raw);
BaseCacheUser user = new BaseCacheUser().setUserId(raw.getStaffId()).setFullName(raw.getStaffName())
.setLoginName(raw.getUsername()).setAuthorityList(filterByEBTP(raw.getAuthorityList()))
.setProvince(raw.getProvince()).setEmployeeNumber(raw.getExp()).setUserType(raw.getUserSource());
adapterUserSource(user, raw);
replenishAuthority(user);
return user;
}
/**
* 为联通用户添加默认角色
*
* @param user
*/
private void replenishAuthority(BaseCacheUser user) {
List<AuthorityEntity> authorityList = Optional.ofNullable(user.getAuthorityList()).orElseGet(ArrayList::new);
if (authorityList.isEmpty() && "0".equals(user.getUserType())) {
authorityList.add(new AuthorityEntity()
.setRoleId("20004").setRoleCode("ebtp-unicom-default")
.setRoleName("联通普通用户").setRoleScope("EBTP"));
user.setAuthorityList(authorityList);
}
}
/**
* 适配不同的用户来源,针对性的设定部门信息
*
* @param raw
*/
private void adapterUserSource(BaseCacheUser user, SecurityEntity raw) {
if ("0".equals(raw.getUserSource())) {// 联通用户
user.setOrganizationId(raw.getOrgId()).setOrganizationName(raw.getOrgName()).setDeptId(raw.getOu())
.setDeptName(raw.getOuName());
} else {
user.setOrganizationId(raw.getOu()).setOrganizationName(raw.getOuName()).setDeptId(raw.getPartnerId())
.setDeptName(raw.getPartnerName());
}
}
/**
* @param token
* @return
*/
private SecurityEntity getAuthentication(String token) {
try {
return client.get(token);
} catch (Exception e) {
log.error("Authentication Token: {}", token);
log.error(e.getMessage());
}
return new SecurityEntity();
}
/**
* 使用ebtp标识过滤数据
*
* @param list
* @return
*/
private List<AuthorityEntity> filterByEBTP(List<AuthorityEntity> list) {
return Optional.ofNullable(list).map(
ls -> list.stream().filter(auth -> "EBTP".equals(auth.getRoleScope())).collect(Collectors.toList()))
.orElseGet(() -> list);
}
}

View File

@ -5,8 +5,7 @@ import feign.RequestTemplate;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@ -23,8 +22,6 @@ import static com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants.
*/
@Configuration
@Slf4j
@ConfigurationProperties(prefix = "ebtp.cloud")
@ConditionalOnProperty(name = "ebtp.universal.feign.token.interceptor", havingValue = "true", matchIfMissing = true)
public class FeignConfig implements RequestInterceptor {
/* 白名单名单内请求不注入token */
@ -37,7 +34,8 @@ public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
log.info("--------feign-url- " + template.url());
log.info("--------feign url- " + template.url());
if (Objects.nonNull(attributes)) {
if (isNonExistsWhiteList(template.url())) {
injectToken(template, attributes);
@ -49,9 +47,11 @@ public class FeignConfig implements RequestInterceptor {
template.removeHeader(CURRENT_ROLE_CODE);
template.header(CURRENT_ROLE_CODE, currentRoleCode);
}
}
}
/**
* 非白名单内的请求都需要注入token
*
@ -60,7 +60,7 @@ public class FeignConfig implements RequestInterceptor {
*/
private boolean isNonExistsWhiteList(String url) {
if (Objects.nonNull(tokenWhiteList)) {
return tokenWhiteList.stream().filter(rule -> StringUtils.contains(url, rule)).count() == 0;
return tokenWhiteList.stream().noneMatch(rule -> StringUtils.contains(url, rule));
}
return true;
}
@ -81,12 +81,12 @@ public class FeignConfig implements RequestInterceptor {
if (StringUtils.startsWith(header, TOKEN_PREFIX)) {
template.header(AUTHORIZATION_HEADER, header);
} else {// 检查cookie
Optional.ofNullable(attributes.getRequest().getCookies()).ifPresent(cookies -> {
Stream.of(cookies).filter(item -> StringUtils.equals(item.getName(), COOKIE_TOKEN_CODE)).findFirst()
.ifPresent(token -> {
String authToken = token.getValue();
template.header(AUTHORIZATION_HEADER, String.format("%s%s", TOKEN_PREFIX, authToken));
});
Optional.ofNullable(attributes.getRequest().getCookies())
.flatMap(cookies -> Stream.of(cookies)
.filter(item -> StringUtils.equals(item.getName(), COOKIE_TOKEN_CODE)
).findFirst()).ifPresent(token -> {
String authToken = token.getValue();
template.header(AUTHORIZATION_HEADER, String.format("%s%s", TOKEN_PREFIX, authToken));
});
}
}