Skip to content

Commit

Permalink
dashboard: Add a simple login page to support basic auth (alibaba#659)
Browse files Browse the repository at this point in the history
- Add `AuthController` and `SimpleWebAuthServiceImpl`
- Update `AuthFilter`
- Add a simple login page and frontend interceptor to support auth check and session storage
  • Loading branch information
cdfive authored and sczyh30 committed Apr 20, 2019
1 parent 7d344dc commit 4acb907
Show file tree
Hide file tree
Showing 18 changed files with 514 additions and 56 deletions.
9 changes: 8 additions & 1 deletion sentinel-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ mvn clean package

```bash
java -Dserver.port=8080 \
-Dserver.servlet.session.timeout=7200 \
-Dauth.username=sentinel \
-Dauth.password=123456 \
-Dcsp.sentinel.dashboard.server=localhost:8080 \
-Dproject.name=sentinel-dashboard \
-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 参数的含义是:

| 参数 | 作用 |
Expand Down
2 changes: 2 additions & 0 deletions sentinel-dashboard/Sentinel_Dashboard_Feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ public class DashboardConfig {

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.
*/
Expand Down Expand Up @@ -70,7 +80,22 @@ private static String getConfig(String name) {
}
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) {
if (cacheMap.containsKey(name)) {
return (int)cacheMap.get(name);
Expand All @@ -84,7 +109,15 @@ protected static int getConfigInt(String name, int defaultVal, int minVal) {
cacheMap.put(name, 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() {
return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,8 @@
*/
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.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.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -40,6 +27,8 @@
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.Filter;

/**
* @author leyou
*/
Expand All @@ -49,7 +38,7 @@ public class WebConfig implements WebMvcConfigurer {
private final Logger logger = LoggerFactory.getLogger(WebConfig.class);

@Autowired
private AuthService<HttpServletRequest> authService;
private AuthFilter authFilter;

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
Expand Down Expand Up @@ -81,29 +70,7 @@ public FilterRegistrationBean sentinelFilterRegistration() {
@Bean
public FilterRegistrationBean authenticationFilterRegistration() {
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.setName("authenticationFilter");
registration.setOrder(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 4acb907

Please sign in to comment.