* refactor(dashboard): use AuthConfiguration to manage the bean of auth * refactor(dashboard): change AuthorizationInterceptor to an interface * refactor(dashboard): change LoginAuthenticationFilter to an interface * refactor(dashboard): use AuthProperties to manage auth config * test(dashboard): use NoAuthConfigurationTest to create no auth configuration for testingmaster
@@ -15,58 +15,15 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import com.alibaba.csp.sentinel.dashboard.domain.Result; | |||
import com.alibaba.fastjson.JSON; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Component; | |||
import org.springframework.web.method.HandlerMethod; | |||
import org.springframework.web.servlet.HandlerInterceptor; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.lang.reflect.Method; | |||
/** | |||
* The web interceptor for privilege-based authorization. | |||
* | |||
* @author lkxiaolou | |||
* @author wxq | |||
* @since 1.7.1 | |||
*/ | |||
@Component | |||
public class AuthorizationInterceptor implements HandlerInterceptor { | |||
@Autowired | |||
private AuthService<HttpServletRequest> authService; | |||
@Override | |||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | |||
throws Exception { | |||
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) { | |||
Method method = ((HandlerMethod) handler).getMethod(); | |||
AuthAction authAction = method.getAnnotation(AuthAction.class); | |||
if (authAction != null) { | |||
AuthService.AuthUser authUser = authService.getAuthUser(request); | |||
if (authUser == null) { | |||
responseNoPrivilegeMsg(response, authAction.message()); | |||
return false; | |||
} | |||
String target = request.getParameter(authAction.targetName()); | |||
if (!authUser.authTarget(target, authAction.value())) { | |||
responseNoPrivilegeMsg(response, authAction.message()); | |||
return false; | |||
} | |||
} | |||
} | |||
return true; | |||
} | |||
public interface AuthorizationInterceptor extends HandlerInterceptor { | |||
private void responseNoPrivilegeMsg(HttpServletResponse response, String message) throws IOException { | |||
Result result = Result.ofFail(-1, message); | |||
response.addHeader("Content-Type", "application/json;charset=UTF-8"); | |||
response.getOutputStream().write(JSON.toJSONBytes(result)); | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import com.alibaba.csp.sentinel.dashboard.domain.Result; | |||
import com.alibaba.fastjson.JSON; | |||
import org.springframework.web.method.HandlerMethod; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.lang.reflect.Method; | |||
/** | |||
* The web interceptor for privilege-based authorization. | |||
* <p> | |||
* move from old {@link AuthorizationInterceptor}. | |||
* | |||
* @author lkxiaolou | |||
* @author wxq | |||
* @since 1.7.1 | |||
*/ | |||
public class DefaultAuthorizationInterceptor implements AuthorizationInterceptor { | |||
private final AuthService<HttpServletRequest> authService; | |||
public DefaultAuthorizationInterceptor(AuthService<HttpServletRequest> authService) { | |||
this.authService = authService; | |||
} | |||
@Override | |||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | |||
throws Exception { | |||
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) { | |||
Method method = ((HandlerMethod) handler).getMethod(); | |||
AuthAction authAction = method.getAnnotation(AuthAction.class); | |||
if (authAction != null) { | |||
AuthService.AuthUser authUser = authService.getAuthUser(request); | |||
if (authUser == null) { | |||
responseNoPrivilegeMsg(response, authAction.message()); | |||
return false; | |||
} | |||
String target = request.getParameter(authAction.targetName()); | |||
if (!authUser.authTarget(target, authAction.value())) { | |||
responseNoPrivilegeMsg(response, authAction.message()); | |||
return false; | |||
} | |||
} | |||
} | |||
return true; | |||
} | |||
private void responseNoPrivilegeMsg(HttpServletResponse response, String message) throws IOException { | |||
Result result = Result.ofFail(-1, message); | |||
response.addHeader("Content-Type", "application/json;charset=UTF-8"); | |||
response.getOutputStream().write(JSON.toJSONBytes(result)); | |||
} | |||
} |
@@ -0,0 +1,125 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.beans.factory.annotation.Value; | |||
import org.springframework.http.HttpStatus; | |||
import org.springframework.util.AntPathMatcher; | |||
import javax.servlet.*; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.List; | |||
/** | |||
* <p>The Servlet filter for authentication.</p> | |||
* | |||
* <p>Note: some urls are excluded as they needn't auth, such as:</p> | |||
* <ul> | |||
* <li>index url: {@code /}</li> | |||
* <li>authentication request url: {@code /login}, {@code /logout}</li> | |||
* <li>machine registry: {@code /registry/machine}</li> | |||
* <li>static resources</li> | |||
* </ul> | |||
* <p> | |||
* The excluded urls and urlSuffixes could be configured in {@code application.properties} file. | |||
* | |||
* @author cdfive | |||
* @since 1.6.0 | |||
*/ | |||
public class DefaultLoginAuthenticationFilter implements LoginAuthenticationFilter { | |||
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); | |||
private static final String URL_SUFFIX_DOT = "."; | |||
/** | |||
* Some urls which needn't auth, such as /auth/login, /registry/machine and so on. | |||
*/ | |||
@Value("#{'${auth.filter.exclude-urls}'.split(',')}") | |||
private List<String> authFilterExcludeUrls; | |||
/** | |||
* Some urls with suffixes which needn't auth, such as htm, html, js and so on. | |||
*/ | |||
@Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}") | |||
private List<String> authFilterExcludeUrlSuffixes; | |||
/** | |||
* Authentication using AuthService interface. | |||
*/ | |||
private final AuthService<HttpServletRequest> authService; | |||
public DefaultLoginAuthenticationFilter(AuthService<HttpServletRequest> authService) { | |||
this.authService = authService; | |||
} | |||
@Override | |||
public void init(FilterConfig filterConfig) throws ServletException { | |||
} | |||
@Override | |||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | |||
throws IOException, ServletException { | |||
HttpServletRequest httpRequest = (HttpServletRequest) request; | |||
String servletPath = httpRequest.getServletPath(); | |||
// Exclude the urls which needn't auth | |||
boolean authFilterExcludeMatch = authFilterExcludeUrls.stream() | |||
.anyMatch(authFilterExcludeUrl -> PATH_MATCHER.match(authFilterExcludeUrl, servletPath)); | |||
if (authFilterExcludeMatch) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
// Exclude the urls with suffixes which needn't auth | |||
for (String authFilterExcludeUrlSuffix : authFilterExcludeUrlSuffixes) { | |||
if (StringUtils.isBlank(authFilterExcludeUrlSuffix)) { | |||
continue; | |||
} | |||
// Add . for url suffix so that we needn't add . in property file | |||
if (!authFilterExcludeUrlSuffix.startsWith(URL_SUFFIX_DOT)) { | |||
authFilterExcludeUrlSuffix = URL_SUFFIX_DOT + authFilterExcludeUrlSuffix; | |||
} | |||
if (servletPath.endsWith(authFilterExcludeUrlSuffix)) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
} | |||
AuthService.AuthUser authUser = authService.getAuthUser(httpRequest); | |||
HttpServletResponse httpResponse = (HttpServletResponse) response; | |||
if (authUser == null) { | |||
// If auth fail, set response status code to 401 | |||
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); | |||
} else { | |||
chain.doFilter(request, response); | |||
} | |||
} | |||
@Override | |||
public void destroy() { | |||
} | |||
} |
@@ -15,9 +15,10 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import javax.servlet.http.HttpServletRequest; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.stereotype.Component; | |||
import javax.servlet.http.HttpServletRequest; | |||
/** | |||
* A fake AuthService implementation, which will pass all user auth checking. | |||
@@ -25,9 +26,14 @@ import org.springframework.stereotype.Component; | |||
* @author Carpenter Lee | |||
* @since 1.5.0 | |||
*/ | |||
@Component | |||
public class FakeAuthServiceImpl implements AuthService<HttpServletRequest> { | |||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
public FakeAuthServiceImpl() { | |||
this.logger.warn("there is no auth, use {} by implementation {}", AuthService.class, this.getClass()); | |||
} | |||
@Override | |||
public AuthUser getAuthUser(HttpServletRequest request) { | |||
return new AuthUserImpl(); | |||
@@ -15,23 +15,7 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.beans.factory.annotation.Value; | |||
import org.springframework.http.HttpStatus; | |||
import org.springframework.stereotype.Component; | |||
import org.springframework.util.AntPathMatcher; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
import javax.servlet.FilterConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.ServletRequest; | |||
import javax.servlet.ServletResponse; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.List; | |||
/** | |||
* <p>The Servlet filter for authentication.</p> | |||
@@ -43,87 +27,13 @@ import java.util.List; | |||
* <li>machine registry: {@code /registry/machine}</li> | |||
* <li>static resources</li> | |||
* </ul> | |||
* | |||
* <p> | |||
* The excluded urls and urlSuffixes could be configured in {@code application.properties} file. | |||
* | |||
* @author cdfive | |||
* @author wxq | |||
* @since 1.6.0 | |||
*/ | |||
@Component | |||
public class LoginAuthenticationFilter implements Filter { | |||
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); | |||
private static final String URL_SUFFIX_DOT = "."; | |||
/** | |||
* Some urls which needn't auth, such as /auth/login, /registry/machine and so on. | |||
*/ | |||
@Value("#{'${auth.filter.exclude-urls}'.split(',')}") | |||
private List<String> authFilterExcludeUrls; | |||
/** | |||
* Some urls with suffixes which needn't auth, such as htm, html, js and so on. | |||
*/ | |||
@Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}") | |||
private List<String> authFilterExcludeUrlSuffixes; | |||
/** | |||
* Authentication using AuthService interface. | |||
*/ | |||
@Autowired | |||
private AuthService<HttpServletRequest> authService; | |||
@Override | |||
public void init(FilterConfig filterConfig) throws ServletException { | |||
} | |||
@Override | |||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | |||
throws IOException, ServletException { | |||
HttpServletRequest httpRequest = (HttpServletRequest) request; | |||
String servletPath = httpRequest.getServletPath(); | |||
// Exclude the urls which needn't auth | |||
boolean authFilterExcludeMatch = authFilterExcludeUrls.stream() | |||
.anyMatch(authFilterExcludeUrl -> PATH_MATCHER.match(authFilterExcludeUrl, servletPath)); | |||
if (authFilterExcludeMatch) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
// Exclude the urls with suffixes which needn't auth | |||
for (String authFilterExcludeUrlSuffix : authFilterExcludeUrlSuffixes) { | |||
if (StringUtils.isBlank(authFilterExcludeUrlSuffix)) { | |||
continue; | |||
} | |||
// Add . for url suffix so that we needn't add . in property file | |||
if (!authFilterExcludeUrlSuffix.startsWith(URL_SUFFIX_DOT)) { | |||
authFilterExcludeUrlSuffix = URL_SUFFIX_DOT + authFilterExcludeUrlSuffix; | |||
} | |||
if (servletPath.endsWith(authFilterExcludeUrlSuffix)) { | |||
chain.doFilter(request, response); | |||
return; | |||
} | |||
} | |||
AuthService.AuthUser authUser = authService.getAuthUser(httpRequest); | |||
HttpServletResponse httpResponse = (HttpServletResponse) response; | |||
if (authUser == null) { | |||
// If auth fail, set response status code to 401 | |||
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); | |||
} else { | |||
chain.doFilter(request, response); | |||
} | |||
} | |||
@Override | |||
public void destroy() { | |||
public interface LoginAuthenticationFilter extends Filter { | |||
} | |||
} |
@@ -15,10 +15,6 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.auth; | |||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |||
import org.springframework.context.annotation.Primary; | |||
import org.springframework.stereotype.Component; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpSession; | |||
@@ -26,9 +22,6 @@ import javax.servlet.http.HttpSession; | |||
* @author cdfive | |||
* @since 1.6.0 | |||
*/ | |||
@Component | |||
@Primary | |||
@ConditionalOnProperty(name = "auth.enabled", matchIfMissing = true) | |||
public class SimpleWebAuthServiceImpl implements AuthService<HttpServletRequest> { | |||
public static final String WEB_SESSION_KEY = "session_sentinel_admin"; | |||
@@ -0,0 +1,57 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.config; | |||
import com.alibaba.csp.sentinel.dashboard.auth.*; | |||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | |||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import javax.servlet.http.HttpServletRequest; | |||
@Configuration | |||
@EnableConfigurationProperties(AuthProperties.class) | |||
public class AuthConfiguration { | |||
private final AuthProperties authProperties; | |||
public AuthConfiguration(AuthProperties authProperties) { | |||
this.authProperties = authProperties; | |||
} | |||
@Bean | |||
@ConditionalOnMissingBean | |||
public AuthService<HttpServletRequest> httpServletRequestAuthService() { | |||
if (this.authProperties.isEnabled()) { | |||
return new SimpleWebAuthServiceImpl(); | |||
} | |||
return new FakeAuthServiceImpl(); | |||
} | |||
@Bean | |||
@ConditionalOnMissingBean | |||
public LoginAuthenticationFilter loginAuthenticationFilter(AuthService<HttpServletRequest> httpServletRequestAuthService) { | |||
return new DefaultLoginAuthenticationFilter(httpServletRequestAuthService); | |||
} | |||
@Bean | |||
@ConditionalOnMissingBean | |||
public AuthorizationInterceptor authorizationInterceptor(AuthService<HttpServletRequest> httpServletRequestAuthService) { | |||
return new DefaultAuthorizationInterceptor(httpServletRequestAuthService); | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.config; | |||
import org.springframework.boot.context.properties.ConfigurationProperties; | |||
@ConfigurationProperties(prefix = "auth") | |||
public class AuthProperties { | |||
private boolean enabled = true; | |||
public boolean isEnabled() { | |||
return enabled; | |||
} | |||
public void setEnabled(boolean enabled) { | |||
this.enabled = enabled; | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.config; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||
import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl; | |||
import org.springframework.boot.test.context.TestConfiguration; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Import; | |||
import javax.servlet.http.HttpServletRequest; | |||
/** | |||
* disable auth in test. | |||
* | |||
* @author wxq | |||
*/ | |||
@TestConfiguration | |||
@Import(AuthConfiguration.class) | |||
public class NoAuthConfigurationTest { | |||
@Bean | |||
public AuthService<HttpServletRequest> httpServletRequestAuthService() { | |||
return new FakeAuthServiceImpl(); | |||
} | |||
} |
@@ -15,9 +15,8 @@ | |||
*/ | |||
package com.alibaba.csp.sentinel.dashboard.controller.gateway; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthorizationInterceptor; | |||
import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl; | |||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.alibaba.csp.sentinel.dashboard.config.NoAuthConfigurationTest; | |||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity; | |||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity; | |||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; | |||
@@ -61,7 +60,7 @@ import static org.mockito.BDDMockito.*; | |||
*/ | |||
@RunWith(SpringRunner.class) | |||
@WebMvcTest(GatewayApiController.class) | |||
@Import({FakeAuthServiceImpl.class, InMemApiDefinitionStore.class, AppManagement.class, SimpleMachineDiscovery.class, AuthorizationInterceptor.class}) | |||
@Import({NoAuthConfigurationTest.class, InMemApiDefinitionStore.class, AppManagement.class, SimpleMachineDiscovery.class}) | |||
public class GatewayApiControllerTest { | |||
private static final String TEST_APP = "test_app"; | |||
@@ -18,6 +18,7 @@ package com.alibaba.csp.sentinel.dashboard.controller.gateway; | |||
import com.alibaba.csp.sentinel.dashboard.auth.AuthorizationInterceptor; | |||
import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl; | |||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.alibaba.csp.sentinel.dashboard.config.NoAuthConfigurationTest; | |||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity; | |||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity; | |||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; | |||
@@ -64,8 +65,7 @@ import static org.mockito.BDDMockito.*; | |||
*/ | |||
@RunWith(SpringRunner.class) | |||
@WebMvcTest(GatewayFlowRuleController.class) | |||
@Import({FakeAuthServiceImpl.class, InMemGatewayFlowRuleStore.class, AppManagement.class, SimpleMachineDiscovery.class, | |||
AuthorizationInterceptor.class }) | |||
@Import({NoAuthConfigurationTest.class, InMemGatewayFlowRuleStore.class, AppManagement.class, SimpleMachineDiscovery.class}) | |||
public class GatewayFlowRuleControllerTest { | |||
private static final String TEST_APP = "test_app"; | |||