初始化提交

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

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>