18 changed files with 1024 additions and 20 deletions
@ -0,0 +1,19 @@ |
|||||||
|
package com.ecell.internationalize.auth.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class UserType { |
||||||
|
/** |
||||||
|
* 用户名 |
||||||
|
*/ |
||||||
|
private String username; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户密码 |
||||||
|
*/ |
||||||
|
private String password; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package com.ecell.internationalize.auth.fallback; |
||||||
|
|
||||||
|
import com.ecell.internationalize.auth.feign.SysUserFeignClient; |
||||||
|
import com.ecell.internationalize.common.core.domain.UserLogin; |
||||||
|
import com.ecell.internationalize.common.core.exception.ServiceException; |
||||||
|
import com.ecell.internationalize.common.core.utils.locale.LocaleUtil; |
||||||
|
import net.bytebuddy.implementation.bytecode.constant.FieldConstant; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class SysUserFeignServiceFallBack implements SysUserFeignClient { |
||||||
|
@Override |
||||||
|
public UserLogin queryByUser(String username) { |
||||||
|
throw new ServiceException(LocaleUtil.getMessage("messages.fallback.info")); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void update(UserLogin userLogin) { |
||||||
|
throw new ServiceException(LocaleUtil.getMessage("messages.fallback.info")); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package com.ecell.internationalize.auth.feign; |
||||||
|
|
||||||
|
import com.ecell.internationalize.auth.fallback.SysUserFeignServiceFallBack; |
||||||
|
import com.ecell.internationalize.common.core.domain.UserLogin; |
||||||
|
import org.springframework.cloud.openfeign.FeignClient; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@FeignClient(value = "yisai-system-security",fallback = SysUserFeignServiceFallBack.class,contextId ="yisai-system-security003") |
||||||
|
public interface SysUserFeignClient { |
||||||
|
/** |
||||||
|
* 通过用户名查询用户信息 |
||||||
|
* @param userName 用户名 |
||||||
|
* @return 结果 |
||||||
|
*/ |
||||||
|
@GetMapping("sys_user/user/queryByUserName") |
||||||
|
UserLogin queryByUser(@RequestParam("userName") String userName); |
||||||
|
/** |
||||||
|
* 修改用户信息 |
||||||
|
* @param userLogin 实体对象 |
||||||
|
* @return 结果 |
||||||
|
*/ |
||||||
|
@PostMapping("sys_user/user/updateLoginStatus") |
||||||
|
void update(@RequestBody UserLogin userLogin); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
package com.ecell.internationalize.auth.service; |
||||||
|
|
||||||
|
import com.ecell.internationalize.common.core.domain.UserLogin; |
||||||
|
import com.ecell.internationalize.common.core.web.domain.R; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
public interface SysUserLoginService { |
||||||
|
/** |
||||||
|
* 账号认证 |
||||||
|
* @param username 用户名 |
||||||
|
* @param password 密码 |
||||||
|
* @return UserLogin |
||||||
|
*/ |
||||||
|
R<UserLogin> login(String username, String password); |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
package com.ecell.internationalize.auth.service.impl; |
||||||
|
|
||||||
|
import cn.hutool.http.HttpUtil; |
||||||
|
import com.ecell.internationalize.auth.feign.SysUserFeignClient; |
||||||
|
import com.ecell.internationalize.auth.service.SysUserLoginService; |
||||||
|
import com.ecell.internationalize.common.core.domain.UserLogin; |
||||||
|
import com.ecell.internationalize.common.core.enums.UserStatus; |
||||||
|
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.ip.IpUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.locale.LocaleUtil; |
||||||
|
import com.ecell.internationalize.common.core.web.domain.R; |
||||||
|
import com.ecell.internationalize.common.security.utils.SecurityUtils; |
||||||
|
import net.bytebuddy.implementation.bytecode.constant.FieldConstant; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.text.SimpleDateFormat; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class SysUserLoginServiceImpl implements SysUserLoginService { |
||||||
|
private final String DELETED="0"; |
||||||
|
@Autowired |
||||||
|
private SysUserFeignClient sysUserFeignClient; |
||||||
|
|
||||||
|
@Override |
||||||
|
public R<UserLogin> login(String username, String password) { |
||||||
|
|
||||||
|
// 用户名或密码为空
|
||||||
|
if (StringUtils.isAnyBlank(username, password)) { |
||||||
|
return R.fail(LocaleUtil.getMessage("messages.login.empty")); |
||||||
|
} |
||||||
|
UserLogin userInfo = sysUserFeignClient.queryByUser(username); |
||||||
|
//查询错误
|
||||||
|
if (StringUtils.isNull(userInfo)){ |
||||||
|
return R.fail(LocaleUtil.getMessage("messages.login.error")); |
||||||
|
} |
||||||
|
if (!SecurityUtils.matchesPassword(password, userInfo.getPassword())){ |
||||||
|
return R.fail(LocaleUtil.getMessage("messages.error.password")); |
||||||
|
} |
||||||
|
//账号被删除
|
||||||
|
if (DELETED.equals(userInfo.getDelFlag())) { |
||||||
|
return R.fail(LocaleUtil.getMessage("messages.account.delete")); |
||||||
|
} |
||||||
|
//账号被停用
|
||||||
|
if (UserStatus.DISABLE.getCode().equals(userInfo.getStatus())) { |
||||||
|
return R.fail(LocaleUtil.getMessage("messages.account.delete")); |
||||||
|
} |
||||||
|
System.out.println("userInfo:"+userInfo.getStatus()); |
||||||
|
System.out.println("校验通过"); |
||||||
|
//修改登录时间和IP
|
||||||
|
UserLogin userLogin=new UserLogin(); |
||||||
|
userLogin.setUserId(userInfo.getUserId()); |
||||||
|
userLogin.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); |
||||||
|
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" ); |
||||||
|
Date date= new Date(); |
||||||
|
String str = sdf.format(date); |
||||||
|
userLogin.setLoginDate(str); |
||||||
|
sysUserFeignClient.update(userLogin); |
||||||
|
return R.ok(userInfo); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
package com.ecell.internationalize.common.core.domain; |
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class UserLogin implements Serializable { |
||||||
|
private static final long serialVersionUID = 1L; |
||||||
|
/** |
||||||
|
* 用户唯一标识 |
||||||
|
*/ |
||||||
|
private String token; |
||||||
|
|
||||||
|
/** |
||||||
|
* 主键Id |
||||||
|
*/ |
||||||
|
private String userId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户昵称 |
||||||
|
*/ |
||||||
|
private String nickName; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户账号 |
||||||
|
*/ |
||||||
|
private String account; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户类型 |
||||||
|
*/ |
||||||
|
private String userType; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户头像 |
||||||
|
*/ |
||||||
|
private String headImg; |
||||||
|
|
||||||
|
/** |
||||||
|
* 密码 |
||||||
|
*/ |
||||||
|
private String password; |
||||||
|
|
||||||
|
/** |
||||||
|
* 帐号状态(0正常 1停用) |
||||||
|
*/ |
||||||
|
private String status; |
||||||
|
|
||||||
|
/** |
||||||
|
* 删除标识(0:已删除,1:正常) |
||||||
|
*/ |
||||||
|
private String delFlag; |
||||||
|
|
||||||
|
/** |
||||||
|
* 登录IP |
||||||
|
*/ |
||||||
|
private String loginIp; |
||||||
|
|
||||||
|
/** |
||||||
|
* 最后登录时间 |
||||||
|
*/ |
||||||
|
private String loginDate; |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建人 |
||||||
|
*/ |
||||||
|
private String createUser; |
||||||
|
/** |
||||||
|
* 创建时间 |
||||||
|
*/ |
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
||||||
|
private Date createTime; |
||||||
|
/** |
||||||
|
* 修改人 |
||||||
|
*/ |
||||||
|
private String updateUser; |
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
||||||
|
private Date updateTime; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 厂商Id |
||||||
|
*/ |
||||||
|
private String firmId; |
||||||
|
/** |
||||||
|
* 代理商Id |
||||||
|
*/ |
||||||
|
private String secondFirmId; |
||||||
|
|
||||||
|
/** |
||||||
|
* 标识(1:厂商:2:代理商) |
||||||
|
*/ |
||||||
|
private String firmFlag; |
||||||
|
/** |
||||||
|
* 登录时间 |
||||||
|
*/ |
||||||
|
private Long loginTime; |
||||||
|
|
||||||
|
/** |
||||||
|
* 过期时间 |
||||||
|
*/ |
||||||
|
private Long expireTime; |
||||||
|
|
||||||
|
/** |
||||||
|
* 权限列表 |
||||||
|
*/ |
||||||
|
private Set<String> permissions; |
||||||
|
|
||||||
|
/** |
||||||
|
* 角色列表 |
||||||
|
*/ |
||||||
|
private Set<String> roles; |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package com.ecell.internationalize.gateway.config; |
||||||
|
|
||||||
|
import com.ecell.internationalize.gateway.handler.ValidateCodeHandler; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.web.reactive.function.server.RequestPredicates; |
||||||
|
import org.springframework.web.reactive.function.server.RouterFunction; |
||||||
|
import org.springframework.web.reactive.function.server.RouterFunctions; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Configuration |
||||||
|
public class RouterFunctionConfiguration { |
||||||
|
@Autowired |
||||||
|
private ValidateCodeHandler validateCodeHandler; |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
@Bean |
||||||
|
public RouterFunction routerFunction() |
||||||
|
{ |
||||||
|
return RouterFunctions.route( |
||||||
|
RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), |
||||||
|
validateCodeHandler); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
package com.ecell.internationalize.gateway.config; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.gateway.config.GatewayProperties; |
||||||
|
import org.springframework.cloud.gateway.route.RouteLocator; |
||||||
|
import org.springframework.cloud.gateway.support.NameUtils; |
||||||
|
import org.springframework.context.annotation.Lazy; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.springframework.web.reactive.config.ResourceHandlerRegistry; |
||||||
|
import org.springframework.web.reactive.config.WebFluxConfigurer; |
||||||
|
import springfox.documentation.swagger.web.SwaggerResource; |
||||||
|
import springfox.documentation.swagger.web.SwaggerResourcesProvider; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer { |
||||||
|
/** |
||||||
|
* Swagger2默认的url后缀 |
||||||
|
*/ |
||||||
|
public static final String SWAGGER2URL = "/v2/api-docs"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 网关路由 |
||||||
|
*/ |
||||||
|
@Lazy |
||||||
|
@Autowired |
||||||
|
private RouteLocator routeLocator; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private GatewayProperties gatewayProperties; |
||||||
|
|
||||||
|
/** |
||||||
|
* 聚合其他服务接口 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public List<SwaggerResource> get() |
||||||
|
{ |
||||||
|
List<SwaggerResource> resourceList = new ArrayList<>(); |
||||||
|
List<String> routes = new ArrayList<>(); |
||||||
|
// 获取网关中配置的route
|
||||||
|
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); |
||||||
|
gatewayProperties.getRoutes().stream() |
||||||
|
.filter(routeDefinition -> routes |
||||||
|
.contains(routeDefinition.getId())) |
||||||
|
.forEach(routeDefinition -> routeDefinition.getPredicates().stream() |
||||||
|
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName())) |
||||||
|
.filter(predicateDefinition -> !"ecell-auth".equalsIgnoreCase(routeDefinition.getId())) |
||||||
|
.forEach(predicateDefinition -> resourceList |
||||||
|
.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs() |
||||||
|
.get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL))))); |
||||||
|
return resourceList; |
||||||
|
} |
||||||
|
|
||||||
|
private SwaggerResource swaggerResource(String name, String location) |
||||||
|
{ |
||||||
|
SwaggerResource swaggerResource = new SwaggerResource(); |
||||||
|
swaggerResource.setName(name); |
||||||
|
swaggerResource.setLocation(location); |
||||||
|
swaggerResource.setSwaggerVersion("2.0"); |
||||||
|
return swaggerResource; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) |
||||||
|
{ |
||||||
|
/** swagger-ui 地址 */ |
||||||
|
registry.addResourceHandler("/swagger-ui/**") |
||||||
|
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package com.ecell.internationalize.gateway.config.properties; |
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||||
|
import org.springframework.cloud.context.config.annotation.RefreshScope; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Configuration |
||||||
|
@RefreshScope |
||||||
|
@ConfigurationProperties(prefix = "security.xss") |
||||||
|
public class XssProperties { |
||||||
|
/** |
||||||
|
* Xss开关 |
||||||
|
*/ |
||||||
|
private Boolean enabled; |
||||||
|
|
||||||
|
/** |
||||||
|
* 排除路径 |
||||||
|
*/ |
||||||
|
private List<String> excludeUrls = new ArrayList<>(); |
||||||
|
|
||||||
|
public Boolean getEnabled() |
||||||
|
{ |
||||||
|
return enabled; |
||||||
|
} |
||||||
|
|
||||||
|
public void setEnabled(Boolean enabled) |
||||||
|
{ |
||||||
|
this.enabled = enabled; |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> getExcludeUrls() |
||||||
|
{ |
||||||
|
return excludeUrls; |
||||||
|
} |
||||||
|
|
||||||
|
public void setExcludeUrls(List<String> excludeUrls) |
||||||
|
{ |
||||||
|
this.excludeUrls = excludeUrls; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
package com.ecell.internationalize.gateway.filter; |
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON; |
||||||
|
import com.alibaba.fastjson2.JSONObject; |
||||||
|
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||||
|
import com.ecell.internationalize.gateway.config.properties.CaptchaProperties; |
||||||
|
import com.ecell.internationalize.gateway.service.ValidateCodeService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilter; |
||||||
|
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.core.io.buffer.DataBufferUtils; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
|
||||||
|
import java.nio.CharBuffer; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> { |
||||||
|
private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" }; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ValidateCodeService validateCodeService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private CaptchaProperties captchaProperties; |
||||||
|
|
||||||
|
private static final String CODE = "code"; |
||||||
|
|
||||||
|
private static final String UUID = "uuid"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public GatewayFilter apply(Object config) |
||||||
|
{ |
||||||
|
return (exchange, chain) -> { |
||||||
|
ServerHttpRequest request = exchange.getRequest(); |
||||||
|
|
||||||
|
// 非登录/注册请求或验证码关闭,不处理
|
||||||
|
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) |
||||||
|
{ |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
String rspStr = resolveBodyFromRequest(request); |
||||||
|
JSONObject obj = JSON.parseObject(rspStr); |
||||||
|
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); |
||||||
|
} |
||||||
|
catch (Exception e) |
||||||
|
{ |
||||||
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage()); |
||||||
|
} |
||||||
|
return chain.filter(exchange); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) |
||||||
|
{ |
||||||
|
// 获取请求体
|
||||||
|
Flux<DataBuffer> body = serverHttpRequest.getBody(); |
||||||
|
AtomicReference<String> bodyRef = new AtomicReference<>(); |
||||||
|
body.subscribe(buffer -> { |
||||||
|
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); |
||||||
|
DataBufferUtils.release(buffer); |
||||||
|
bodyRef.set(charBuffer.toString()); |
||||||
|
}); |
||||||
|
return bodyRef.get(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
package com.ecell.internationalize.gateway.filter; |
||||||
|
|
||||||
|
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.html.EscapeUtil; |
||||||
|
import com.ecell.internationalize.gateway.config.properties.XssProperties; |
||||||
|
import io.netty.buffer.ByteBufAllocator; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.core.io.buffer.*; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") |
||||||
|
public class XssFilter implements GlobalFilter, Ordered { |
||||||
|
// 跨站脚本的 xss 配置,nacos自行添加
|
||||||
|
@Autowired |
||||||
|
private XssProperties xss; |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) |
||||||
|
{ |
||||||
|
ServerHttpRequest request = exchange.getRequest(); |
||||||
|
// GET DELETE 不过滤
|
||||||
|
HttpMethod method = request.getMethod(); |
||||||
|
if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) |
||||||
|
{ |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
// 非json类型,不过滤
|
||||||
|
if (!isJsonRequest(exchange)) |
||||||
|
{ |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
// excludeUrls 不过滤
|
||||||
|
String url = request.getURI().getPath(); |
||||||
|
if (StringUtils.matches(url, xss.getExcludeUrls())) |
||||||
|
{ |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); |
||||||
|
return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) |
||||||
|
{ |
||||||
|
ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) |
||||||
|
{ |
||||||
|
@Override |
||||||
|
public Flux<DataBuffer> getBody() |
||||||
|
{ |
||||||
|
Flux<DataBuffer> body = super.getBody(); |
||||||
|
return body.buffer().map(dataBuffers -> { |
||||||
|
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); |
||||||
|
DataBuffer join = dataBufferFactory.join(dataBuffers); |
||||||
|
byte[] content = new byte[join.readableByteCount()]; |
||||||
|
join.read(content); |
||||||
|
DataBufferUtils.release(join); |
||||||
|
String bodyStr = new String(content, StandardCharsets.UTF_8); |
||||||
|
// 防xss攻击过滤
|
||||||
|
bodyStr = EscapeUtil.clean(bodyStr); |
||||||
|
// 转成字节
|
||||||
|
byte[] bytes = bodyStr.getBytes(); |
||||||
|
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); |
||||||
|
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); |
||||||
|
buffer.write(bytes); |
||||||
|
return buffer; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public HttpHeaders getHeaders() |
||||||
|
{ |
||||||
|
HttpHeaders httpHeaders = new HttpHeaders(); |
||||||
|
httpHeaders.putAll(super.getHeaders()); |
||||||
|
// 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length
|
||||||
|
httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); |
||||||
|
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
||||||
|
return httpHeaders; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
return serverHttpRequestDecorator; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否是Json请求 |
||||||
|
* |
||||||
|
* @param exchange HTTP请求 |
||||||
|
*/ |
||||||
|
public boolean isJsonRequest(ServerWebExchange exchange) |
||||||
|
{ |
||||||
|
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); |
||||||
|
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getOrder() |
||||||
|
{ |
||||||
|
return -100; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package com.ecell.internationalize.gateway.handler; |
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; |
||||||
|
import com.alibaba.csp.sentinel.slots.block.BlockException; |
||||||
|
import com.ecell.internationalize.common.core.utils.ServletUtils; |
||||||
|
import org.springframework.web.reactive.function.server.ServerResponse; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import org.springframework.web.server.WebExceptionHandler; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
public class SentinelFallbackHandler implements WebExceptionHandler { |
||||||
|
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) |
||||||
|
{ |
||||||
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) |
||||||
|
{ |
||||||
|
if (exchange.getResponse().isCommitted()) |
||||||
|
{ |
||||||
|
return Mono.error(ex); |
||||||
|
} |
||||||
|
if (!BlockException.isBlockException(ex)) |
||||||
|
{ |
||||||
|
return Mono.error(ex); |
||||||
|
} |
||||||
|
return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) |
||||||
|
{ |
||||||
|
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package com.ecell.internationalize.gateway.handler; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
import springfox.documentation.swagger.web.*; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@RestController |
||||||
|
@RequestMapping("/swagger-resources") |
||||||
|
public class SwaggerHandler { |
||||||
|
@Autowired(required = false) |
||||||
|
private SecurityConfiguration securityConfiguration; |
||||||
|
|
||||||
|
@Autowired(required = false) |
||||||
|
private UiConfiguration uiConfiguration; |
||||||
|
|
||||||
|
private final SwaggerResourcesProvider swaggerResources; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) |
||||||
|
{ |
||||||
|
this.swaggerResources = swaggerResources; |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/configuration/security") |
||||||
|
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() |
||||||
|
{ |
||||||
|
return Mono.just(new ResponseEntity<>( |
||||||
|
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), |
||||||
|
HttpStatus.OK)); |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/configuration/ui") |
||||||
|
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() |
||||||
|
{ |
||||||
|
return Mono.just(new ResponseEntity<>( |
||||||
|
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK)); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
@GetMapping("") |
||||||
|
public Mono<ResponseEntity> swaggerResources() |
||||||
|
{ |
||||||
|
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package com.ecell.internationalize.gateway.handler; |
||||||
|
|
||||||
|
import com.ecell.internationalize.common.core.exception.CaptchaException; |
||||||
|
import com.ecell.internationalize.common.core.web.domain.AjaxResult; |
||||||
|
import com.ecell.internationalize.gateway.service.ValidateCodeService; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.springframework.web.reactive.function.BodyInserters; |
||||||
|
import org.springframework.web.reactive.function.server.HandlerFunction; |
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest; |
||||||
|
import org.springframework.web.reactive.function.server.ServerResponse; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class ValidateCodeHandler implements HandlerFunction<ServerResponse> { |
||||||
|
@Autowired |
||||||
|
private ValidateCodeService validateCodeService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<ServerResponse> handle(ServerRequest serverRequest) |
||||||
|
{ |
||||||
|
AjaxResult ajax; |
||||||
|
try |
||||||
|
{ |
||||||
|
ajax = validateCodeService.createCaptcha(); |
||||||
|
} |
||||||
|
catch (CaptchaException | IOException e) |
||||||
|
{ |
||||||
|
return Mono.error(e); |
||||||
|
} |
||||||
|
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.ecell.internationalize.gateway.service; |
||||||
|
|
||||||
|
import com.ecell.internationalize.common.core.exception.CaptchaException; |
||||||
|
import com.ecell.internationalize.common.core.web.domain.AjaxResult; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
public interface ValidateCodeService { |
||||||
|
/** |
||||||
|
* 生成验证码 |
||||||
|
*/ |
||||||
|
public AjaxResult createCaptcha() throws IOException, CaptchaException; |
||||||
|
|
||||||
|
/** |
||||||
|
* 校验验证码 |
||||||
|
*/ |
||||||
|
public void checkCaptcha(String key, String value) throws CaptchaException; |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
package com.ecell.internationalize.gateway.service.impl; |
||||||
|
|
||||||
|
import com.ecell.internationalize.common.core.constant.Constants; |
||||||
|
import com.ecell.internationalize.common.core.exception.CaptchaException; |
||||||
|
import com.ecell.internationalize.common.core.utils.StringUtils; |
||||||
|
import com.ecell.internationalize.common.core.utils.sign.Base64; |
||||||
|
import com.ecell.internationalize.common.core.utils.uuid.IdUtils; |
||||||
|
import com.ecell.internationalize.common.core.web.domain.AjaxResult; |
||||||
|
import com.ecell.internationalize.common.redis.service.RedisService; |
||||||
|
import com.ecell.internationalize.gateway.config.properties.CaptchaProperties; |
||||||
|
import com.ecell.internationalize.gateway.service.ValidateCodeService; |
||||||
|
import com.google.code.kaptcha.Producer; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.util.FastByteArrayOutputStream; |
||||||
|
|
||||||
|
import javax.annotation.Resource; |
||||||
|
import javax.imageio.ImageIO; |
||||||
|
import java.awt.image.BufferedImage; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author borui |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class ValidateCodeServiceImpl implements ValidateCodeService { |
||||||
|
@Resource(name = "captchaProducer") |
||||||
|
private Producer captchaProducer; |
||||||
|
|
||||||
|
@Resource(name = "captchaProducerMath") |
||||||
|
private Producer captchaProducerMath; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private RedisService redisService; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private CaptchaProperties captchaProperties; |
||||||
|
|
||||||
|
/** |
||||||
|
* 生成验证码 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public AjaxResult createCaptcha() throws IOException, CaptchaException |
||||||
|
{ |
||||||
|
AjaxResult ajax = AjaxResult.success(); |
||||||
|
boolean captchaOnOff = captchaProperties.getEnabled(); |
||||||
|
ajax.put("captchaOnOff", captchaOnOff); |
||||||
|
if (!captchaOnOff) |
||||||
|
{ |
||||||
|
return ajax; |
||||||
|
} |
||||||
|
|
||||||
|
// 保存验证码信息
|
||||||
|
String uuid = IdUtils.simpleUUID(); |
||||||
|
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; |
||||||
|
|
||||||
|
String capStr = null, code = null; |
||||||
|
BufferedImage image = null; |
||||||
|
|
||||||
|
String captchaType = captchaProperties.getType(); |
||||||
|
// 生成验证码
|
||||||
|
if ("math".equals(captchaType)) |
||||||
|
{ |
||||||
|
String capText = captchaProducerMath.createText(); |
||||||
|
capStr = capText.substring(0, capText.lastIndexOf("@")); |
||||||
|
code = capText.substring(capText.lastIndexOf("@") + 1); |
||||||
|
image = captchaProducerMath.createImage(capStr); |
||||||
|
} |
||||||
|
else if ("char".equals(captchaType)) |
||||||
|
{ |
||||||
|
capStr = code = captchaProducer.createText(); |
||||||
|
image = captchaProducer.createImage(capStr); |
||||||
|
} |
||||||
|
|
||||||
|
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); |
||||||
|
// 转换流信息写出
|
||||||
|
FastByteArrayOutputStream os = new FastByteArrayOutputStream(); |
||||||
|
try |
||||||
|
{ |
||||||
|
ImageIO.write(image, "jpg", os); |
||||||
|
} |
||||||
|
catch (IOException e) |
||||||
|
{ |
||||||
|
return AjaxResult.error(e.getMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
ajax.put("uuid", uuid); |
||||||
|
ajax.put("img", Base64.encode(os.toByteArray())); |
||||||
|
return ajax; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 校验验证码 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void checkCaptcha(String code, String uuid) throws CaptchaException |
||||||
|
{ |
||||||
|
if (StringUtils.isEmpty(code)) |
||||||
|
{ |
||||||
|
throw new CaptchaException("验证码不能为空"); |
||||||
|
} |
||||||
|
if (StringUtils.isEmpty(uuid)) |
||||||
|
{ |
||||||
|
throw new CaptchaException("验证码已失效"); |
||||||
|
} |
||||||
|
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; |
||||||
|
String captcha = redisService.getCacheObject(verifyKey); |
||||||
|
redisService.deleteObject(verifyKey); |
||||||
|
|
||||||
|
if (!code.equalsIgnoreCase(captcha)) |
||||||
|
{ |
||||||
|
throw new CaptchaException("验证码错误"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue