初始化提交
This commit is contained in:
@ -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