From 1588f70b52e39b6ea273aed6cfd242b8fd809c63 Mon Sep 17 00:00:00 2001 From: walking98 Date: Tue, 16 Apr 2019 20:14:27 +0800 Subject: [PATCH] [ISSUE-205|247] Support SSL + Login: Polish with testcase, doc, session over cookie --- doc/1_0_0/UserGuide_CN.md | 53 +++++++++- doc/1_0_0/UserGuide_EN.md | 55 +++++++++- .../config/AuthWebMVCConfigurerAdapter.java | 4 +- .../rocketmq/console/config/RMQConfigure.java | 20 ---- .../console/controller/LoginController.java | 40 ++++--- .../console/interceptor/AuthInterceptor.java | 14 --- .../apache/rocketmq/console/model/User.java | 4 + .../console/service/LoginService.java | 4 - .../service/impl/LoginServiceImpl.java | 61 +---------- .../console/service/impl/UserServiceImpl.java | 14 ++- .../rocketmq/console/util/CipherHelper.java | 72 ------------- .../apache/rocketmq/console/util/WebUtil.java | 100 +++++++----------- src/main/resources/application.properties | 2 +- src/main/resources/static/src/app.js | 97 +++++++++++------ src/main/resources/static/src/controller.js | 6 +- src/main/resources/static/src/login.js | 6 +- .../resources/static/view/pages/login.html | 2 - src/main/resources/users.properties | 9 ++ .../console/service/impl/LoginFileTest.java | 26 +++++ src/test/resources/users.properties | 2 + 20 files changed, 294 insertions(+), 297 deletions(-) delete mode 100644 src/main/java/org/apache/rocketmq/console/util/CipherHelper.java create mode 100644 src/main/resources/users.properties create mode 100644 src/test/java/org/apache/rocketmq/console/service/impl/LoginFileTest.java create mode 100644 src/test/resources/users.properties diff --git a/doc/1_0_0/UserGuide_CN.md b/doc/1_0_0/UserGuide_CN.md index 26ac0ed..1a5f257 100755 --- a/doc/1_0_0/UserGuide_CN.md +++ b/doc/1_0_0/UserGuide_CN.md @@ -61,4 +61,55 @@ * 根据Topic和Key进行查询 * 最多只会展示64条 * 根据消息主题和消息Id进行消息的查询 -* 消息详情可以展示这条消息的详细信息,查看消息对应到具体消费组的消费情况(如果异常,可以查看具体的异常信息)。可以向指定的消费组重发消息。 \ No newline at end of file +* 消息详情可以展示这条消息的详细信息,查看消息对应到具体消费组的消费情况(如果异常,可以查看具体的异常信息)。可以向指定的消费组重发消息。 + + +## HTTPS 方式访问Console +* HTTPS功能实际上是使用SpringBoot提供的配置功能即可完成,首先,需要有一个SSL KeyStore来存放服务端证书,可以使用本工程所提供的测试密钥库: +resources/rmqcngkeystore.jks, 它可以通过如下keytool命令生成 +``` +#生成库并以rmqcngKey别名添加秘钥 +keytool -genkeypair -alias rmqcngKey -keyalg RSA -validity 3650 -keystore rmqcngkeystore.jks +#查看keystore内容 +keytool -list -v -keystore rmqcngkeystore.jks +#转换库格式 +keytool -importkeystore -srckeystore rmqcngkeystore.jks -destkeystore rmqcngkeystore.jks -deststoretype pkcs12 +``` + +* 配置resources/application.properties, 打开SSL的相关选项, 启动console后即开启了HTTPS. +``` +#设置https端口 +server.port=8443 + +### SSL setting +#server.ssl.key-store=classpath:rmqcngkeystore.jks +#server.ssl.key-store-password=rocketmq +#server.ssl.keyStoreType=PKCS12 +#server.ssl.keyAlias=rmqcngkey +``` + +## 登录访问Console +在访问Console时支持按用户名和密码登录控制台,在操作完成后登出。需要做如下的设置: + +* 1.在Spring配置文件resources/application.properties中修改 开启登录功能 +```$xslt +# 开启登录功能 +rocketmq.config.loginRequired=true + +# Dashboard文件目录,登录用户配置文件所在目录 +rocketmq.config.dataPath=/tmp/rocketmq-console/data +``` +* 2.确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建登录配置文件"users.properties", 如果该目录下不存在此文件,则默认使用resources/users.properties文件。 +users.properties文件格式为: +```$xslt +# 该文件支持热修改,即添加和修改用户时,不需要重新启动console +# 格式, 每行定义一个用户, username=password[,N] #N是可选项,可以为0 (普通用户); 1 (管理员) + +#定义管理员 +admin=admin,1 + +#定义普通用户 +user1=user1 +user2=user2 +``` +* 3. 启动控制台则开启了登录功能 \ No newline at end of file diff --git a/doc/1_0_0/UserGuide_EN.md b/doc/1_0_0/UserGuide_EN.md index f883f9e..b13b0b4 100644 --- a/doc/1_0_0/UserGuide_EN.md +++ b/doc/1_0_0/UserGuide_EN.md @@ -62,4 +62,57 @@ * Only Return 64 Messages * Query By Topic And MessageId * look over this message's detail info.you can see the message's consume state(each group has one line),show the exception message if has exception. -you can send this message to the group you selected \ No newline at end of file +you can send this message to the group you selected + + +## Access Console with HTTPS +* SpringBoot itself has provided the SSL configuration. You can use the project test Keystore:resources/rmqcngkeystore.jks. The store is generated with the following unix keytool commands: +``` +#Generate Keystore and add alias rmqcngKey +keytool -genkeypair -alias rmqcngKey -keyalg RSA -validity 3650 -keystore rmqcngkeystore.jks +#View keystore content +keytool -list -v -keystore rmqcngkeystore.jks +#Transfer type as official +keytool -importkeystore -srckeystore rmqcngkeystore.jks -destkeystore rmqcngkeystore.jks -deststoretype pkcs12 +``` + +* Uncomment the following SSL properties in resources/application.properties. restart Console then access with HTTPS. + +``` +#Set https port +server.port=8443 + +### SSL setting +server.ssl.key-store=classpath:rmqcngkeystore.jks +server.ssl.key-store-password=rocketmq +server.ssl.keyStoreType=PKCS12 +server.ssl.keyAlias=rmqcngkey +``` + +## Login/Logout on Console +Access Console with username and password and logout to leave the console。To stage the function on, we need the steps below: + +* 1.Turn on the property in resources/application.properties. +```$xslt +# open the login func +rocketmq.config.loginRequired=true + +# Directory of ashboard & login user configure file +rocketmq.config.dataPath=/tmp/rocketmq-console/data +``` +* 2.Make sure the directory defined in property ${rocketmq.config.dataPath} exists and the file "users.properties" is created under it. +The console system will use the resources/users.properties by default if a customized file is not found。 + +The format in the content of users.properties: +```$xslt +# This file supports hot change, any change will be auto-reloaded without Console restarting. +# Format: a user per line, username=password[,N] #N is optional, 0 (Normal User); 1 (Admin) + +# Define Admin +admin=admin,1 + +# Define Normal users +user1=user1 +user2=user2 +``` +* 3. Restart Console Application after above configuration setting well. \ No newline at end of file diff --git a/src/main/java/org/apache/rocketmq/console/config/AuthWebMVCConfigurerAdapter.java b/src/main/java/org/apache/rocketmq/console/config/AuthWebMVCConfigurerAdapter.java index 90936f5..2f44b13 100644 --- a/src/main/java/org/apache/rocketmq/console/config/AuthWebMVCConfigurerAdapter.java +++ b/src/main/java/org/apache/rocketmq/console/config/AuthWebMVCConfigurerAdapter.java @@ -33,7 +33,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.annotation.Resource; -import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; import java.util.List; @Configuration @@ -64,7 +64,7 @@ public class AuthWebMVCConfigurerAdapter extends WebMvcConfigurerAdapter { @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { - UserInfo userInfo = (UserInfo) WebUtil.getAttribute((ServletRequest) nativeWebRequest.getNativeRequest(), + UserInfo userInfo = (UserInfo) WebUtil.getValueFromSession((HttpServletRequest) nativeWebRequest.getNativeRequest(), UserInfo.USER_INFO); if (userInfo != null) { return userInfo; diff --git a/src/main/java/org/apache/rocketmq/console/config/RMQConfigure.java b/src/main/java/org/apache/rocketmq/console/config/RMQConfigure.java index e91f5ec..516cf1f 100644 --- a/src/main/java/org/apache/rocketmq/console/config/RMQConfigure.java +++ b/src/main/java/org/apache/rocketmq/console/config/RMQConfigure.java @@ -17,15 +17,12 @@ package org.apache.rocketmq.console.config; import java.io.File; -import java.io.UnsupportedEncodingException; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; -import org.apache.rocketmq.console.util.CipherHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.apache.rocketmq.client.ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY; @@ -47,19 +44,8 @@ public class RMQConfigure { private String msgTrackTopicName; - private String ciperKey = "rocketmqrocketmq"; - private boolean loginRequired = false; - public String getCiperKey() { - return ciperKey; - } - - public void setCiperKey(String ciperKey) { - this.ciperKey = ciperKey; - } - - public String getNamesrvAddr() { return namesrvAddr; } @@ -112,12 +98,6 @@ public class RMQConfigure { this.msgTrackTopicName = msgTrackTopicName; } - @Bean - public CipherHelper cipherHelper() throws UnsupportedEncodingException { - CipherHelper cipherHelper = new CipherHelper(getCiperKey()); - return cipherHelper; - } - public boolean isLoginRequired() { return loginRequired; } diff --git a/src/main/java/org/apache/rocketmq/console/controller/LoginController.java b/src/main/java/org/apache/rocketmq/console/controller/LoginController.java index 8517571..0236475 100644 --- a/src/main/java/org/apache/rocketmq/console/controller/LoginController.java +++ b/src/main/java/org/apache/rocketmq/console/controller/LoginController.java @@ -17,9 +17,10 @@ package org.apache.rocketmq.console.controller; +import org.apache.rocketmq.console.config.RMQConfigure; import org.apache.rocketmq.console.model.User; +import org.apache.rocketmq.console.model.UserInfo; import org.apache.rocketmq.console.service.UserService; -import org.apache.rocketmq.console.util.CipherHelper; import org.apache.rocketmq.console.util.WebUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,29 +31,33 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.Map; @Controller @RequestMapping("/login") public class LoginController { private Logger logger = LoggerFactory.getLogger(this.getClass()); + @Resource + private RMQConfigure configure; + @Autowired private UserService userService; - @Autowired - private CipherHelper cipherHelper; - - @RequestMapping(value = "/index", method = RequestMethod.GET) - public String index(Map map) { - return "login"; + @RequestMapping(value = "/check.query", method = RequestMethod.GET) + @ResponseBody + public Object check(HttpServletRequest request) { + WebUtil.setSessionValue(request, WebUtil.NEED_LOGIN, configure.isLoginRequired()); + return new Boolean(configure.isLoginRequired()); } - @RequestMapping(value = "/check", method = RequestMethod.POST) + @RequestMapping(value = "/login.do", method = RequestMethod.POST) @ResponseBody - public Object check(@RequestParam("username") String username, + public Object login(@RequestParam("username") String username, @RequestParam(value = "password") String password, + HttpServletRequest request, HttpServletResponse response) throws Exception { logger.info("user:{} login", username); User user = userService.queryByUsernameAndPassword(username, password); @@ -60,16 +65,19 @@ public class LoginController { if (user == null) { throw new IllegalArgumentException("Bad username or password!"); } else { - WebUtil.setCookie(response, WebUtil.LOGIN_TOKEN, cipherHelper.encrypt(username), false); - WebUtil.setCookie(response, WebUtil.USER_NAME, username, false); + user.setPassword(null); + UserInfo userInfo = WebUtil.setLoginInfo(request, response, user); + WebUtil.setSessionValue(request, WebUtil.USER_INFO, userInfo); + WebUtil.setSessionValue(request, WebUtil.USER_NAME, username); return Boolean.TRUE; } } - @RequestMapping(value = "/logout", method = RequestMethod.POST) + @RequestMapping(value = "/logout.do", method = RequestMethod.POST) @ResponseBody - public void logout(HttpServletResponse response) { - WebUtil.setCookie(response, WebUtil.LOGIN_TOKEN,"", true); - WebUtil.setCookie(response, WebUtil.USER_NAME, "", true); + public Object logout(HttpServletRequest request) { + WebUtil.removeSession(request); + + return Boolean.TRUE; } } diff --git a/src/main/java/org/apache/rocketmq/console/interceptor/AuthInterceptor.java b/src/main/java/org/apache/rocketmq/console/interceptor/AuthInterceptor.java index dd93693..c433950 100644 --- a/src/main/java/org/apache/rocketmq/console/interceptor/AuthInterceptor.java +++ b/src/main/java/org/apache/rocketmq/console/interceptor/AuthInterceptor.java @@ -17,11 +17,7 @@ package org.apache.rocketmq.console.interceptor; -import org.apache.rocketmq.console.model.UserInfo; import org.apache.rocketmq.console.service.LoginService; -import org.apache.rocketmq.console.util.WebUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -31,8 +27,6 @@ import javax.servlet.http.HttpServletResponse; @Component public class AuthInterceptor extends HandlerInterceptorAdapter { - private Logger logger = LoggerFactory.getLogger(this.getClass()); - @Autowired private LoginService loginService; @@ -43,14 +37,6 @@ public class AuthInterceptor extends HandlerInterceptorAdapter { if (!ok) { return false; } - - UserInfo userInfo = loginService.parse(request, response); - if (userInfo.getUser() == null) { - logger.warn("cannot parse userinfo, userInfo:{}", userInfo); - WebUtil.print(response, "User does not exist or wrong password"); - return false; - } - WebUtil.setAttribute(request, UserInfo.USER_INFO, userInfo); return true; } } diff --git a/src/main/java/org/apache/rocketmq/console/model/User.java b/src/main/java/org/apache/rocketmq/console/model/User.java index 8c80d3f..7dd2ba8 100644 --- a/src/main/java/org/apache/rocketmq/console/model/User.java +++ b/src/main/java/org/apache/rocketmq/console/model/User.java @@ -35,6 +35,10 @@ public class User { this.type = type; } + public User cloneOne() { + return new User(this.name, this.password, this.type); + } + public long getId() { return id; } diff --git a/src/main/java/org/apache/rocketmq/console/service/LoginService.java b/src/main/java/org/apache/rocketmq/console/service/LoginService.java index 7577722..34af054 100644 --- a/src/main/java/org/apache/rocketmq/console/service/LoginService.java +++ b/src/main/java/org/apache/rocketmq/console/service/LoginService.java @@ -17,13 +17,9 @@ package org.apache.rocketmq.console.service; -import org.apache.rocketmq.console.model.UserInfo; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface LoginService { - String getLoginId(HttpServletRequest request); boolean login(HttpServletRequest request, HttpServletResponse response); - UserInfo parse(HttpServletRequest request, HttpServletResponse response); } diff --git a/src/main/java/org/apache/rocketmq/console/service/impl/LoginServiceImpl.java b/src/main/java/org/apache/rocketmq/console/service/impl/LoginServiceImpl.java index 594e3c8..79781c5 100644 --- a/src/main/java/org/apache/rocketmq/console/service/impl/LoginServiceImpl.java +++ b/src/main/java/org/apache/rocketmq/console/service/impl/LoginServiceImpl.java @@ -17,12 +17,9 @@ package org.apache.rocketmq.console.service.impl; -import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.console.config.RMQConfigure; -import org.apache.rocketmq.console.model.UserInfo; import org.apache.rocketmq.console.service.LoginService; import org.apache.rocketmq.console.service.UserService; -import org.apache.rocketmq.console.util.CipherHelper; import org.apache.rocketmq.console.util.WebUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +31,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.net.URLEncoder; @Service @@ -44,34 +40,13 @@ public class LoginServiceImpl implements LoginService { @Resource private RMQConfigure rmqConfigure; - @Resource - private CipherHelper cipherHelper; - @Autowired private UserService userService; - @Override - public String getLoginId(HttpServletRequest request) { - String loginToken = WebUtil.getLoginCookieValue(request); - if (loginToken != null) { - String userName = cipherHelper.decrypt(loginToken); - if (StringUtils.isNotBlank(loginToken)) { - WebUtil.setAttribute(request, "username", userName); - return userName; - } - } - return null; - } - - private String getLoginId(String ticket) { - // You can extend this func to support external ticket - return null; - } @Override public boolean login(HttpServletRequest request, HttpServletResponse response) { - String username = getLoginId(request); - if (username != null) { + if (WebUtil.getValueFromSession(request, WebUtil.USER_NAME) != null) { return true; } @@ -79,40 +54,6 @@ public class LoginServiceImpl implements LoginService { return false; } - @Override - public UserInfo parse(HttpServletRequest request, HttpServletResponse response) { - String ip = WebUtil.getIp(request); - UserInfo userInfo = new UserInfo(); - userInfo.setIp(ip); - userInfo.setLoginTime(System.currentTimeMillis()); - - Object username = WebUtil.getAttribute(request, "username"); - if (username == null) { - userInfo.setUser(null); - } else { - userInfo.setUser(userService.queryByName(username.toString())); - } - - return userInfo; - } - - private String parseRedirect(HttpServletRequest request) throws Exception { - try { - String redirect = request.getParameter("redirect"); - String url = null; - if (redirect != null) { - url = URLDecoder.decode(redirect, "UTF-8"); - } - if (url != null) { - logger.info("redirect to:" + url); - return url; - } - } catch (Exception e) { - logger.error("", e); - } - return ""; - } - protected void auth(HttpServletRequest request, HttpServletResponse response) { try { String url = WebUtil.getUrl(request); diff --git a/src/main/java/org/apache/rocketmq/console/service/impl/UserServiceImpl.java b/src/main/java/org/apache/rocketmq/console/service/impl/UserServiceImpl.java index fc7d625..54ffd83 100644 --- a/src/main/java/org/apache/rocketmq/console/service/impl/UserServiceImpl.java +++ b/src/main/java/org/apache/rocketmq/console/service/impl/UserServiceImpl.java @@ -18,6 +18,7 @@ package org.apache.rocketmq.console.service.impl; import org.apache.rocketmq.console.config.RMQConfigure; +import org.apache.rocketmq.console.exception.ServiceException; import org.apache.rocketmq.console.model.User; import org.apache.rocketmq.console.service.UserService; import org.apache.rocketmq.srvutil.FileWatchService; @@ -59,7 +60,7 @@ public class UserServiceImpl implements UserService, InitializingBean { } } - private static class FileBasedUserInfoStore { + /*packaged*/ static class FileBasedUserInfoStore { private final Logger log = LoggerFactory.getLogger(this.getClass()); private static final String FILE_NAME = "users.properties"; @@ -68,9 +69,14 @@ public class UserServiceImpl implements UserService, InitializingBean { public FileBasedUserInfoStore(RMQConfigure configure) { - log.info("XXXXXX " + configure); filePath = configure.getRocketMqConsoleDataPath() + File.separator + FILE_NAME; + if (!new File(filePath).exists()) { + //Use the default path + filePath = this.getClass().getResource("/" + FILE_NAME).getPath(); + } + log.info(String.format("Login Users configure file is %s", filePath)); load(); + watch(); } private void load() { @@ -79,7 +85,7 @@ public class UserServiceImpl implements UserService, InitializingBean { try { prop.load(new FileReader(filePath)); } catch (Exception e) { - throw new RuntimeException(String.format("Failed to load loginUserInfo property file: %s", filePath)); + throw new ServiceException(0, String.format("Failed to load loginUserInfo property file: %s", filePath)); } Map loadUserMap = new HashMap<>(); @@ -131,7 +137,7 @@ public class UserServiceImpl implements UserService, InitializingBean { public User queryByUsernameAndPassword(@NotNull String username, @NotNull String password) { User user = queryByName(username); if (user != null && password.equals(user.getPassword())) { - return user; + return user.cloneOne(); } return null; diff --git a/src/main/java/org/apache/rocketmq/console/util/CipherHelper.java b/src/main/java/org/apache/rocketmq/console/util/CipherHelper.java deleted file mode 100644 index 045a8a0..0000000 --- a/src/main/java/org/apache/rocketmq/console/util/CipherHelper.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.rocketmq.console.util; - -import org.apache.commons.codec.binary.Base64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.crypto.Cipher; -import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; - -public class CipherHelper { - private Logger logger = LoggerFactory.getLogger(this.getClass()); - - private static final String ALGORITHM = "AES"; - - private SecretKeySpec secretKeySpec; - - public CipherHelper(String ciperKey) throws UnsupportedEncodingException { - secretKeySpec = new SecretKeySpec(ciperKey.getBytes("UTF-8"), ALGORITHM); - } - - /** - * encrypt - * @param toBeEncrypt - * @return - */ - public String encrypt(String toBeEncrypt) { - try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); - byte[] encrypted = cipher.doFinal(toBeEncrypt.getBytes()); - return Base64.encodeBase64String(encrypted); - } catch (Exception e) { - logger.error("encrypt:{} err", toBeEncrypt, e); - } - return null; - } - - /** - * decrypt - * @param encrypted - * @return - */ - public String decrypt(String encrypted) { - try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); - byte[] decryptedBytes = cipher.doFinal(Base64.decodeBase64(encrypted)); - return new String(decryptedBytes); - } catch (Exception e) { - logger.error("decrypt:{} err", encrypted, e); - } - return null; - } -} diff --git a/src/main/java/org/apache/rocketmq/console/util/WebUtil.java b/src/main/java/org/apache/rocketmq/console/util/WebUtil.java index 330d74c..553b084 100644 --- a/src/main/java/org/apache/rocketmq/console/util/WebUtil.java +++ b/src/main/java/org/apache/rocketmq/console/util/WebUtil.java @@ -18,17 +18,20 @@ package org.apache.rocketmq.console.util; import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.console.model.User; +import org.apache.rocketmq.console.model.UserInfo; import javax.servlet.ServletRequest; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; public class WebUtil { - public static final String LOGIN_TOKEN = "TOKEN"; + public static final String USER_INFO = "userInfo"; public static final String USER_NAME = "username"; + public static final String NEED_LOGIN = "needLogin"; /** * Obtain ServletRequest header value @@ -66,48 +69,6 @@ public class WebUtil { return addr; } - /** - * Obtain cookie - * - * @param request - * @param name - * @return - */ - public static Cookie getCookie(HttpServletRequest request, String name) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (name.equals(cookie.getName())) { - return cookie; - } - } - } - return null; - } - - public static void setCookie(HttpServletResponse response, String key, String value, boolean removeFlag) { - Cookie cookie = new Cookie(key, value); - cookie.setPath("/"); - if (removeFlag) { - cookie.setMaxAge(0); - } - response.addCookie(cookie); - } - - /** - * Obtain login cookie info - * - * @param request - * @return - */ - public static String getLoginCookieValue(HttpServletRequest request) { - Cookie cookie = getCookie(request, LOGIN_TOKEN); - if (cookie != null) { - return cookie.getValue(); - } - return null; - } - public static void redirect(HttpServletResponse response, HttpServletRequest request, String path) throws IOException { response.sendRedirect(request.getContextPath() + path); } @@ -127,26 +88,6 @@ public class WebUtil { return url; } - /** - * Fetch attribute form request - * - * @param request - * @return - */ - public static void setAttribute(ServletRequest request, String name, Object obj) { - request.setAttribute(name, obj); - } - - /** - * Set attribute to request - * - * @param request - * @return - */ - public static Object getAttribute(ServletRequest request, String name) { - return request.getAttribute(name); - } - /** * Write content to front-page/response * @@ -161,4 +102,35 @@ public class WebUtil { out.flush(); out.close(); } + + public static Object getValueFromSession(HttpServletRequest request, String key) { + HttpSession session = request.getSession(false); + + if (session != null) { + return session.getAttribute(key); + } + + return null; + } + + public static UserInfo setLoginInfo(HttpServletRequest request, HttpServletResponse response, User user) { + String ip = WebUtil.getIp(request); + UserInfo userInfo = new UserInfo(); + userInfo.setIp(ip); + userInfo.setLoginTime(System.currentTimeMillis()); + + userInfo.setUser(user); + + return userInfo; + } + + public static void removeSession(HttpServletRequest request) { + HttpSession session = request.getSession(); + session.invalidate(); + } + + public static void setSessionValue(HttpServletRequest request, String key, Object value) { + HttpSession session = request.getSession(); + session.setAttribute(key, value); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9293fcb..d75f709 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,4 +26,4 @@ rocketmq.config.msgTrackTopicName= rocketmq.config.ticketKey=ticket #Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required -rocketmq.config.loginRequired=true \ No newline at end of file +rocketmq.config.loginRequired=false \ No newline at end of file diff --git a/src/main/resources/static/src/app.js b/src/main/resources/static/src/app.js index 79fc160..5843afc 100644 --- a/src/main/resources/static/src/app.js +++ b/src/main/resources/static/src/app.js @@ -15,6 +15,8 @@ * limitations under the License. */ 'use strict'; +var initFlag = false; +var loginFlag = false; var app = angular.module('app', [ 'ngAnimate', 'ngCookies', @@ -29,43 +31,60 @@ var app = angular.module('app', [ 'localytics.directives', 'pascalprecht.translate' ]).run( - ['$rootScope','$location','$cookies','$http', - function ($rootScope,$location,$cookies,$http) { - // var filter = function(url){ - // var outFilterArrs = [] - // outFilterArrs.push("/login"); - // outFilterArrs.push("/reg"); - // outFilterArrs.push("/logout"); - // outFilterArrs.push("/404"); - // var flag = false; - // $.each(outFilterArrs,function(i,value){ - // if(url.indexOf(value) > -1){ - // flag = true; - // return false; - // } - // }); - // return flag; - // } + ['$rootScope','$location','$cookies','$http', '$window','Notification', + function ($rootScope,$location,$cookies,$http, $window, Notification) { + var init = function(callback){ + if (initFlag) return; + initFlag = true; - // if(angular.isDefined($cookies.get("isLogin")) && $cookies.get("isLogin") == 'true'){ - // chatApi.login(); - // } + //TODO: make the session timeout consistent with backend +// var sessionId = $cookies.get("JSESSIONID"); +// console.log("sessionId "+ sessionId); +// +// if (sessionId === undefined || sessionId == null) { +// $window.sessionStorage.clear(); +// } - $rootScope.username = $cookies.get("username"); - if (!angular.isDefined($rootScope.username)) { - $rootScope.username = ''; + var url = '/login/check.query'; + var setting = { + type: "GET", + timeout:15000, + success:callback, + async:false + } + //sync invoke + $.ajax(url,setting) } - console.log("username " + $rootScope.username); - $rootScope.globals = $cookies.get('TOKEN'); - console.log('TOKEN ' + $rootScope.globals); + console.log('initFlag0='+ initFlag + ' loginFlag0==='+loginFlag); + $rootScope.$on('$locationChangeStart', function (event, next, current) { // redirect to login page if not logged in and trying to access a restricted page - var restrictedPage = $.inArray($location.path(), ['/login']) === -1; - var loggedIn = $rootScope.globals; - if (restrictedPage && (!angular.isDefined(loggedIn) || !loggedIn)) { - var callback = $location.path(); - $location.path('/login'); + init(function(resp){ + if (resp.status == 0) { + // console.log('resp.data==='+resp.data); + loginFlag = resp.data; + }else { + Notification.error({message: "" + resp.errMsg, delay: 2000}); + } + }); + + console.log('initFlag='+ initFlag + ' loginFlag==='+loginFlag); + $rootScope.username = ''; + if (loginFlag || loginFlag == "true") { + var username = $window.sessionStorage.getItem("username"); + + if (username != null) { + $rootScope.username = username; + } + + // console.log("username " + $rootScope.username); + var restrictedPage = $.inArray($location.path(), ['/login']) === -1; + if (restrictedPage && !username) { + var callback = $location.path(); + $location.path('/login'); + } } + }); @@ -94,6 +113,19 @@ var app = angular.module('app', [ } }); +app.factory('abc', function ($http, $window) { +console.log('xxxxxxx'); + $http({ + method: "GET", + url: "/login/check.query" + }).success(function (resp) { + if (resp.status == 0) { + alert(resp.data) + } + }); + return 1; +}); + app.provider('getDictName', function () { var dictList = []; @@ -142,6 +174,9 @@ app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNamePro } }); + // check login status + + $httpProvider.defaults.cache = false; $routeProvider.when('/', { diff --git a/src/main/resources/static/src/controller.js b/src/main/resources/static/src/controller.js index 6d62c10..cd4c295 100644 --- a/src/main/resources/static/src/controller.js +++ b/src/main/resources/static/src/controller.js @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -app.controller('AppCtrl', ['$scope','$rootScope','$cookies','$location','$translate','$http','Notification', function ($scope,$rootScope,$cookies,$location,$translate, $http, Notification) { +app.controller('AppCtrl', ['$scope','$window','$translate','$http','Notification', function ($scope,$window,$translate, $http, Notification) { $scope.changeTranslate = function(langKey){ $translate.use(langKey); } @@ -22,10 +22,10 @@ app.controller('AppCtrl', ['$scope','$rootScope','$cookies','$location','$transl $scope.logout = function(){ $http({ method: "POST", - url: "login/logout" + url: "login/logout.do" }).success(function (resp) { window.location = "/"; - $cookies.remove("username"); + $window.sessionStorage.clear(); }); } }]); diff --git a/src/main/resources/static/src/login.js b/src/main/resources/static/src/login.js index 7e561e7..abd1430 100644 --- a/src/main/resources/static/src/login.js +++ b/src/main/resources/static/src/login.js @@ -15,7 +15,7 @@ * limitations under the License. */ -app.controller('loginController', ['$scope','$location','$http','Notification','$cookies', function ($scope,$location,$http,Notification,$cookies) { +app.controller('loginController', ['$scope','$location','$http','Notification','$cookies','$window', function ($scope,$location,$http,Notification,$cookies, $window) { $scope.login = function () { if(!$("#username").val()) { alert("用户名不能为空"); @@ -28,12 +28,14 @@ app.controller('loginController', ['$scope','$location','$http','Notification',' $http({ method: "POST", - url: "login/check", + url: "login/login.do", params:{username:$("#username").val(), password:$("#password").val()} }).success(function (resp) { if (resp.status == 0) { Notification.info({message: 'Login successful, redirect now', delay: 2000}); + $window.sessionStorage.setItem("username", $("#username").val()); window.location = "/"; + initFlag = false; } else{ Notification.error({message: resp.errMsg, delay: 2000}); } diff --git a/src/main/resources/static/view/pages/login.html b/src/main/resources/static/view/pages/login.html index 1dbb0ed..32b843a 100644 --- a/src/main/resources/static/view/pages/login.html +++ b/src/main/resources/static/view/pages/login.html @@ -37,8 +37,6 @@ \ No newline at end of file diff --git a/src/main/resources/users.properties b/src/main/resources/users.properties new file mode 100644 index 0000000..16690c0 --- /dev/null +++ b/src/main/resources/users.properties @@ -0,0 +1,9 @@ +# This file supports hot change, any change will be auto-reloaded without Console restarting. +# Format: a user per line, username=password[,N] #N is optional, 0 (Normal User); 1 (Admin) + +# Define Admin +admin=admin,1 + +# Define Users +user1=user1 +user2=user2 \ No newline at end of file diff --git a/src/test/java/org/apache/rocketmq/console/service/impl/LoginFileTest.java b/src/test/java/org/apache/rocketmq/console/service/impl/LoginFileTest.java new file mode 100644 index 0000000..0bb59d4 --- /dev/null +++ b/src/test/java/org/apache/rocketmq/console/service/impl/LoginFileTest.java @@ -0,0 +1,26 @@ +package org.apache.rocketmq.console.service.impl; + +import org.apache.rocketmq.console.config.RMQConfigure; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class LoginFileTest { + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testLoad() throws Exception { + RMQConfigure configure = new RMQConfigure(); + configure.setDataPath(this.getClass().getResource("/").getPath()); + + UserServiceImpl.FileBasedUserInfoStore fileBasedUserInfoStore = new UserServiceImpl.FileBasedUserInfoStore(configure); + Assert.assertTrue("No exception raise for FileBasedUserInfoStore", true); + } +} diff --git a/src/test/resources/users.properties b/src/test/resources/users.properties new file mode 100644 index 0000000..4fc20b0 --- /dev/null +++ b/src/test/resources/users.properties @@ -0,0 +1,2 @@ +admin=admin +test=test \ No newline at end of file