初始化提交
This commit is contained in:
16
auth/authentication-client/.gitignore
vendored
Normal file
16
auth/authentication-client/.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
target/
|
||||
logs/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
83
auth/authentication-client/pom.xml
Normal file
83
auth/authentication-client/pom.xml
Normal 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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user