- Add `AuthController` and `SimpleWebAuthServiceImpl` - Update `AuthFilter` - Add a simple login page and frontend interceptor to support auth check and session storagemaster
@@ -20,12 +20,19 @@ mvn clean package | |||||
```bash | ```bash | ||||
java -Dserver.port=8080 \ | java -Dserver.port=8080 \ | ||||
-Dserver.servlet.session.timeout=7200 \ | |||||
-Dauth.username=sentinel \ | |||||
-Dauth.password=123456 \ | |||||
-Dcsp.sentinel.dashboard.server=localhost:8080 \ | -Dcsp.sentinel.dashboard.server=localhost:8080 \ | ||||
-Dproject.name=sentinel-dashboard \ | -Dproject.name=sentinel-dashboard \ | ||||
-jar target/sentinel-dashboard.jar | -jar target/sentinel-dashboard.jar | ||||
``` | ``` | ||||
上述命令中我们指定几个 JVM 参数,其中 `-Dserver.port=8080` 用于指定 Spring Boot 启动端口为 `8080`,其余几个是 Sentinel 客户端的参数。 | |||||
上述命令中我们指定几个 JVM 参数,其中: | |||||
`-Dserver.port=8080` 用于指定 Spring Boot 启动端口为 `8080`; | |||||
`-Dserver.servlet.session.timeout=7200` 用于指定 Spring Boot 服务器端会话的过期时间,如不带后缀的7200表示7200秒,60m表示60分钟,默认为30分钟; | |||||
`-Dauth.username=sentinel`、 `-Dauth.password=123456` 用于指定控制台的登录用户和密码分别为sentinel和123456,如果省略这2个参数,默认用户和密码均为sentinel; | |||||
其余几个是 Sentinel 客户端的参数。 | |||||
为便于演示,我们对控制台本身加入了流量控制功能,具体做法是引入 `CommonFilter` 这个 Sentinel 拦截器。上述 JVM 参数的含义是: | 为便于演示,我们对控制台本身加入了流量控制功能,具体做法是引入 `CommonFilter` 这个 Sentinel 拦截器。上述 JVM 参数的含义是: | ||||
| 参数 | 作用 | | | 参数 | 作用 | | ||||
@@ -55,6 +55,8 @@ Sentinel 提供了多种规则来保护系统的不同部分。流量控制规 | |||||
项 | 类型 | 默认值 | 最小值 | 描述 | 项 | 类型 | 默认值 | 最小值 | 描述 | ||||
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ||||
sentinel.dashboard.auth.username | String | sentinel | 无 | 登录控制台的用户,默认sentinel | |||||
sentinel.dashboard.auth.password | String | sentinel | 无 | 登录控制台的密码,默认sentinel | |||||
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 | sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 | ||||
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 | sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 | ||||
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 | sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 | ||||
@@ -0,0 +1,78 @@ | |||||
/* | |||||
* 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.springframework.context.annotation.Primary; | |||||
import org.springframework.stereotype.Component; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpSession; | |||||
/** | |||||
* @author cdfive | |||||
* @since 1.6.0 | |||||
*/ | |||||
@Primary | |||||
@Component | |||||
public class SimpleWebAuthServiceImpl implements AuthService<HttpServletRequest> { | |||||
public static final String WEB_SESSTION_KEY = "session_sentinel_admin"; | |||||
@Override | |||||
public AuthUser getAuthUser(HttpServletRequest request) { | |||||
HttpSession session = request.getSession(); | |||||
Object sentinelUserObj = session.getAttribute(SimpleWebAuthServiceImpl.WEB_SESSTION_KEY); | |||||
if (sentinelUserObj != null && sentinelUserObj instanceof AuthUser) { | |||||
return (AuthUser) sentinelUserObj; | |||||
} | |||||
return null; | |||||
} | |||||
public static final class SimpleWebAuthUserImpl implements AuthUser { | |||||
private String username; | |||||
public SimpleWebAuthUserImpl(String username) { | |||||
this.username = username; | |||||
} | |||||
@Override | |||||
public boolean authTarget(String target, PrivilegeType privilegeType) { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isSuperUser() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public String getNickName() { | |||||
return username; | |||||
} | |||||
@Override | |||||
public String getLoginName() { | |||||
return username; | |||||
} | |||||
@Override | |||||
public String getId() { | |||||
return username; | |||||
} | |||||
} | |||||
} |
@@ -37,6 +37,16 @@ public class DashboardConfig { | |||||
public static final int DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS = 60_000; | public static final int DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS = 60_000; | ||||
/** | |||||
* Login username | |||||
*/ | |||||
public static final String CONFIG_AUTH_USERNAME = "sentinel.dashboard.auth.username"; | |||||
/** | |||||
* Login password | |||||
*/ | |||||
public static final String CONFIG_AUTH_PASSWORD = "sentinel.dashboard.auth.password"; | |||||
/** | /** | ||||
* Hide application name in sidebar when it has no healthy machines after specific period in millisecond. | * Hide application name in sidebar when it has no healthy machines after specific period in millisecond. | ||||
*/ | */ | ||||
@@ -70,7 +80,22 @@ public class DashboardConfig { | |||||
} | } | ||||
return ""; | return ""; | ||||
} | } | ||||
protected static String getConfigStr(String name) { | |||||
if (cacheMap.containsKey(name)) { | |||||
return (String) cacheMap.get(name); | |||||
} | |||||
String val = getConfig(name); | |||||
if (StringUtils.isBlank(val)) { | |||||
return null; | |||||
} | |||||
cacheMap.put(name, val); | |||||
return val; | |||||
} | |||||
protected static int getConfigInt(String name, int defaultVal, int minVal) { | protected static int getConfigInt(String name, int defaultVal, int minVal) { | ||||
if (cacheMap.containsKey(name)) { | if (cacheMap.containsKey(name)) { | ||||
return (int)cacheMap.get(name); | return (int)cacheMap.get(name); | ||||
@@ -84,7 +109,15 @@ public class DashboardConfig { | |||||
cacheMap.put(name, val); | cacheMap.put(name, val); | ||||
return val; | return val; | ||||
} | } | ||||
public static String getAuthUsername() { | |||||
return getConfigStr(CONFIG_AUTH_USERNAME); | |||||
} | |||||
public static String getAuthPassword() { | |||||
return getConfigStr(CONFIG_AUTH_PASSWORD); | |||||
} | |||||
public static int getHideAppNoMachineMillis() { | public static int getHideAppNoMachineMillis() { | ||||
return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000); | return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000); | ||||
} | } | ||||
@@ -15,21 +15,8 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.dashboard.config; | package com.alibaba.csp.sentinel.dashboard.config; | ||||
import java.io.IOException; | |||||
import java.io.PrintWriter; | |||||
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 com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; | import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; | ||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.AuthUser; | |||||
import com.alibaba.csp.sentinel.dashboard.filter.AuthFilter; | |||||
import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
@@ -40,6 +27,8 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry | |||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; | ||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
import javax.servlet.Filter; | |||||
/** | /** | ||||
* @author leyou | * @author leyou | ||||
*/ | */ | ||||
@@ -49,7 +38,7 @@ public class WebConfig implements WebMvcConfigurer { | |||||
private final Logger logger = LoggerFactory.getLogger(WebConfig.class); | private final Logger logger = LoggerFactory.getLogger(WebConfig.class); | ||||
@Autowired | @Autowired | ||||
private AuthService<HttpServletRequest> authService; | |||||
private AuthFilter authFilter; | |||||
@Override | @Override | ||||
public void addResourceHandlers(ResourceHandlerRegistry registry) { | public void addResourceHandlers(ResourceHandlerRegistry registry) { | ||||
@@ -81,29 +70,7 @@ public class WebConfig implements WebMvcConfigurer { | |||||
@Bean | @Bean | ||||
public FilterRegistrationBean authenticationFilterRegistration() { | public FilterRegistrationBean authenticationFilterRegistration() { | ||||
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); | FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); | ||||
registration.setFilter(new Filter() { | |||||
@Override | |||||
public void init(FilterConfig filterConfig) throws ServletException { } | |||||
@Override | |||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, | |||||
FilterChain filterChain) throws IOException, ServletException { | |||||
HttpServletRequest request = (HttpServletRequest)servletRequest; | |||||
AuthUser authUser = authService.getAuthUser(request); | |||||
// authentication fail | |||||
if (authUser == null) { | |||||
PrintWriter writer = servletResponse.getWriter(); | |||||
writer.append("login needed"); | |||||
writer.flush(); | |||||
} else { | |||||
filterChain.doFilter(servletRequest, servletResponse); | |||||
} | |||||
} | |||||
@Override | |||||
public void destroy() { } | |||||
}); | |||||
registration.setFilter(authFilter); | |||||
registration.addUrlPatterns("/*"); | registration.addUrlPatterns("/*"); | ||||
registration.setName("authenticationFilter"); | registration.setName("authenticationFilter"); | ||||
registration.setOrder(0); | registration.setOrder(0); | ||||
@@ -0,0 +1,80 @@ | |||||
/* | |||||
* 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.controller; | |||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||||
import com.alibaba.csp.sentinel.dashboard.auth.SimpleWebAuthServiceImpl; | |||||
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig; | |||||
import com.alibaba.csp.sentinel.dashboard.domain.Result; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.http.MediaType; | |||||
import org.springframework.web.bind.annotation.RequestMapping; | |||||
import org.springframework.web.bind.annotation.RequestMethod; | |||||
import org.springframework.web.bind.annotation.RestController; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
/** | |||||
* @author cdfive | |||||
* @since 1.6.0 | |||||
*/ | |||||
@RestController | |||||
@RequestMapping(value = "/auth", produces = MediaType.APPLICATION_JSON_VALUE) | |||||
public class AuthController { | |||||
private static Logger LOGGER = LoggerFactory.getLogger(AuthController.class); | |||||
@Value("${auth.username:sentinel}") | |||||
private String authUsername; | |||||
@Value("${auth.password:sentinel}") | |||||
private String authPassword; | |||||
@RequestMapping(value = "/login", method = RequestMethod.POST) | |||||
public Result login(HttpServletRequest request, String username, String password) { | |||||
if (StringUtils.isNotBlank(DashboardConfig.getAuthUsername())) { | |||||
authUsername = DashboardConfig.getAuthUsername(); | |||||
} | |||||
if (StringUtils.isNotBlank(DashboardConfig.getAuthPassword())) { | |||||
authPassword = DashboardConfig.getAuthPassword(); | |||||
} | |||||
/** | |||||
* If auth.username or auth.password is blank(set in application.properties or VM arguments), | |||||
* auth will pass, as the front side validate the input which can't be blank, | |||||
* so user can input any username or password(both are not blank) to login in that case. | |||||
*/ | |||||
if ( StringUtils.isNotBlank(authUsername) && !authUsername.equals(username) | |||||
|| StringUtils.isNotBlank(authPassword) && !authPassword.equals(password)) { | |||||
LOGGER.error("Login failed: Invalid username or password, username=" + username + ", password=" + password); | |||||
return Result.ofFail(-1, "Invalid username or password"); | |||||
} | |||||
AuthService.AuthUser authUser = new SimpleWebAuthServiceImpl.SimpleWebAuthUserImpl(username); | |||||
request.getSession().setAttribute(SimpleWebAuthServiceImpl.WEB_SESSTION_KEY, authUser); | |||||
return Result.ofSuccess(authUser); | |||||
} | |||||
@RequestMapping(value = "/logout", method = RequestMethod.POST) | |||||
public Result logout(HttpServletRequest request) { | |||||
request.getSession().invalidate(); | |||||
return Result.ofSuccess(null); | |||||
} | |||||
} |
@@ -0,0 +1,118 @@ | |||||
/* | |||||
* 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.filter; | |||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService; | |||||
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 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; | |||||
/** | |||||
* Servlet Filter that authenticate requests. | |||||
* | |||||
* Note: | |||||
* Some urls are excluded as they needn't auth, such as: | |||||
* | |||||
* Index url: / | |||||
* Authentication request url: /login,logout | |||||
* Used for client: /registry/machine | |||||
* Static resources: htm,html,js and so on. | |||||
* | |||||
* The excluded urls and urlSuffixes are configured in application.properties | |||||
* | |||||
* @author cdfive | |||||
* @since 1.6.0 | |||||
*/ | |||||
@Component | |||||
public class AuthFilter implements Filter { | |||||
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 requestURI = httpRequest.getRequestURI(); | |||||
// Exclude the urls which needn't auth | |||||
if (authFilterExcludeUrls.contains(requestURI)) { | |||||
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 (requestURI.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() { | |||||
} | |||||
} |
@@ -8,3 +8,9 @@ logging.level.org.springframework.web=INFO | |||||
logging.file=${user.home}/logs/csp/sentinel-dashboard.log | logging.file=${user.home}/logs/csp/sentinel-dashboard.log | ||||
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | ||||
#logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | #logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n | ||||
#auth settings | |||||
auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine | |||||
auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff | |||||
auth.username=sentinel | |||||
auth.password=sentinel |
@@ -23,16 +23,57 @@ angular | |||||
'selectize', | 'selectize', | ||||
'angularUtils.directives.dirPagination' | 'angularUtils.directives.dirPagination' | ||||
]) | ]) | ||||
.config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', | |||||
function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) { | |||||
$ocLazyLoadProvider.config({ | |||||
debug: false, | |||||
events: true, | |||||
}); | |||||
.factory('AuthInterceptor', ['$window', '$state', function ($window, $state) { | |||||
var authInterceptor = { | |||||
'responseError' : function(response) { | |||||
if (response.status == 401) { | |||||
// If not auth, clear session in localStorage and jump to the login page | |||||
$window.localStorage.removeItem("session_sentinel_admin"); | |||||
$state.go('login'); | |||||
} | |||||
return response; | |||||
}, | |||||
'response' : function(response) { | |||||
return response; | |||||
}, | |||||
'request' : function(config) { | |||||
return config; | |||||
}, | |||||
'requestError' : function(config){ | |||||
return config; | |||||
} | |||||
}; | |||||
return authInterceptor; | |||||
}]) | |||||
.config(['$stateProvider', '$urlRouterProvider', '$ocLazyLoadProvider', '$httpProvider', | |||||
function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider, $httpProvider) { | |||||
$httpProvider.interceptors.push('AuthInterceptor'); | |||||
$ocLazyLoadProvider.config({ | |||||
debug: false, | |||||
events: true, | |||||
}); | |||||
$urlRouterProvider.otherwise('/dashboard/home'); | |||||
$urlRouterProvider.otherwise('/dashboard/home'); | |||||
$stateProvider | |||||
.state('login', { | |||||
url: '/login', | |||||
templateUrl: 'app/views/login.html', | |||||
controller: 'LoginCtl', | |||||
resolve: { | |||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { | |||||
return $ocLazyLoad.load({ | |||||
name: 'sentinelDashboardApp', | |||||
files: [ | |||||
'app/scripts/controllers/login.js', | |||||
] | |||||
}); | |||||
}] | |||||
} | |||||
}) | |||||
$stateProvider | |||||
.state('dashboard', { | .state('dashboard', { | ||||
url: '/dashboard', | url: '/dashboard', | ||||
templateUrl: 'app/views/dashboard/main.html', | templateUrl: 'app/views/dashboard/main.html', | ||||
@@ -0,0 +1,36 @@ | |||||
var app = angular.module('sentinelDashboardApp'); | |||||
app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService', | |||||
function ($scope, $state, $window, LoginService) { | |||||
// If auth, jump to the index page directly | |||||
if ($window.localStorage.getItem('session_sentinel_admin')) { | |||||
$state.go('dashboard'); | |||||
} | |||||
$scope.login = function () { | |||||
if (!$scope.username) { | |||||
alert('请输入用户名'); | |||||
return; | |||||
} | |||||
if (!$scope.password) { | |||||
alert('请输入密码'); | |||||
return; | |||||
} | |||||
var param = {"username": $scope.username, "password": $scope.password}; | |||||
LoginService.login(param).success(function (data) { | |||||
if (data.code == 0) { | |||||
$window.localStorage.setItem('session_sentinel_admin', { | |||||
username: data.data | |||||
}); | |||||
$state.go('dashboard'); | |||||
} else { | |||||
alert(data.msg); | |||||
} | |||||
}); | |||||
}; | |||||
}] | |||||
); |
@@ -3,6 +3,11 @@ | |||||
<div class="navbar-brand"> | <div class="navbar-brand"> | ||||
<span style="color: #fff;font-size: 26px;">Sentinel 控制台</span> | <span style="color: #fff;font-size: 26px;">Sentinel 控制台</span> | ||||
</div> | </div> | ||||
<ul class="nav navbar-nav navbar-right"> | |||||
<li> | |||||
<a href="javascript:void(0);" ng-click="logout()" style="margin: 3px 15px 0px 0px;"><span class="glyphicon glyphicon-log-out"></span>退出</a> | |||||
</li> | |||||
</ul> | |||||
</nav> | </nav> | ||||
<!-- end nav --> | <!-- end nav --> | ||||
<sidebar></sidebar> | <sidebar></sidebar> |
@@ -5,12 +5,26 @@ | |||||
* # adminPosHeader | * # adminPosHeader | ||||
*/ | */ | ||||
angular.module('sentinelDashboardApp') | angular.module('sentinelDashboardApp') | ||||
.directive('header', [function () { | |||||
.directive('header', ['AuthService', function () { | |||||
return { | return { | ||||
templateUrl: 'app/scripts/directives/header/header.html', | templateUrl: 'app/scripts/directives/header/header.html', | ||||
restrict: 'E', | restrict: 'E', | ||||
replace: true, | replace: true, | ||||
controller: function ($scope) { | |||||
controller: function ($scope, $state, $window, AuthService) { | |||||
if (!$window.localStorage.getItem('session_sentinel_admin')) { | |||||
$state.go('login'); | |||||
} | |||||
$scope.logout = function () { | |||||
AuthService.logout().success(function (data) { | |||||
if (data.code == 0) { | |||||
$window.localStorage.removeItem("session_sentinel_admin"); | |||||
$state.go('login'); | |||||
} else { | |||||
alert('logout error'); | |||||
} | |||||
}); | |||||
} | |||||
} | } | ||||
} | } | ||||
}]); | }]); |
@@ -0,0 +1,18 @@ | |||||
var app = angular.module('sentinelDashboardApp'); | |||||
app.service('AuthService', ['$http', function ($http) { | |||||
this.login = function (param) { | |||||
return $http({ | |||||
url: '/auth/login', | |||||
params: param, | |||||
method: 'POST' | |||||
}) | |||||
} | |||||
this.logout = function () { | |||||
return $http({ | |||||
url: '/auth/logout', | |||||
method: 'POST' | |||||
}) | |||||
} | |||||
}]); |
@@ -0,0 +1,29 @@ | |||||
<div class="container"> | |||||
<div class="row" style="margin: 200px auto 15px auto; display: table;"> | |||||
<h1 id='login_title'>Sentinel控制台</h1> | |||||
</div> | |||||
<div class="row"> | |||||
<div class="col-md-4" > | |||||
</div> | |||||
<div class="col-md-4"> | |||||
<form class="form-horizontal"> | |||||
<div class="form-group"> | |||||
<label class="col-md-2 control-label">用户</label> | |||||
<div class="col-md-9"> | |||||
<input class="form-control" type="text" ng-model="username" autofocus="autofocus"/> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label class="col-md-2 control-label">密码</label> | |||||
<div class="col-md-9"> | |||||
<input class="form-control" type="password" ng-model="password" /> | |||||
</div> | |||||
</div> | |||||
<div class="form-group btn-group" style="margin: 0px auto;display: table;"> | |||||
<button class="btn btn-success btn-primary" ng-click="login()">登录</button> | |||||
</div> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> |
@@ -43,6 +43,7 @@ const CSS_APP = [ | |||||
const JS_APP = [ | const JS_APP = [ | ||||
'app/scripts/app.js', | 'app/scripts/app.js', | ||||
'app/scripts/filters/filters.js', | 'app/scripts/filters/filters.js', | ||||
'app/scripts/services/auth_service.js', | |||||
'app/scripts/services/appservice.js', | 'app/scripts/services/appservice.js', | ||||
'app/scripts/services/flow_service_v1.js', | 'app/scripts/services/flow_service_v1.js', | ||||
'app/scripts/services/flow_service_v2.js', | 'app/scripts/services/flow_service_v2.js', | ||||
@@ -24,11 +24,34 @@ import org.junit.contrib.java.lang.system.EnvironmentVariables; | |||||
public class DashboardConfigTest { | public class DashboardConfigTest { | ||||
@Rule | @Rule | ||||
public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); | public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); | ||||
@Test | |||||
public void testGetConfigStr() { | |||||
// clear cache | |||||
DashboardConfig.clearCache(); | |||||
// if not set, return null | |||||
assertEquals(null, DashboardConfig.getConfigStr("a")); | |||||
// test property | |||||
System.setProperty("a", "111"); | |||||
assertEquals("111", DashboardConfig.getConfigStr("a")); | |||||
// test env | |||||
environmentVariables.set("a", "222"); | |||||
// return value in cache | |||||
assertEquals("111", DashboardConfig.getConfigStr("a")); | |||||
// clear cache and then test | |||||
DashboardConfig.clearCache(); | |||||
assertEquals("222", DashboardConfig.getConfigStr("a")); | |||||
} | |||||
@Test | @Test | ||||
public void testGetConfigInt() { | public void testGetConfigInt() { | ||||
// skip cache | |||||
// clear cache | |||||
DashboardConfig.clearCache(); | |||||
// default value | // default value | ||||
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); | assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10)); | ||||
DashboardConfig.clearCache(); | DashboardConfig.clearCache(); | ||||