初始化提交

This commit is contained in:
2021-01-20 18:30:23 +08:00
commit 3eb965f380
208 changed files with 8103 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
target/
**/target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
*.iml
.idea
modules/log
modules/logs
.project
.settings
.classpath
logs
out
application.yml
bootstrap.yml

16
auth/authentication-client/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
target/
logs/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

View File

@ -0,0 +1,83 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>authentication-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>authentication-client</name>
<description>Demo Oauth2 project for Spring Cloud Oauth2 Authentication Client</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.0.0.RC2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>provided</scope>
</dependency>
<!--oauth2认证-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
package com.springboot.cloud.auth.client.config;
import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Configuration
@ConditionalOnClass(Feign.class)
/****
* 需要修改成OKHTTP的客户端需要在配置文件增加
* feign.httpclient.enabled=false
feign.okhttp.enabled=true
*/
public class FeignOkHttpConfig {
private int feignOkHttpReadTimeout = 60;
private int feignConnectTimeout = 60;
private int feignWriteTimeout = 120;
//如果此处定义拦截器,则服务之间相互调用控制不了,此模块仅用于远程授权
@Bean
public okhttp3.OkHttpClient okHttpClient() {
return new okhttp3.OkHttpClient.Builder()
.readTimeout(feignOkHttpReadTimeout, TimeUnit.SECONDS)
.connectTimeout(feignConnectTimeout, TimeUnit.SECONDS)
.writeTimeout(feignWriteTimeout, TimeUnit.SECONDS)
// .connectionPool(new ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit)) //自定义链接池
// .addInterceptor(XXXXXXXInterceptor) //自定义拦截器
.build();
}
}

View File

@ -0,0 +1,54 @@
package com.springboot.cloud.auth.client.provider;
import com.springboot.cloud.common.core.entity.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@Component
@FeignClient(name = "authentication-server", fallback = AuthProvider.AuthProviderFallback.class, path = "authentication-server")
//@FeignClient(name = "authentication-server", fallback = AuthProvider.AuthProviderFallback.class)
public interface AuthProvider {
/**
* 调用签权服务,判断用户是否有权限
*
* @param authentication
* @param url
* @param companyId
* @return <pre>
* Result:
* {
* code:"000000"
* mesg:"请求成功"
* data: true/false
* }
* </pre>
*/
@PostMapping(value = "/auth/permission")
Result auth(@RequestParam("companyId") String companyId, @RequestHeader(HttpHeaders.AUTHORIZATION) String authentication, @RequestParam("url") String url);
@Component
class AuthProviderFallback implements AuthProvider {
/**
* 降级统一返回无权限
*
* @param authentication
* @param url
* @param companyId
* @return <pre>
* Result:
* {
* code:"-1"
* mesg:"系统异常"
* }
* </pre>
*/
@Override
public Result auth(String authentication, String url, String companyId) {
return Result.fail();
}
}
}

View File

@ -0,0 +1,60 @@
package com.springboot.cloud.auth.client.service;
import com.springboot.cloud.common.core.entity.vo.Result;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
public interface IAuthService {
/**
* 调用签权服务,判断用户是否有权限
*
* @param authentication
* @param url
* @return Result
*/
Result authenticate(String companyId,String authentication, String url);
/**
* 判断url是否在忽略的范围内
* 只要是配置中的开头即返回true
*
* @param url
* @return
*/
boolean ignoreAuthentication(String url);
/**
* 查看签权服务器返回结果有权限返回true
*
* @param authResult
* @return
*/
boolean hasPermission(Result authResult);
/**
* 调用签权服务,判断用户是否有权限
*
*
* @param companyId
* @param authentication
* @param url
* @return true/false
*/
boolean hasPermission(String companyId,String authentication, String url);
/**
* 是否无效authentication
*
* @param authentication
* @return
*/
boolean invalidJwtAccessToken(String authentication);
/**
* 从认证信息中提取jwt token 对象
*
* @param jwtToken toke信息 header.payload.signature
* @return Jws对象
*/
Jws<Claims> getJwt(String jwtToken);
}

View File

@ -0,0 +1,92 @@
package com.springboot.cloud.auth.client.service.impl;
import com.springboot.cloud.auth.client.provider.AuthProvider;
import com.springboot.cloud.auth.client.service.IAuthService;
import com.springboot.cloud.common.core.entity.vo.Result;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.stream.Stream;
@Service
@Slf4j
public class AuthService implements IAuthService {
/**
* Authorization认证开头是"bearer "
*/
private static final String BEARER = "Bearer ";
@Autowired
private AuthProvider authProvider;
/**
* jwt token 密钥主要用于token解析签名验证
*/
@Value("${spring.security.oauth2.jwt.signingKey}")
private String signingKey;
/**
* 不需要网关签权的url配置(/oauth,/open)
* 默认/oauth开头是不需要的
*/
@Value("${gate.ignore.authentication.startWith}")
private String ignoreUrls = "/oauth";
@Override
public Result authenticate(String companyId,String authentication, String url) {
return authProvider.auth(companyId,authentication, url);
}
@Override
public boolean ignoreAuthentication(String url) {
return Stream.of(this.ignoreUrls.split(",")).anyMatch(ignoreUrl -> url.startsWith(StringUtils.trim(ignoreUrl)));
}
@Override
public boolean hasPermission(Result authResult) {
log.debug("签权结果:{}", authResult.getData());
return authResult.isSuccess() && (boolean) authResult.getData();
}
@Override
public boolean hasPermission(String companyId, String authentication, String url) {
// 如果请求未携带token信息, 直接权限
if (StringUtils.isBlank(authentication) || !authentication.startsWith(BEARER)) {
log.error("user token is null");
return Boolean.FALSE;
}
//token是否有效在网关进行校验无效/过期等
if (invalidJwtAccessToken(authentication)) {
return Boolean.FALSE;
}
//从认证服务获取是否有权限,远程调用
return hasPermission(authenticate(companyId,authentication, url));
}
@Override
public Jws<Claims> getJwt(String jwtToken) {
if (jwtToken.startsWith(BEARER)) {
jwtToken = StringUtils.substring(jwtToken, BEARER.length());
}
return Jwts.parser() //得到DefaultJwtParser
.setSigningKey(signingKey.getBytes()) //设置签名的秘钥
.parseClaimsJws(jwtToken);
}
@Override
public boolean invalidJwtAccessToken(String authentication) {
// 是否无效true表示无效
boolean invalid = Boolean.TRUE;
try {
getJwt(authentication);
invalid = Boolean.FALSE;
} catch (SignatureException | ExpiredJwtException | MalformedJwtException ex) {
log.error("user token error :{}", ex.getMessage());
}
return invalid;
}
}

View File

@ -0,0 +1,111 @@
package com.springboot.cloud.auth.client.service.impl;
import com.springboot.cloud.auth.client.provider.AuthProvider;
import com.springboot.cloud.auth.client.service.IAuthService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Field;
public class AuthServiceTest {
@InjectMocks
IAuthService authService;
@Mock
AuthProvider authProvider;
private static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiXSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJleHAiOjEzNTcyMDIxNjM3LCJhdXRob3JpdGllcyI6WyJBRE1JTiJdLCJqdGkiOiI4MzcyMzI0Ny00ZDA5LTQ0YjYtYTNlOS01OGUzYzZiMGUzYjIiLCJjbGllbnRfaWQiOiJ0ZXN0X2NsaWVudCJ9.IkOtKapS5PJLKU1NfiqVSCgsQngE7qGnIx1NziJMvVA";
private static final String BEARER = "Bearer ";
@Before
public void before() throws NoSuchFieldException, IllegalAccessException {
authService = new AuthService();
setInstancePrivateField(authService, "signingKey", "123456");
setInstancePrivateField(authService, "ignoreUrls", "/oauth,/open");
MockitoAnnotations.initMocks(this);
}
private void setInstancePrivateField(Object instance, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field signingKeyField = instance.getClass().getDeclaredField(fieldName);
signingKeyField.setAccessible(true);
signingKeyField.set(instance, value);
}
@Test
public void testInvalidJwtAccessToken_假如授权服务通过给定密钥生成了token_当输入该token组成的authentication_那么返回false表示token有效() {
Assert.assertFalse(authService.invalidJwtAccessToken(BEARER + VALID_TOKEN));
}
@Test
public void testInvalidJwtAccessToken_假如_当输入随机字串_那么返回true表示token无效() {
String authentication = BEARER + "im random string";
Assert.assertTrue(authService.invalidJwtAccessToken(authentication));
}
@Test
public void testInvalidJwtAccessToken_假如有人获取用户token_当输入篡改过playload中信息_那么返回true表示token无效() {
String authentication = BEARER + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.111eyJGc2VyX25hbWUiOiJ6aG91dGFvbyIsInNjb3BlIjpbInJlYWQiXSwib3JnYW5pemF0aW9uIjoiemhvdXRhb28iLCJleHAiOjE1Mjc0NTM5NDQsImF1dGhvcml0aWVzIjpbIkFETUlOIiwiSVQiXSwianRpIjoiZTZiNzM5ZmUtYWEzZC00Y2RmLWIxZjUtNzZkMmVlMjU0ODU1IiwiY2xpZW50X2lkIjoidGVzdF9jbGllbnQifQ.l6PQrs98zT40H6Ad4NHE7NSXyeWnMn-ZhURw3zO-EfE";
Assert.assertTrue(authService.invalidJwtAccessToken(authentication));
}
@Test
public void testInvalidJwtAccessToken_假如有人获取用户token_当输入token去掉了signature_那么返回true表示token无效() {
String authentication = BEARER + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aG91dGFvbyIsInNjb3BlIjpbInJlYWQiXSwib3JnYW5pemF0aW9uIjoiemhvdXRhb28iLCJleHAiOjE1Mjc0NTM5NDQsImF1dGhvcml0aWVzIjpbIkFETUlOIiwiSVQiXSwianRpIjoiZTZiNzM5ZmUtYWEzZC00Y2RmLWIxZjUtNzZkMmVlMjU0ODU1IiwiY2xpZW50X2lkIjoidGVzdF9jbGllbnQifQ";
Assert.assertTrue(authService.invalidJwtAccessToken(authentication));
}
@Test
public void testAuthenticate_假如用户authentication正确且有对请求url有权限_当用户请求该url_那么返回成功有权限() {
// when(authProvider.auth(BEARER + VALID_TOKEN, "/users", , "POST")).thenReturn(Result.success(true));
Assert.assertTrue((Boolean) authService.authenticate(BEARER + VALID_TOKEN, "/users", "POST").getData());
}
@Test
public void testAuthenticate_假如用户authentication正确且有对请求url只有POST权限_当用户请求该url的GET_那么返回成功无权限() {
// when(authProvider.auth(BEARER + VALID_TOKEN, "/users", , "GET")).thenReturn(Result.success(false));
Assert.assertFalse((Boolean) authService.authenticate(BEARER + VALID_TOKEN, "/users", "GET").getData());
}
@Test
public void testHasPermission_假如_当传入无效token_那么返回无权限() {
// Assert.assertFalse(authService.hasPermission(, "invalid token", "/users"));
}
@Test
public void testHasPermission_假如用户authentication正确且有对请求url有权限_当用户请求该url_那么返回成功有权限() {
// when(authProvider.auth(BEARER + VALID_TOKEN, "/users", , "POST")).thenReturn(Result.success(true));
// Assert.assertTrue(authService.hasPermission(, BEARER + VALID_TOKEN, "/users"));
}
@Test
public void testHasPermission_假如用户authentication正确且有对请求url只有POST权限_当用户请求该url的GET_那么返回成功无权限() {
// when(authProvider.auth(BEARER + VALID_TOKEN, "/users", , "GET")).thenReturn(Result.success(false));
// Assert.assertFalse(authService.hasPermission(, BEARER + VALID_TOKEN, "/users"));
}
@Test
public void testIgnoreAuthentication_假如配置的忽略前缀为oauth和open_当用户请求以oauth开头的url_那么返回返回true() {
Assert.assertTrue(authService.ignoreAuthentication("/oauth/token?test=123"));
}
@Test
public void testIgnoreAuthentication_假如配置的忽略前缀为oauth和open_当用户请求以open开头的url_那么返回返回true() {
Assert.assertTrue(authService.ignoreAuthentication("/open/"));
}
@Test
public void testIgnoreAuthentication_假如配置的忽略前缀为oauth和open_当用户请求以test开头的url_那么返回返回true() {
Assert.assertFalse(authService.ignoreAuthentication("/test"));
}
@Test
public void testIgnoreAuthentication_假如配置的忽略前缀为oauth和open_当用户请求以open结尾的url_那么返回返回true() {
Assert.assertFalse(authService.ignoreAuthentication("/test/open"));
}
}

16
auth/authentication-server/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
target/
logs/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

View File

@ -0,0 +1,107 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>authentication-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- <packaging>jar</packaging>-->
<name>authentication-server</name>
<description>Demo Oauth2 project for Spring Cloud Oauth2 Authentication Server</description>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--oauth2认证-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<!--Swagger2 - RESTful API文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--jetcache缓存 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
</dependency>
<!-- 独立运行依赖-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- tomcat部署运行依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
<!-- tomcat容器运行构建工具-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.4.RELEASE</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
<finalName>authentication-server</finalName>
</build>
</project>

View File

@ -0,0 +1,24 @@
package com.springboot.cloud.auth.authentication;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCreateCacheAnnotation
public class Oauth2AuthenticationApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Oauth2AuthenticationApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Oauth2AuthenticationApplication.class);
}
}

View File

@ -0,0 +1,104 @@
package com.springboot.cloud.auth.authentication.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.auth.authentication.events.BusReceiver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 消息设置
*/
@Configuration
@Slf4j
public class BusConfig {
private static final String EXCHANGE_NAME = "spring-boot-exchange";
private static final String ROUTING_KEY = "organization-popedom";
@Value("${spring.application.name}")
private String appName;
/**
* 配置监听队列mq只能直接监听队列不能直接监听交换机
* @return 队列
*/
@Bean
Queue queue() {
String queueName = new Base64UrlNamingStrategy(appName + ".").generateName();
log.info("queue name:{}", queueName);
return new Queue(queueName, false);
}
/**
* 采用主题交换机
* @return 主题交换机
*/
@Bean
TopicExchange exchange() {
log.info("exchange:{}", EXCHANGE_NAME);
return new TopicExchange(EXCHANGE_NAME);
}
/**
* 将队列绑定到主题交换机上以router_key作为键
* @param queue
* @param exchange
* @return
*/
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
log.info("binding {} to {} with {}", queue, exchange, ROUTING_KEY);
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
/**
* 设置消息处理监听器
* @param connectionFactory mq连接工厂用于配置mq服务器地址端口连接关闭或者断开监听器等等
* @param messageListenerAdapter 消息监听适配器用于收到消息后对消息进行处理或者是代理到其他对象进行处理的适配器默认处理方法为handleMessage
* @param queue 消息接收队列,可以配置多个队列
* @return 消息监听容器,可以设置消费者数量、最大最小数量、批量消费等等
*/
@Bean
SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory, MessageListenerAdapter messageListenerAdapter, Queue queue) {
log.info("init simpleMessageListenerContainer {}", queue.getName());
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueueNames(queue.getName());
container.setMessageListener(messageListenerAdapter);
return container;
}
/**
* 设置消息监听适配器
* @param busReceiver 处理消息的代理对象
* @param messageConverter 消息转换器此处用Jackson作为json转换工具类
* @return
*/
@Bean
MessageListenerAdapter messageListenerAdapter(BusReceiver busReceiver, MessageConverter messageConverter) {
log.info("new listener");
return new MessageListenerAdapter(busReceiver, messageConverter);
}
/**
* 消息转换器
* @return Jackson转换器
*/
@Bean
public MessageConverter messageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
}
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.auth.authentication.config;
/*
**********************************************
* DATE PERSON REASON
* 2020/12/7 FXY Created
**********************************************
*/
import com.springboot.cloud.common.web.interceptor.FeignBasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InterceptConfig {
@Bean
public FeignBasicAuthRequestInterceptor interceptor(){
return new FeignBasicAuthRequestInterceptor();
}
}

View File

@ -0,0 +1,19 @@
package com.springboot.cloud.auth.authentication.config;
import com.springboot.cloud.auth.authentication.service.PopedomService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
class LoadResourceDefine {
@Autowired
private PopedomService popedomService;
@PostConstruct
public void resourceConfigAttributes() {
popedomService.loadPopedom();
}
}

View File

@ -0,0 +1,56 @@
package com.springboot.cloud.auth.authentication.config;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-24 FXY Created
**********************************************
*/
import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.lang.management.ManagementFactory;
import java.util.Set;
@Configuration
@Slf4j
public class NacosRegisterConfig implements ApplicationRunner {
@Autowired(required = false)
private NacosAutoServiceRegistration registration;
private Integer port;
public NacosRegisterConfig() {
try {
this.port = Integer.parseInt(getTomcatPort());
} catch (Exception e) {
log.error("获取tomcat端口出错了原因{}", e.toString());
}
}
@Override
public void run(ApplicationArguments args) {
if (registration != null && port != null) {
registration.setPort(port);
registration.start();
}
}
//获取tomcat端口
private String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return port;
}
}

View File

@ -0,0 +1,51 @@
package com.springboot.cloud.auth.authentication.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${spring.security.oauth2.jwt.signingKey}")
private String signingKey;
@Override
public void configure(ResourceServerSecurityConfigurer resourceServerSecurityConfigurer) {
resourceServerSecurityConfigurer
.tokenStore(tokenStore())
.resourceId("WEBS");
}
@Override
public void configure(HttpSecurity http) throws Exception {
log.debug("HttpSecurity configure method");
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.anyRequest().authenticated();
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
}

View File

@ -0,0 +1,26 @@
package com.springboot.cloud.auth.authentication.entity;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-29 FXY Created
**********************************************
*/
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
public class License {
private String companyId;
private String franchiserId;
private String applicationCode;
private String grade;
private Date expireDate;
private boolean isForever;
private String state;
}

View File

@ -0,0 +1,32 @@
package com.springboot.cloud.auth.authentication.entity;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-29 FXY Created
**********************************************
*/
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Popedom {
private String id;
private String applicationCode;
private String name;
private String parentId;
private String url;
private String icon;
private String isMenu;
private String description;
private String path;
private String redirect;
private String component;
private String title;
private boolean alwaysShow;
private boolean hidden;
private String companyId;
private Integer orderNo;
}

View File

@ -0,0 +1,30 @@
package com.springboot.cloud.auth.authentication.events;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import com.springboot.cloud.auth.authentication.service.PopedomService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 消息接收者收到消息后进行处理内部反射调用默认回调方法handleMessage,方法参数由消息
* 发送方和接收方约定,方法参数尽可能作为一个对象,多个参数向上封装成一个对象
*
* @see MessageListenerAdapter
* @see org.springframework.amqp.core.MessageListener
*/
@Component
@Slf4j
public class BusReceiver {
@Autowired
private PopedomService popedomService;
public void handleMessage(List<Popedom> popedoms) {
log.info("Received Message:<{}>", popedoms);
popedomService.savePopedom(popedoms);
}
}

View File

@ -0,0 +1,24 @@
package com.springboot.cloud.auth.authentication.provider;
import com.springboot.cloud.auth.authentication.entity.License;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import com.springboot.cloud.common.core.entity.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "organization", fallback = PopedomProviderFallback.class, path = "organization")
//@FeignClient(name = "organization", fallback = PopedomProviderFallback.class)
public interface PopedomProvider {
@GetMapping(value = "/company/getAllPopedom")
Result<List<Popedom>> popedoms();
@GetMapping(value = "/company/getPopedom")
Result<List<Popedom>> popedoms(@RequestParam("companyId") String companyId,@RequestParam("username")String username);
@GetMapping(value = "/company/license")
Result<License> license(@RequestParam("companyId")String companyId,@RequestParam("applicationCode")String applicationCode);
}

View File

@ -0,0 +1,36 @@
package com.springboot.cloud.auth.authentication.provider;
import com.springboot.cloud.auth.authentication.entity.License;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import com.springboot.cloud.common.core.entity.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
@Slf4j
public class PopedomProviderFallback implements PopedomProvider {
@Override
public Result<List<Popedom>> popedoms() {
log.error("认证服务启动时加载资源异常!未加载到资源!");
return Result.fail();
}
@Override
public Result<List<Popedom>> popedoms(String companyId, String username) {
log.error("认证服务查询用户异常!查询用户资源为空!");
return Result.success(new ArrayList<Popedom>());
}
@Override
public Result<License> license(String companyId, String applicationCode) {
log.error("认证服务查询企业应用有效期异常!企业应用过期!");
return Result.success(new License());
}
}

View File

@ -0,0 +1,34 @@
package com.springboot.cloud.auth.authentication.rest;
import com.springboot.cloud.auth.authentication.service.AuthenticationService;
import com.springboot.cloud.common.core.entity.vo.Result;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@Api("auth")
@Slf4j
public class AuthenticationController {
@Autowired
AuthenticationService authenticationService;
@ApiOperation(value = "权限验证", notes = "根据用户token访问的url和method判断用户是否有权限访问")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "url", value = "访问的url", required = true, dataType = "string"),
@ApiImplicitParam(paramType = "query", name = "method", value = "访问的method", required = true, dataType = "string")
})
@ApiResponses(@ApiResponse(code = 200, message = "处理成功", response = Result.class))
@PostMapping(value = "/auth/permission")
public Result decide(@RequestParam String url, HttpServletRequest request) {
boolean decide = authenticationService.decide(new HttpServletRequestAuthWrapper(request, url));
return Result.success(decide);
}
}

View File

@ -0,0 +1,22 @@
package com.springboot.cloud.auth.authentication.rest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class HttpServletRequestAuthWrapper extends HttpServletRequestWrapper {
private String url;
/**
* @param url
*/
public HttpServletRequestAuthWrapper(HttpServletRequest request, String url) {
super(request);
this.url = url;
}
@Override
public String getServletPath() {
return this.url;
}
}

View File

@ -0,0 +1,17 @@
package com.springboot.cloud.auth.authentication.service;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
@Service
public interface AuthenticationService {
/**
* 校验权限
*
* @param authRequest
* @return 是否有权限
*/
boolean decide(HttpServletRequest authRequest);
}

View File

@ -0,0 +1,31 @@
package com.springboot.cloud.auth.authentication.service;
import com.google.common.base.Objects;
import lombok.Getter;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@Getter
public class NewMvcRequestMatcher extends MvcRequestMatcher {
private String pattern;
public NewMvcRequestMatcher(HandlerMappingIntrospector introspector, String pattern) {
super(introspector, pattern);
this.pattern = pattern;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NewMvcRequestMatcher that = (NewMvcRequestMatcher) o;
return Objects.equal(pattern, that.pattern);
}
@Override
public int hashCode() {
return Objects.hashCode(pattern);
}
}

View File

@ -0,0 +1,52 @@
package com.springboot.cloud.auth.authentication.service;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Service
public interface PopedomService {
/**
* 动态新增更新权限
*
* @param popedoms
*/
void savePopedom(List<Popedom> popedoms);
/**
* 动态删除权限
*
* @param popedom
*/
void removePopedom(Popedom popedom);
/**
* 加载权限资源数据
*/
void loadPopedom();
/**
* 根据url和method查询到对应的权限信息
*
* @param authRequest
* @return
*/
List<ConfigAttribute> findConfigAttributesByUrl(HttpServletRequest authRequest);
/**
* 根据用户名查询 该用户所拥有的角色对应的资源信息
* @param companyId
* @param username
* @return
*/
List<Popedom> queryByUsername(String companyId,String username);
/**
* 资源对应的应用是否过期
*/
boolean license(String companyId, String applicationCode);
}

View File

@ -0,0 +1,84 @@
package com.springboot.cloud.auth.authentication.service.impl;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import com.springboot.cloud.auth.authentication.service.AuthenticationService;
import com.springboot.cloud.auth.authentication.service.PopedomService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
@Service
@Slf4j
public class AuthenticationServiceImpl implements AuthenticationService {
@Autowired
private PopedomService popedomService;
/**
* @param authRequest 访问的url,method
* @return 有权限true, 无权限或全局资源中未找到请求url返回否
*/
@Override
public boolean decide(HttpServletRequest authRequest) {
log.debug("正在访问的url是:{}", authRequest.getServletPath());
//获取用户认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//获取此url访问对应的权限资源信息
List<ConfigAttribute> configAttributes = popedomService.findConfigAttributesByUrl(authRequest);
if (configAttributes.size() == 0) {
//如果未匹配到资源,则返回未授权
return false;
} else if (configAttributes.size() == 1) {
//默认授权所有资源,所有资源都会匹配,如果仅匹配到一个资源,则必定不是功能接口
return true;
} else {
String companyId = Optional.ofNullable(authRequest.getParameter("companyId")).orElseGet(String::new);
// String companyId = Optional.ofNullable(UserContextHolder.getInstance().getCurrentCompany()).orElseGet(String::new);
//获取此访问用户所有角色拥有的权限资源
List<Popedom> userPopedoms = findPopedomByUsername(companyId, authentication.getName());
//用户拥有权限资源 与 url要求的资源进行对比
return isMatch(companyId,configAttributes, userPopedoms);
}
}
/**
* url对应资源与用户拥有资源进行匹配
* 前端传入所在公司
* @param userPopedoms
* @return
*/
public boolean isMatch(String companyId,List<ConfigAttribute> urlConfigAttributes, List<Popedom> userPopedoms) {
//首先检查用户所属角色是否有资源权限
Optional<Popedom> optionalPopedom = userPopedoms.stream().filter(popedom -> urlConfigAttributes.contains(new SecurityConfig(popedom.getId()))).findAny();
if(optionalPopedom.isPresent()){
//再检查用户所在公司应用是否过期
return popedomService.license(companyId, optionalPopedom.orElseGet(Popedom::new).getApplicationCode());
}else{
return false;
}
}
/**
* 根据用户所被授予的角色,查询到用户所拥有的资源
*
* @param username
* @return
*/
private List<Popedom> findPopedomByUsername(String companyId, String username) {
//用户被授予的角色资源
List<Popedom> popedoms = popedomService.queryByUsername(companyId, username);
if (log.isDebugEnabled()) {
log.debug("用户被授予角色的资源数量是:{}, 资源集合信息为:{}", popedoms.size(), popedoms);
}
return popedoms;
}
}

View File

@ -0,0 +1,127 @@
package com.springboot.cloud.auth.authentication.service.impl;
import com.springboot.cloud.auth.authentication.entity.License;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import com.springboot.cloud.auth.authentication.provider.PopedomProvider;
import com.springboot.cloud.auth.authentication.service.NewMvcRequestMatcher;
import com.springboot.cloud.auth.authentication.service.PopedomService;
import com.springboot.cloud.common.core.entity.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class PopedomServiceImpl implements PopedomService {
@Autowired
private HandlerMappingIntrospector mvcHandlerMappingIntrospector;
@Autowired
private PopedomProvider popedomProvider;
/**
* 系统中权限分组集合
* key:companyId,value:{key:url,value:config}
*/
private static final Map<String, Map<RequestMatcher, ConfigAttribute>> popedomConfigAttributes = new HashMap<>();
@Override
public synchronized void savePopedom(List<Popedom> popedoms) {
popedoms.stream().forEach(popedom -> {
if (popedomConfigAttributes.containsKey(popedom.getCompanyId())) {
//如果包含company对应的权限表则更新
Map<RequestMatcher, ConfigAttribute> attributeMap = popedomConfigAttributes.get(popedom.getCompanyId());
attributeMap.put(this.newMvcRequestMatcher(popedom.getUrl()), new SecurityConfig(popedom.getId()));
} else {
//如果不包含company对应的权限表则新增
Map<RequestMatcher, ConfigAttribute> map = new HashMap<>();
map.put(this.newMvcRequestMatcher(popedom.getUrl()), new SecurityConfig(popedom.getId()));
popedomConfigAttributes.put(popedom.getCompanyId(), map);
}
});
log.info("popedomConfigAttributes size:{}", popedomConfigAttributes.size());
}
@Override
public synchronized void removePopedom(Popedom popedom) {
if (popedomConfigAttributes.containsKey(popedom.getCompanyId())) {
Map<RequestMatcher, ConfigAttribute> attributeMap = popedomConfigAttributes.get(popedom.getCompanyId());
attributeMap.remove(this.newMvcRequestMatcher(popedom.getUrl()));
}
log.info("resourceConfigAttributes size:{}", popedomConfigAttributes.size());
}
@Override
public synchronized void loadPopedom() {
Result<List<Popedom>> resourcesResult = popedomProvider.popedoms();
if (resourcesResult.isFail()) {
System.exit(1);
}
List<Popedom> popedoms = resourcesResult.getData();
//先根据公司分组,然后放入内存
Map<String, Map<RequestMatcher, ConfigAttribute>> map = popedoms.stream().filter(a -> null != a.getUrl() && !a.getUrl().equals("")).collect(Collectors.groupingBy(Popedom::getCompanyId, Collectors.toMap(a -> newMvcRequestMatcher(a.getUrl()), a -> new SecurityConfig(a.getId()))));
popedomConfigAttributes.putAll(map);
log.debug("init resourceConfigAttributes:{}", popedomConfigAttributes);
}
@Override
public List<ConfigAttribute> findConfigAttributesByUrl(HttpServletRequest authRequest) {
String companyId = Optional.ofNullable(authRequest.getParameter("companyId")).orElseGet(String::new);
// String companyId = Optional.ofNullable(UserContextHolder.getInstance().getCurrentCompany()).orElseGet(String::new);
if (popedomConfigAttributes.containsKey(companyId)) {
Map<RequestMatcher, ConfigAttribute> attributeMap = popedomConfigAttributes.get(companyId);
return attributeMap.keySet().stream()
.filter(requestMatcher -> requestMatcher.matches(authRequest))
.map(requestMatcher -> attributeMap.get(requestMatcher))
.peek(urlConfigAttribute -> log.debug("url在资源池中配置{}", urlConfigAttribute.getAttribute()))
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public List<Popedom> queryByUsername(String companyId, String username) {
return popedomProvider.popedoms(companyId, username).getData();
}
@Override
public boolean license(String companyId, String applicationCode) {
Result<License> licenseResult = popedomProvider.license(companyId, applicationCode);
if (licenseResult.isFail()) {
return false;
}
License data = licenseResult.getData();
if (null == data) {
return false;
}
if (data.isForever()) {
return true;
}
if (data.getExpireDate().getTime() > new Date().getTime()) {
return true;
} else {
return false;
}
}
/**
* 创建RequestMatcher
*
* @param url
* @return
*/
private MvcRequestMatcher newMvcRequestMatcher(String url) {
return new NewMvcRequestMatcher(mvcHandlerMappingIntrospector, url);
}
}

View File

@ -0,0 +1,54 @@
package com.springboot.cloud.auth.authentication;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import java.util.List;
import java.util.stream.Collectors;
public class ApplicationTests {
@Test
public void testMethod() {
List<SimpleGrantedAuthority> authorities;
SimpleGrantedAuthority admin = new SimpleGrantedAuthority("ADMIN");
SimpleGrantedAuthority user = new SimpleGrantedAuthority("USER");
authorities = Lists.newArrayList(admin, user);
authorities.stream().map(authority -> authority.getAuthority()).collect(Collectors.toList());
}
@Test
public void testMethod1() throws JsonProcessingException {
// Resource resource = new Resource();
// resource.setCode("user_manager:all");
// resource.setMethod("GET");
// resource.setUrl("/users/a");
//
// ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//
// System.out.println(objectMapper.writeValueAsString(resource));
}
@Test
public void testMatcher() {
MvcRequestMatcher mvcRequestMatcher = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users/{id}");
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("GET", "/users/1")));
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("GET", "/users/aaa")));
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("GET", "/users")));
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("POST", "/users/1")));
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("PUT", "/users/1")));
System.out.println(mvcRequestMatcher.matches(new MockHttpServletRequest("DELETE", "/users/1")));
}
}

View File

@ -0,0 +1,31 @@
package com.springboot.cloud.auth.authentication.service.impl;
import com.google.common.collect.Sets;
import com.springboot.cloud.auth.authentication.entity.Popedom;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.security.access.SecurityConfig;
import java.util.Set;
public class AuthenticationServiceImplTest {
@Test
public void testIsMatch_假如存在如上资源信息_当给定包含在资源信息时_那么返回true() {
AuthenticationServiceImpl authenticationServiceImpl = new AuthenticationServiceImpl();
Popedom popedom = new Popedom();
popedom.setApplicationCode("user_manager:view");
Set<Popedom> popedoms = Sets.newHashSet(popedom);
// Assert.assertTrue(authenticationServiceImpl.isMatch(new SecurityConfig("user_manager:view"), popedoms));
}
@Test
public void testIsMatch_假如存在如上资源信息_当给不包含在资源信息时_那么返回false() {
AuthenticationServiceImpl authenticationServiceImpl = new AuthenticationServiceImpl();
Popedom popedom = new Popedom();
popedom.setApplicationCode("user_manager:manager");
Set<Popedom> popedoms = Sets.newHashSet(popedom);
// Assert.assertFalse(authenticationServiceImpl.isMatch(new SecurityConfig("user_manager:view"), popedoms));
}
}

View File

@ -0,0 +1,73 @@
package com.springboot.cloud.auth.authentication.service.impl;
import com.springboot.cloud.auth.authentication.provider.PopedomProvider;
import com.springboot.cloud.auth.authentication.rest.HttpServletRequestAuthWrapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.access.ConfigAttribute;
//@RunWith(SpringRunner.class)
//@SpringBootTest
public class PopedomServiceImplTest {
/* private Map<RequestMatcher, ConfigAttribute> resourceConfigAttributes = new HashMap() {
{
MvcRequestMatcher mvcRequestMatcher1 = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users");
mvcRequestMatcher1.setMethod(HttpMethod.resolve("POST"));
MvcRequestMatcher mvcRequestMatcher2 = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users/{id}");
mvcRequestMatcher2.setMethod(HttpMethod.resolve("PUT"));
MvcRequestMatcher mvcRequestMatcher3 = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users/{id}");
mvcRequestMatcher3.setMethod(HttpMethod.resolve("DELETE"));
MvcRequestMatcher mvcRequestMatcher4 = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users/{id}");
mvcRequestMatcher4.setMethod(HttpMethod.resolve("GET"));
MvcRequestMatcher mvcRequestMatcher5 = new MvcRequestMatcher(new HandlerMappingIntrospector(), "/users/{id}/order");
mvcRequestMatcher5.setMethod(HttpMethod.resolve("GET"));
put(mvcRequestMatcher1, new SecurityConfig("user_manager:btn_add"));
put(mvcRequestMatcher2, new SecurityConfig("user_manager:btn_edit"));
put(mvcRequestMatcher3, new SecurityConfig("user_manager:btn_del"));
put(mvcRequestMatcher4, new SecurityConfig("user_manager:view"));
put(mvcRequestMatcher5, new SecurityConfig("user_order:view"));
}
};*/
@InjectMocks
private PopedomServiceImpl popedomServiceImpl;
@Mock
private PopedomProvider popedomProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
@Ignore
public void testGetConfigAttributesByUrl_假如存在如上资源信息_当请求不存在method的资源时_那么返回NONEXISTENT_URL() {
// ConfigAttribute attributesByUrl = popedomServiceImpl
// .findConfigAttributesByUrl("", new HttpServletRequestAuthWrapper(new MockHttpServletRequest(), "/users/1/order"));
// Assert.assertEquals("NONEXISTENT_URL", attributesByUrl.getAttribute());
}
@Test
@Ignore
public void testGetConfigAttributesByUrl_假如存在如上资源信息_当请求url存在参数时_那么返回匹配的资源信息() {
// ConfigAttribute attributesByUrl = popedomServiceImpl
// .findConfigAttributesByUrl("", new HttpServletRequestAuthWrapper(new MockHttpServletRequest(), "/users/1/order"));
// Assert.assertEquals("NONEXISTENT_URL", attributesByUrl.getAttribute());
}
@Test
@Ignore
public void testGetConfigAttributesByUrl_假如存在如上资源信息_当请求存在的资源时_那么返回url和method都匹配的资源信息() {
// ConfigAttribute attributesByUrl = popedomServiceImpl
// .findConfigAttributesByUrl("", new HttpServletRequestAuthWrapper(new MockHttpServletRequest(), "/users"));
// Assert.assertEquals("user_manager:btn_add", attributesByUrl.getAttribute());
}
}

16
auth/authorization-server/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
target/
logs/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

View File

@ -0,0 +1,114 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>authorization-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- <packaging>jar</packaging>-->
<name>authorization-server</name>
<description>Demo Oauth2 project for Spring Cloud Oauth2 Authorization Server</description>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--oauth2认证-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 独立运行依赖-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- tomcat容器部署运行依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.4.RELEASE</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
<finalName>authorization-server</finalName>
</build>
</project>

View File

@ -0,0 +1,23 @@
package com.springboot.auth.authorization;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Oauth2AuthorizationApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Oauth2AuthorizationApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Oauth2AuthorizationApplication.class);
}
}

View File

@ -0,0 +1,179 @@
package com.springboot.auth.authorization.config;
import com.google.common.collect.Lists;
import com.springboot.auth.authorization.exception.CustomWebResponseExceptionTranslator;
import com.springboot.auth.authorization.oauth2.enhancer.CustomTokenEnhancer;
import com.springboot.auth.authorization.oauth2.granter.MobileTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.InMemoryApprovalStore;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
// @Qualifier("dataSource")
// @Autowired
// DataSource dataSource;
@Autowired
@Qualifier("userDetailsService")
UserDetailsService userDetailsService;
/**
* jwt 对称加密密钥
*/
@Value("${spring.security.oauth2.jwt.signingKey}")
private String signingKey;
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// 支持将client参数放在header或body中
oauthServer.allowFormAuthenticationForClients();
//认证策略默认为denyAll()
oauthServer.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端信息从数据库中读取对应oauth_client_details表
// clients.jdbc(dataSource);
clients.inMemory().withClient("test_client").secret("$2a$10$2szDKjvKHJCWE6YQNznogOeQF3USZHmCYj1fG7YbfK.vnTgNKLzri").scopes("read")
.authorizedGrantTypes("client_credentials", "authorization_code", "mobile", "password", "refresh_token")
.redirectUris("http://www.baidu.com").accessTokenValiditySeconds(3600).refreshTokenValiditySeconds(3600);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 配置token的数据源、自定义的tokenServices等信息,配置身份认证器配置认证方式TokenStoreTokenGranterOAuth2RequestFactory
endpoints.tokenStore(tokenStore())
.authorizationCodeServices(authorizationCodeServices())
.approvalStore(approvalStore())
.exceptionTranslator(customExceptionTranslator())
.tokenEnhancer(tokenEnhancerChain())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
//update by joe_chen add granter
.tokenGranter(tokenGranter(endpoints));
}
/**
* 自定义OAuth2异常处理
*
* @return CustomWebResponseExceptionTranslator
*/
@Bean
public WebResponseExceptionTranslator<OAuth2Exception> customExceptionTranslator() {
return new CustomWebResponseExceptionTranslator();
}
/**
* 授权信息持久化实现
* 密码模式无用
* @return JdbcApprovalStore
*/
@Bean
public ApprovalStore approvalStore() {
InMemoryApprovalStore memoryApprovalStore = new InMemoryApprovalStore();
Approval approval = new Approval("1", "test_client", "read", 3600, Approval.ApprovalStatus.APPROVED);
memoryApprovalStore.addApprovals(Arrays.asList(approval));
// return new JdbcApprovalStore(dataSource);
return memoryApprovalStore;
}
/**
* 授权码模式持久化授权码code
* 密码模式无用
* @return JdbcAuthorizationCodeServices
*/
@Bean
protected AuthorizationCodeServices authorizationCodeServices() {
// 授权码存储等处理方式类使用jdbc操作oauth_code表
InMemoryAuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
// return new JdbcAuthorizationCodeServices(dataSource);
return authorizationCodeServices;
}
/**
* token的持久化
*
* @return JwtTokenStore
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
/**
* 自定义token
*
* @return tokenEnhancerChain
*/
@Bean
public TokenEnhancerChain tokenEnhancerChain() {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter()));
return tokenEnhancerChain;
}
/**
* jwt token的生成配置
*
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
/**
* 配置自定义的granter,手机号验证码登陆
*
* @param endpoints
* @return
*/
public TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = Lists.newArrayList(endpoints.getTokenGranter());
granters.add(new MobileTokenGranter(
authenticationManager,
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(granters);
}
}

View File

@ -0,0 +1,21 @@
package com.springboot.auth.authorization.config;
/*
**********************************************
* DATE PERSON REASON
* 2020/12/7 FXY Created
**********************************************
*/
import com.springboot.cloud.common.web.interceptor.FeignBasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InterceptConfig {
@Bean
public FeignBasicAuthRequestInterceptor interceptor(){
return new FeignBasicAuthRequestInterceptor();
}
}

View File

@ -0,0 +1,56 @@
package com.springboot.auth.authorization.config;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-24 FXY Created
**********************************************
*/
import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.lang.management.ManagementFactory;
import java.util.Set;
@Configuration
@Slf4j
public class NacosRegisterConfig implements ApplicationRunner {
@Autowired(required = false)
private NacosAutoServiceRegistration registration;
private Integer port;
public NacosRegisterConfig() {
try {
this.port = Integer.parseInt(getTomcatPort());
} catch (Exception e) {
log.error("获取tomcat端口出错了原因{}", e.toString());
}
}
@Override
public void run(ApplicationArguments args) {
if (registration != null && port != null) {
registration.setPort(port);
registration.start();
}
}
//获取tomcat端口
private String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return port;
}
}

View File

@ -0,0 +1,83 @@
package com.springboot.auth.authorization.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.springboot.auth.authorization.oauth2.granter.MobileAuthenticationProvider;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@EnableWebSecurity
public class WebServerSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
@Autowired
@Qualifier("mobileUserDetailsService")
private UserDetailsService mobileUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
/**
* 注入自定义的userDetailsService实现获取用户信息设置密码加密方式
*
* @param authenticationManagerBuilder
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
// 设置手机验证码登陆的AuthenticationProvider
authenticationManagerBuilder.authenticationProvider(mobileAuthenticationProvider());
}
/**
* 将 AuthenticationManager 注册为 bean , 方便配置 oauth server 的时候使用
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 创建手机验证码登陆的AuthenticationProvider
*
* @return mobileAuthenticationProvider
*/
@Bean
public MobileAuthenticationProvider mobileAuthenticationProvider() {
MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider(this.mobileUserDetailsService);
mobileAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return mobileAuthenticationProvider;
}
}

View File

@ -0,0 +1,24 @@
package com.springboot.auth.authorization.entity;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-29 FXY Created
**********************************************
*/
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Company {
private String companyId;
private String userId;
private String organizationId;
private String empNo;
private String position;
private Integer isDefault;
private Integer state;
private String mobile;
}

View File

@ -0,0 +1,31 @@
package com.springboot.auth.authorization.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
private Date registryTime;
private String identity;
private String nickName;
private String photo;
private String lastModifier;
private Date lastModifyTime;
private Double state;
private String mobile;
private String email;
private String introduce;
private String avatar;
private String signature;
private String password;
private String username;
}

View File

@ -0,0 +1,38 @@
package com.springboot.auth.authorization.exception;
import com.springboot.cloud.common.core.exception.ErrorType;
import lombok.Getter;
@Getter
public enum AuthErrorType implements ErrorType {
INVALID_REQUEST("040001", "无效请求"),
INVALID_CLIENT("040002", "无效client_id"),
INVALID_GRANT("040003", "无效授权"),
INVALID_SCOPE("040004", "无效scope"),
INVALID_TOKEN("040005", "无效token"),
INSUFFICIENT_SCOPE("040010", "授权不足"),
REDIRECT_URI_MISMATCH("040020", "redirect url不匹配"),
ACCESS_DENIED("040030", "拒绝访问"),
METHOD_NOT_ALLOWED("040040", "不支持该方法"),
SERVER_ERROR("040050", "权限服务错误"),
UNAUTHORIZED_CLIENT("040060", "未授权客户端"),
UNAUTHORIZED("040061", "未授权"),
UNSUPPORTED_RESPONSE_TYPE("040070", " 支持的响应类型"),
UNSUPPORTED_GRANT_TYPE("040071", "不支持的授权类型");
/**
* 错误类型码
*/
private String code;
/**
* 错误类型描述信息
*/
private String mesg;
AuthErrorType(String code, String mesg) {
this.code = code;
this.mesg = mesg;
}
}

View File

@ -0,0 +1,20 @@
package com.springboot.auth.authorization.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.springboot.cloud.common.core.entity.vo.Result;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
@EqualsAndHashCode(callSuper = true)
@Data
@JsonSerialize(using = CustomOauthExceptionSerializer.class)
class CustomOauthException extends OAuth2Exception {
private final Result result;
CustomOauthException(OAuth2Exception oAuth2Exception) {
super(oAuth2Exception.getSummary(), oAuth2Exception);
this.result = Result.fail(AuthErrorType.valueOf(oAuth2Exception.getOAuth2ErrorCode().toUpperCase()), oAuth2Exception);
}
}

View File

@ -0,0 +1,18 @@
package com.springboot.auth.authorization.exception;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
public CustomOauthExceptionSerializer() {
super(CustomOauthException.class);
}
@Override
public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeObject(value.getResult());
}
}

View File

@ -0,0 +1,16 @@
package com.springboot.auth.authorization.exception;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e) {
OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
return ResponseEntity.status(oAuth2Exception.getHttpErrorCode())
.body(new CustomOauthException(oAuth2Exception));
}
}

View File

@ -0,0 +1,11 @@
package com.springboot.auth.authorization.exception;
import com.springboot.cloud.common.web.exception.DefaultGlobalExceptionHandlerAdvice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandlerAdvice extends DefaultGlobalExceptionHandlerAdvice {
}

View File

@ -0,0 +1,49 @@
package com.springboot.auth.authorization.oauth2;
import com.springboot.auth.authorization.entity.Company;
import com.springboot.auth.authorization.entity.User;
import com.springboot.auth.authorization.oauth2.authority.CustomGrantedAuthority;
import com.springboot.auth.authorization.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String payload) {
User user = userService.getUserByUsernameOrMobile(payload);
log.info("load user by username :{}", user.toString());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(), //加了密的密码
user.getState() == 1, //账号是否可用
user.getState() == 1, //账号未过期
true, //密码未过期
user.getState() == 1, //账户未锁定
this.obtainGrantedCompany(user));
}
/**
* 获得登录者所有的公司信息
*
* @return
*/
protected Set<CustomGrantedAuthority> obtainGrantedCompany(User user) {
List<Company> companies = userService.getUserAllCompany(user.getId());
return companies.stream().map(companyUser -> new CustomGrantedAuthority(companyUser.getCompanyId(), companyUser.getIsDefault() == 1)).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,42 @@
package com.springboot.auth.authorization.oauth2;
import com.springboot.auth.authorization.entity.User;
import com.springboot.auth.authorization.provider.SmsCodeProvider;
import com.springboot.auth.authorization.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.util.Collections;
/**
* 手机验证码登陆, 用户相关获取
*/
@Slf4j
@Service("mobileUserDetailsService")
public class MobileUserDetailsService extends CustomUserDetailsService {
@Autowired
private UserService userService;
@Autowired
private SmsCodeProvider smsCodeProvider;
@Override
public UserDetails loadUserByUsername(String payload) {
User user = userService.getUserByUsernameOrMobile(payload);
log.info("load user by mobile:{}", user.toString());
// 如果为mobile模式从短信服务中获取验证码动态密码
String credentials = smsCodeProvider.getSmsCode(payload, "LOGIN");
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
credentials,
user.getState() == 1 ? true : false,
user.getState() == 1 ? true : false,
user.getState() == 1 ? true : false,
user.getState() == 1 ? true : false,
super.obtainGrantedCompany(user));
}
}

View File

@ -0,0 +1,36 @@
package com.springboot.auth.authorization.oauth2.authority;
/*
**********************************************
* DATE PERSON REASON
* 2021-01-05 FXY Created
**********************************************
*/
import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
/**
* 根据getter方法进行序列化的
*/
@AllArgsConstructor
public class CustomGrantedAuthority implements GrantedAuthority {
private final String company;
private boolean isDefault;
/**
* 后期可换为角色编号
*
* @return
*/
@Override
public String getAuthority() {
return company;
}
public boolean isDefault() {
return isDefault;
}
}

View File

@ -0,0 +1,25 @@
package com.springboot.auth.authorization.oauth2.enhancer;
import com.google.common.collect.Maps;
import com.springboot.auth.authorization.oauth2.authority.CustomGrantedAuthority;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = Maps.newHashMap();
additionalInfo.put("companies", authentication.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}

View File

@ -0,0 +1,16 @@
package com.springboot.auth.authorization.oauth2.granter;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetailsService;
public class MobileAuthenticationProvider extends DaoAuthenticationProvider {
public MobileAuthenticationProvider(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
@Override
public boolean supports(Class<?> authentication) {
return MobileAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,11 @@
package com.springboot.auth.authorization.oauth2.granter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
public class MobileAuthenticationToken extends UsernamePasswordAuthenticationToken {
public MobileAuthenticationToken(Authentication authenticationToken) {
super(authenticationToken.getPrincipal(), authenticationToken.getCredentials());
}
}

View File

@ -0,0 +1,53 @@
package com.springboot.auth.authorization.oauth2.granter;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
public class MobileTokenGranter extends ResourceOwnerPasswordTokenGranter {
private static final String GRANT_TYPE = "mobile";
private AuthenticationManager authenticationManager;
public MobileTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
super(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String username = parameters.get("no");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("no");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(userAuth);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(mobileAuthenticationToken);
} catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
} catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, mobileAuthenticationToken);
}
}

View File

@ -0,0 +1,21 @@
package com.springboot.auth.authorization.provider;
import com.springboot.auth.authorization.entity.Company;
import com.springboot.auth.authorization.entity.User;
import com.springboot.cloud.common.core.entity.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "organization", fallback = OrganizationProviderFallback.class,path = "organization")
//@FeignClient(name = "organization", fallback = OrganizationProviderFallback.class)
public interface OrganizationProvider {
@GetMapping(value = "/user/getUserByUsernameOrMobile")
Result<User> getUserByUsernameOrMobile(@RequestParam("payload")String payload);
@GetMapping(value = "/company/getUserAllCompany")
Result<List<Company>> getUserAllCompany(@RequestParam("userId")String userId);
}

View File

@ -0,0 +1,23 @@
package com.springboot.auth.authorization.provider;
import com.springboot.auth.authorization.entity.Company;
import com.springboot.auth.authorization.entity.User;
import com.springboot.cloud.common.core.entity.vo.Result;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
@Component
public class OrganizationProviderFallback implements OrganizationProvider {
@Override
public Result<User> getUserByUsernameOrMobile(String payload) {
return Result.success(new User());
}
@Override
public Result<List<Company>> getUserAllCompany(String userId) {
return Result.success(new HashSet<Company>());
}
}

View File

@ -0,0 +1,19 @@
package com.springboot.auth.authorization.provider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* todo 实现短信验证码的服务
*/
//@FeignClient(name = "sms", fallback = OrganizationProviderFallback.class,path="sms")
public interface SmsCodeProvider {
/**
* @param mobile
* @return
*/
@GetMapping(value = "/sms/{mobile}")
String getSmsCode(@PathVariable("mobile") String mobile, @RequestParam("businessType") String businessType);
}

View File

@ -0,0 +1,18 @@
package com.springboot.auth.authorization.provider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class SmsCodeProviderFallback implements SmsCodeProvider {
@Autowired
PasswordEncoder passwordEncoder;
@Override
public String getSmsCode(String mobile, String businessType) {
// 该类为mock, 目前暂时没有sms的服务
return passwordEncoder.encode("123456");
}
}

View File

@ -0,0 +1,22 @@
package com.springboot.auth.authorization.service;
import com.springboot.auth.authorization.entity.Company;
import com.springboot.auth.authorization.entity.User;
import java.util.List;
public interface UserService {
/**
* 通过账号或者手机号得到用户信息
* @param payload 账号或者手机号
* @return
*/
User getUserByUsernameOrMobile(String payload);
/**
* 通过用户编号得到所有所属公司
*/
List<Company> getUserAllCompany(String userId);
}

View File

@ -0,0 +1,27 @@
package com.springboot.auth.authorization.service.impl;
import com.springboot.auth.authorization.entity.Company;
import com.springboot.auth.authorization.entity.User;
import com.springboot.auth.authorization.provider.OrganizationProvider;
import com.springboot.auth.authorization.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private OrganizationProvider organizationProvider;
@Override
public User getUserByUsernameOrMobile(String payload) {
return organizationProvider.getUserByUsernameOrMobile(payload).getData();
}
@Override
public List<Company> getUserAllCompany(String userId) {
return organizationProvider.getUserAllCompany(userId).getData();
}
}

View File

@ -0,0 +1,15 @@
package com.springboot.auth.authorization;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class ApplicationTests {
@Test
public void contextLoads() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("test_secret"));
}
}

68
auth/pom.xml Normal file
View File

@ -0,0 +1,68 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>auth</name>
<description>Demo Auth project for Spring Boot</description>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>xin-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modules>
<module>authentication-server</module>
<module>authentication-client</module>
<module>authorization-server</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>[2.2.4,)</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!--Redis缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

25
common/core/.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

40
common/core/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>core</name>
<description>Demo Core project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<!--Swagger2 - RESTful API文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,167 @@
package com.springboot.cloud.common.core.entity.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.springboot.cloud.common.core.exception.BaseException;
import com.springboot.cloud.common.core.exception.ErrorType;
import com.springboot.cloud.common.core.exception.SystemErrorType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import java.time.Instant;
import java.time.ZonedDateTime;
@ApiModel(description = "rest请求的返回模型所有rest正常都返回该类的对象")
@Getter
public class Result<T> {
public static final String SUCCESSFUL_CODE = "000000";
public static final String SUCCESSFUL_MESG = "处理成功";
@ApiModelProperty(value = "处理结果code", required = true)
private String code;
@ApiModelProperty(value = "处理结果描述信息")
private String msg;
@ApiModelProperty(value = "请求结果生成时间戳")
private Instant time;
@ApiModelProperty(value = "处理结果数据信息")
@JsonInclude(JsonInclude.Include.NON_NULL)
private T data;
public Result() {
this.time = ZonedDateTime.now().toInstant();
}
/**
* @param errorType
*/
public Result(ErrorType errorType) {
this.code = errorType.getCode();
this.msg = errorType.getMesg();
this.time = ZonedDateTime.now().toInstant();
}
/**
* @param errorType
* @param data
*/
public Result(ErrorType errorType, T data) {
this(errorType);
this.data = data;
}
/**
* 内部使用,用于构造成功的结果
*
* @param code
* @param msg
* @param data
*/
private Result(String code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
this.time = ZonedDateTime.now().toInstant();
}
/**
* 快速创建成功结果并返回结果数据
*
* @param data
* @return Result
*/
public static Result success(Object data) {
return new Result<>(SUCCESSFUL_CODE, SUCCESSFUL_MESG, data);
}
/**
* 快速创建成功结果
*
* @return Result
*/
public static Result success() {
return success(null);
}
/**
* 系统异常类没有返回数据
*
* @return Result
*/
public static Result fail() {
return new Result(SystemErrorType.SYSTEM_ERROR);
}
/**
* 系统异常类没有返回数据
*
* @param baseException
* @return Result
*/
public static Result fail(BaseException baseException) {
return fail(baseException, null);
}
/**
* 系统异常类并返回结果数据
*
* @param data
* @return Result
*/
public static Result fail(BaseException baseException, Object data) {
return new Result<>(baseException.getErrorType(), data);
}
/**
* 系统异常类并返回结果数据
*
* @param errorType
* @param data
* @return Result
*/
public static Result fail(ErrorType errorType, Object data) {
return new Result<>(errorType, data);
}
/**
* 系统异常类并返回结果数据
*
* @param errorType
* @return Result
*/
public static Result fail(ErrorType errorType) {
return Result.fail(errorType, null);
}
/**
* 系统异常类并返回结果数据
*
* @param data
* @return Result
*/
public static Result fail(Object data) {
return new Result<>(SystemErrorType.SYSTEM_ERROR, data);
}
/**
* 成功code=000000
*
* @return true/false
*/
@JsonIgnore
public boolean isSuccess() {
return SUCCESSFUL_CODE.equals(this.code);
}
/**
* 失败
*
* @return true/false
*/
@JsonIgnore
public boolean isFail() {
return !isSuccess();
}
}

View File

@ -0,0 +1,32 @@
package com.springboot.cloud.common.core.exception;
import lombok.Getter;
@Getter
public class BaseException extends RuntimeException {
/**
* 异常对应的错误类型
*/
private final ErrorType errorType;
/**
* 默认是系统异常
*/
public BaseException() {
this.errorType = SystemErrorType.SYSTEM_ERROR;
}
public BaseException(ErrorType errorType) {
this.errorType = errorType;
}
public BaseException(ErrorType errorType, String message) {
super(message);
this.errorType = errorType;
}
public BaseException(ErrorType errorType, String message, Throwable cause) {
super(message, cause);
this.errorType = errorType;
}
}

View File

@ -0,0 +1,17 @@
package com.springboot.cloud.common.core.exception;
public interface ErrorType {
/**
* 返回code
*
* @return
*/
String getCode();
/**
* 返回mesg
*
* @return
*/
String getMesg();
}

View File

@ -0,0 +1,11 @@
package com.springboot.cloud.common.core.exception;
/**
* Created by zhoutaoo on 2018/6/2.
*/
public class ServiceException extends BaseException {
//TODO 对业务异常的返回码进行校验,规范到一定范围内
}

View File

@ -0,0 +1,34 @@
package com.springboot.cloud.common.core.exception;
import lombok.Getter;
@Getter
public enum SystemErrorType implements ErrorType {
SYSTEM_ERROR("-1", "系统异常"),
SYSTEM_BUSY("000001", "系统繁忙,请稍候再试"),
GATEWAY_NOT_FOUND_SERVICE("010404", "服务未找到"),
GATEWAY_ERROR("010500", "网关异常"),
GATEWAY_CONNECT_TIME_OUT("010002", "网关超时"),
ARGUMENT_NOT_VALID("020000", "请求参数校验不通过"),
INVALID_TOKEN("020001", "无效token"),
UPLOAD_FILE_SIZE_LIMIT("020010", "上传文件大小超过限制"),
DUPLICATE_PRIMARY_KEY("030000","唯一键冲突");
/**
* 错误类型码
*/
private String code;
/**
* 错误类型描述信息
*/
private String mesg;
SystemErrorType(String code, String mesg) {
this.code = code;
this.mesg = mesg;
}
}

View File

@ -0,0 +1,84 @@
package com.springboot.cloud.common.core.util;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* {"companies":[{"default":true,"authority":"1"},{"default":false,"authority":"2"}],"user_name":"admin","scope":["read"],"exp":1610100088,"authorities":["1","2"],"jti":"nt9bTYbUe8IzuEgfAz70nwaMveI","client_id":"test_client"}
*/
/**
* 用户上下文
*/
public class UserContextHolder {
private ThreadLocal<Map<String, String>> threadLocal;
private UserContextHolder() {
this.threadLocal = new ThreadLocal<>();
}
/**
* 创建实例
*
* @return
*/
public static UserContextHolder getInstance() {
return SingletonHolder.sInstance;
}
/**
* 静态内部类单例模式
* 单例初使化
*/
private static class SingletonHolder {
private static final UserContextHolder sInstance = new UserContextHolder();
}
/**
* 用户上下文中放入信息
*
* @param map
*/
public void setContext(Map<String, String> map) {
threadLocal.set(map);
}
/**
* 获取上下文中的信息
*
* @return
*/
public Map<String, String> getContext() {
return threadLocal.get();
}
/**
* 获取上下文中的用户名
*
* @return
*/
public String getUsername() {
return Optional.ofNullable(threadLocal.get()).orElse(Maps.newHashMap()).get("user_name");
}
/**
* 获取上下文中用户登录的公司
*
* @return
*/
public String getCurrentCompany() {
return Optional.ofNullable(threadLocal.get()).orElse(Maps.newHashMap()).get("company");
}
/**
* 清空上下文
*/
public void clear() {
threadLocal.remove();
}
}

46
common/pom.xml Normal file
View File

@ -0,0 +1,46 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>common</name>
<description>Demo Common project for Spring Boot</description>
<modules>
<module>core</module>
<module>web</module>
</modules>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<!--使用 lombok 简化 Java 代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

102
common/web/pom.xml Normal file
View File

@ -0,0 +1,102 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>web</name>
<description>Demo Web project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--spring web相关包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr1</version>
</dependency>
<!--mybatis plus依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!--Redis缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!--开发相关-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--测试框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>10.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,43 @@
package com.springboot.cloud.common.web.entity.form;
import com.springboot.cloud.common.web.entity.po.BasePo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@ApiModel
@Slf4j
@Data
public class BaseForm<T extends BasePo> {
/**
* 用户名
*/
private String username;
/**
* Form转化为Po进行后续业务处理
*
* @param clazz
* @return
*/
public T toPo(Class<T> clazz) {
T t = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(this, t);
return t;
}
/**
* Form转化为Po进行后续业务处理
*
* @param id
* @param clazz
* @return
*/
public T toPo(String id, Class<T> clazz) {
T t = BeanUtils.instantiateClass(clazz);
t.setId(id);
BeanUtils.copyProperties(this, t);
return t;
}
}

View File

@ -0,0 +1,44 @@
package com.springboot.cloud.common.web.entity.form;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.springboot.cloud.common.web.entity.param.BaseParam;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@ApiModel
@Slf4j
@Data
public class BaseQueryForm<P extends BaseParam> extends BaseForm {
/**
* 分页查询的参数,当前页数
*/
private long current = 1;
/**
* 分页查询的参数,当前页面每页显示的数量
*/
private long size = 10;
/**
* Form转化为Param
*
* @param clazz
* @return
*/
public P toParam(Class<P> clazz) {
P p = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(this, p);
return p;
}
/**
* 从form中获取page参数用于分页查询参数
*
* @return
*/
public Page getPage() {
return new Page(this.getCurrent(), this.getSize());
}
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.common.web.entity.param;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.Data;
import java.util.Date;
@Data
public class BaseParam<T extends BasePo> {
private Date createdTimeStart;
private Date createdTimeEnd;
public QueryWrapper<T> build() {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
queryWrapper.ge(null != this.createdTimeStart, "created_time", this.createdTimeStart)
.le(null != this.createdTimeEnd, "created_time", this.createdTimeEnd);
return queryWrapper;
}
}

View File

@ -0,0 +1,29 @@
package com.springboot.cloud.common.web.entity.po;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class BasePo implements Serializable {
public final static String DEFAULT_USERNAME = "system";
@TableId(type = IdType.ID_WORKER_STR)
private String id;
@TableField(fill = FieldFill.INSERT)
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private Date createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updatedTime;
}

View File

@ -0,0 +1,13 @@
package com.springboot.cloud.common.web.entity.vo;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class BaseVo<T extends BasePo> implements Serializable {
private String id;
}

View File

@ -0,0 +1,59 @@
package com.springboot.cloud.common.web.exception;
import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.cloud.common.core.exception.BaseException;
import com.springboot.cloud.common.core.exception.SystemErrorType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartException;
@Slf4j
public class DefaultGlobalExceptionHandlerAdvice {
@ExceptionHandler(value = {MissingServletRequestParameterException.class})
public Result missingServletRequestParameterException(MissingServletRequestParameterException ex) {
log.error("missing servlet request parameter exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.ARGUMENT_NOT_VALID);
}
@ExceptionHandler(value = {MultipartException.class})
public Result uploadFileLimitException(MultipartException ex) {
log.error("upload file size limit:{}", ex.getMessage());
return Result.fail(SystemErrorType.UPLOAD_FILE_SIZE_LIMIT);
}
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public Result serviceException(MethodArgumentNotValidException ex) {
log.error("service exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.ARGUMENT_NOT_VALID, ex.getBindingResult().getFieldError().getDefaultMessage());
}
@ExceptionHandler(value = {DuplicateKeyException.class})
public Result duplicateKeyException(DuplicateKeyException ex) {
log.error("primary key duplication exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.DUPLICATE_PRIMARY_KEY);
}
@ExceptionHandler(value = {BaseException.class})
public Result baseException(BaseException ex) {
log.error("base exception:{}", ex.getMessage());
return Result.fail(ex.getErrorType());
}
@ExceptionHandler(value = {Exception.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result exception() {
return Result.fail();
}
@ExceptionHandler(value = {Throwable.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result throwable() {
return Result.fail();
}
}

View File

@ -0,0 +1,36 @@
package com.springboot.cloud.common.web.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.springboot.cloud.common.core.util.UserContextHolder;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import java.time.ZonedDateTime;
import java.util.Date;
@Slf4j
public class PoMetaObjectHandler implements MetaObjectHandler {
/**
* 获取当前访问用户为空返回默认system
*
* @return
*/
private String getCurrentUsername() {
return StringUtils.defaultIfBlank(UserContextHolder.getInstance().getUsername(), BasePo.DEFAULT_USERNAME);
}
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName("createdBy", getCurrentUsername(), metaObject);
this.setInsertFieldValByName("createdTime", Date.from(ZonedDateTime.now().toInstant()), metaObject);
this.updateFill(metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName("updatedBy", getCurrentUsername(), metaObject);
this.setUpdateFieldValByName("updatedTime", Date.from(ZonedDateTime.now().toInstant()), metaObject);
}
}

View File

@ -0,0 +1,46 @@
package com.springboot.cloud.common.web.interceptor;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-04 FXY Created
**********************************************
*/
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import java.util.Collection;
import java.util.Map;
@Slf4j
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
public FeignBasicAuthRequestInterceptor(){
log.info("========================加载自定义拦截器=========================");
}
//服务间调用头
private static final String SERVER_CLIENT_TOKEN="server-client-token";
//服务间调用秘钥
private static final String SERVER_CLIENT_SECRET="server";
//网关调用服务头
private static final String X_CLIENT_TOKEN="x-client-token";
@Override
public void apply(RequestTemplate requestTemplate) {
String url = requestTemplate.path();
Map<String, Collection<String>> headers = requestTemplate.headers();
if(!headers.containsKey(X_CLIENT_TOKEN)){
String method = requestTemplate.method();
String originStr = new StringBuilder().append(url).append(method).append(SERVER_CLIENT_TOKEN).append(SERVER_CLIENT_SECRET).toString();
// 加入自定义头信息
String md5Secret = DigestUtils.md5DigestAsHex(originStr.getBytes());
requestTemplate.header(SERVER_CLIENT_TOKEN, md5Secret);
}
}
}

View File

@ -0,0 +1,87 @@
package com.springboot.cloud.common.web.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.core.util.UserContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Optional;
/**
* 用户信息拦截器
*/
@Slf4j
public class UserInterceptor implements HandlerInterceptor {
/**
* 服务间调用token用户信息,格式为json
* {
* "user_name":"必须有"
* "自定义key:"value"
* }
*/
//获取相关授权信息请求头
private static final String X_CLIENT_TOKEN_USER = "x-client-token-user";
//网关调用服务请求头
private static final String X_CLIENT_TOKEN = "x-client-token";
//网关调用服务秘钥
private static final String X_CLIENT_SECRET = "gateway-web";
//服务间调用请求头
private static final String SERVER_CLIENT_TOKEN = "server-client-token";
//服务间调用秘钥
private static final String SERVER_CLIENT_SECRET = "server";
//当前用户登录公司
private static final String USER_CURRENT_COMPANY = "company";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//从网关获取并校验,通过校验就可信任x-client-token-user中的信息
boolean b = checkToken(request);
if (!b) {
//重定向到登录前端
response.sendRedirect("https://www.baidu.com");
return b;
}
//放入上下文数据
String userInfoString = StringUtils.defaultIfBlank(request.getHeader(X_CLIENT_TOKEN_USER), "{}");
Map context = new ObjectMapper().readValue(userInfoString, Map.class);
context.put("company", request.getHeader(USER_CURRENT_COMPANY));
UserContextHolder.getInstance().setContext(context);
return true;
}
private boolean checkToken(HttpServletRequest request) {
String method = request.getMethod();
String url = request.getServletPath();
Optional<String> serverToken = Optional.ofNullable(request.getHeader(SERVER_CLIENT_TOKEN));
Optional<String> clientToken = Optional.ofNullable(request.getHeader(X_CLIENT_TOKEN));
if (serverToken.isPresent()) {
//如果是服务间相互调用不需要加contextPath因为feign拦截器中加密是根据servletPath来加密的此处也是根据servletPath解密的
String originStr = new StringBuilder().append(url).append(method).append(SERVER_CLIENT_TOKEN).append(SERVER_CLIENT_SECRET).toString();
return DigestUtils.md5DigestAsHex(originStr.getBytes()).equals(serverToken.get());
} else if (clientToken.isPresent()) {
//如果是网关调用服务网关服务需要加上contextPath
String originStr = new StringBuilder().append(request.getContextPath().concat(url)).append(method).append(X_CLIENT_TOKEN).append(X_CLIENT_SECRET).toString();
return DigestUtils.md5DigestAsHex(originStr.getBytes()).equals(clientToken.get());
} else {
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserContextHolder.getInstance().clear();
}
}

View File

@ -0,0 +1,48 @@
package com.springboot.cloud.common.web.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
//对象的序列化
RedisSerializationContext.SerializationPair valueSerializationPair
= RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer());
//全局redis缓存过期时间
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
// .serializeKeysWith()
.serializeValuesWith(valueSerializationPair);
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory), redisCacheConfiguration);
}
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
return jackson2JsonRedisSerializer;
}
private ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
return objectMapper;
}
}

View File

@ -0,0 +1,12 @@
package com.springboot.cloud.common.web.exception;
import org.junit.Test;
public class DefaultGlobalExceptionHandlerAdviceTest {
@Test
public void testMethod() {
}
}

View File

@ -0,0 +1,27 @@
package com.springboot.cloud.common.web.interceptor;
import com.springboot.cloud.common.core.util.UserContextHolder;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class UserInterceptorTest {
@Test
public void preHandle_当未设置token_user_那么正常处理下一个handle() throws Exception {
UserInterceptor userInterceptor = new UserInterceptor();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
userInterceptor.preHandle(request, response, new Object());
}
@Test
public void preHandle_当设置token的username_那么username可以在线程中拿出来用() throws Exception {
UserInterceptor userInterceptor = new UserInterceptor();
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("x-client-token-user", "{\"user_name\":\"zhangsan\"}");
MockHttpServletResponse response = new MockHttpServletResponse();
userInterceptor.preHandle(request, response, new Object());
Assert.assertEquals(UserContextHolder.getInstance().getUsername(), "zhangsan");
}
}

BIN
doc/连锁ERP架构.xmind Normal file

Binary file not shown.

Binary file not shown.

16
gateway/gateway-admin/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
target/
logs/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

View File

@ -0,0 +1,33 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>gateway-admin</name>
<description>Demo Gateway Admin project for Spring Cloud</description>
<parent>
<artifactId>webapp-parent</artifactId>
<groupId>business.chaoran</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../webapps/webapp-parent/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.5.14</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
<version>2.1.0.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,66 @@
SET NAMES utf8;
DROP DATABASE IF EXISTS sc_gateway;
CREATE DATABASE sc_gateway DEFAULT CHARSET utf8mb4;
USE sc_gateway;
-- 网关路由表
DROP TABLE IF EXISTS gateway_route;
CREATE TABLE gateway_route
(
id VARCHAR(20) PRIMARY KEY COMMENT 'id',
route_id VARCHAR(100) NOT NULL COMMENT '路由id',
uri VARCHAR(100) NOT NULL COMMENT 'uri路径',
predicates TEXT NOT NULL COMMENT '判定器',
filters TEXT COMMENT '过滤器',
orders INT COMMENT '排序',
description VARCHAR(500) COMMENT '描述',
status VARCHAR(1) DEFAULT 'Y' COMMENT '状态Y-有效N-无效',
created_time DATETIME NOT NULL DEFAULT now() COMMENT '创建时间',
updated_time DATETIME NOT NULL DEFAULT now() COMMENT '更新时间',
created_by VARCHAR(100) NOT NULL COMMENT '创建人',
updated_by VARCHAR(100) NOT NULL COMMENT '更新人'
) COMMENT '网关路由表';
CREATE UNIQUE INDEX ux_gateway_routes_uri ON gateway_route (uri);
-- DML初始数据
-- 路由数据
INSERT INTO gateway_route (id, route_id, uri, predicates, filters, orders, description, status, created_time, updated_time, created_by, updated_by)
VALUES
(101,
'authorization-server',
'lb://authorization-server:8000',
'[{"name":"Path","args":{"pattern":"/authorization-server/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'授权认证服务网关注册',
'Y', now(), now(), 'system', 'system'),
(102,
'authentication-server',
'lb://authentication-server:8001',
'[{"name":"Path","args":{"pattern":"/authentication-server/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'签权服务网关注册',
'Y', now(), now(), 'system', 'system'),
(103,
'organization',
'lb://organization:8010',
'[{"name":"Path","args":{"pattern":"/organization/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'系统管理相关接口',
'Y', now(), now(), 'system', 'system'),
(104,
'gateway-admin',
'lb://gateway-admin:8445',
'[{"name":"Path","args":{"pattern":"/gateway-admin/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'网关管理相关接口',
'Y', now(), now(), 'system', 'system')

View File

@ -0,0 +1,20 @@
package com.springboot.cloud.gateway.admin;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
@SpringBootApplication(exclude = GatewayClassPathWarningAutoConfiguration.class)
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableMethodCache(basePackages = "com.springboot.cloud")
@EnableCreateCacheAnnotation
public class GatewayAdminApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayAdminApplication.class, args);
}
}

View File

@ -0,0 +1,50 @@
package com.springboot.cloud.gateway.admin.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class BusConfig {
public static final String QUEUE_NAME = "event-gateway";
public static final String EXCHANGE_NAME = "spring-boot-exchange";
public static final String ROUTING_KEY = "gateway-route";
@Bean
Queue queue() {
log.info("queue name:{}", QUEUE_NAME);
return new Queue(QUEUE_NAME, false);
}
@Bean
TopicExchange exchange() {
log.info("exchange:{}", EXCHANGE_NAME);
return new TopicExchange(EXCHANGE_NAME);
}
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
log.info("binding {} to {} with {}", queue, exchange, ROUTING_KEY);
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean
public MessageConverter messageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
}
}

View File

@ -0,0 +1,9 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.handler.PoMetaObjectHandler;
import org.springframework.stereotype.Component;
@Component
public class MyMetaObjectHandler extends PoMetaObjectHandler {
}

View File

@ -0,0 +1,10 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.redis.RedisConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class MyRedisConfig extends RedisConfig {
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.gateway.admin.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 初使化Mybatis审计字段自动赋值的interceptor
*/
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

View File

@ -0,0 +1,35 @@
package com.springboot.cloud.gateway.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.springboot.cloud.gateway.admin"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("网关管理api")
.description("网关管理")
.termsOfServiceUrl("https://github.com/zhoutaoo/SpringCloud")
.version("2.0")
.build();
}
}

View File

@ -0,0 +1,22 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.interceptor.UserInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebServerMvcConfigurerAdapter implements WebMvcConfigurer {
@Bean
public HandlerInterceptor userInterceptor() {
return new UserInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor());
}
}

View File

@ -0,0 +1,11 @@
package com.springboot.cloud.gateway.admin.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface GatewayRouteMapper extends BaseMapper<GatewayRoute> {
}

View File

@ -0,0 +1,61 @@
package com.springboot.cloud.gateway.admin.entity.form;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.web.entity.form.BaseForm;
import com.springboot.cloud.gateway.admin.entity.po.FilterDefinition;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.entity.po.PredicateDefinition;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
@Slf4j
public class GatewayRouteForm extends BaseForm<GatewayRoute> {
@NotEmpty(message = "网关断言不能为空")
@ApiModelProperty(value = "网关断言")
private List<PredicateDefinition> predicates = new ArrayList<>();
@ApiModelProperty(value = "网关过滤器信息")
private List<FilterDefinition> filters = new ArrayList<>();
@NotBlank(message = "uri不能为空")
@ApiModelProperty(value = "网关uri")
private String uri;
@NotBlank(message = "路由id不能为空")
@ApiModelProperty(value = "网关路由id")
private String routeId;
@ApiModelProperty(value = "排序")
private Integer orders = 0;
@ApiModelProperty(value = "网关路由描述信息")
private String description;
@Override
public GatewayRoute toPo(Class<GatewayRoute> clazz) {
GatewayRoute gatewayRoute = new GatewayRoute();
BeanUtils.copyProperties(this, gatewayRoute);
try {
ObjectMapper objectMapper = new ObjectMapper();
gatewayRoute.setFilters(objectMapper.writeValueAsString(this.getFilters()));
gatewayRoute.setPredicates(objectMapper.writeValueAsString(this.getPredicates()));
} catch (JsonProcessingException e) {
log.error("网关filter或predicates配置转换异常", e);
}
return gatewayRoute;
}
}

View File

@ -0,0 +1,31 @@
package com.springboot.cloud.gateway.admin.entity.form;
import com.springboot.cloud.common.web.entity.form.BaseQueryForm;
import com.springboot.cloud.gateway.admin.entity.param.GatewayRouteQueryParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Past;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
public class GatewayRouteQueryForm extends BaseQueryForm<GatewayRouteQueryParam> {
@ApiModelProperty(value = "uri路径", required = true)
private String uri;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Past(message = "查询开始时间必须小于当前日期")
@ApiModelProperty(value = "查询开始时间")
private Date createdTimeStart;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Past(message = "查询结束时间必须小于当前日期")
@ApiModelProperty(value = "查询结束时间")
private Date createdTimeEnd;
}

View File

@ -0,0 +1,56 @@
package com.springboot.cloud.gateway.admin.entity.ov;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.web.entity.vo.BaseVo;
import com.springboot.cloud.gateway.admin.entity.po.FilterDefinition;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.entity.po.PredicateDefinition;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public class GatewayRouteVo extends BaseVo {
private String id;
private String routeId;
private String description;
private String status;
private String uri;
private Integer orders;
private String createdBy;
private Date createdTime;
private String updatedBy;
private Date updatedTime;
private List<FilterDefinition> filters = new ArrayList<>();
private List<PredicateDefinition> predicates = new ArrayList<>();
public GatewayRouteVo(GatewayRoute gatewayRoute) {
this.id = gatewayRoute.getId();
this.routeId = gatewayRoute.getRouteId();
this.uri = gatewayRoute.getUri();
this.description = gatewayRoute.getDescription();
this.status = gatewayRoute.getStatus();
this.orders = gatewayRoute.getOrders();
this.createdBy = gatewayRoute.getCreatedBy();
this.createdTime = gatewayRoute.getCreatedTime();
this.updatedBy = gatewayRoute.getUpdatedBy();
this.updatedTime = gatewayRoute.getUpdatedTime();
ObjectMapper objectMapper = new ObjectMapper();
try {
this.filters = objectMapper.readValue(gatewayRoute.getFilters(), new TypeReference<List<FilterDefinition>>() {
});
this.predicates = objectMapper.readValue(gatewayRoute.getPredicates(), new TypeReference<List<PredicateDefinition>>() {
});
} catch (IOException e) {
log.error("网关路由对象转换失败", e);
}
}
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin.entity.param;
import com.springboot.cloud.common.web.entity.param.BaseParam;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GatewayRouteQueryParam extends BaseParam<GatewayRoute> {
private String uri;
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin.entity.po;
import lombok.*;
import java.util.LinkedHashMap;
import java.util.Map;
@EqualsAndHashCode
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FilterDefinition {
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}

Some files were not shown because too many files have changed in this diff Show More