初始化提交

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"));
}
}