This is an automated email from the ASF dual-hosted git repository. min pushed a commit to branch login in repository https://gitbox.apache.org/repos/asf/incubator-dubbo-ops.git
commit 18ac197ef3e9130b5217670007f522bf6a9d95e7 Author: nzomkxia <z82507...@gmail.com> AuthorDate: Sun Jan 13 17:08:49 2019 +0800 add login module --- .../org/apache/dubbo/admin/common/LoginFilter.java | 224 +++++++++++++++++++++ .../org/apache/dubbo/admin/model/domain/User.java | 2 +- .../apache/dubbo/admin/service/UserService.java | 52 +++++ .../dubbo/admin/service/impl/UserServiceImpl.java | 139 +++++++++++++ .../src/components/public/Toolbar.vue | 109 +++++----- 5 files changed, 480 insertions(+), 46 deletions(-) diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/LoginFilter.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/LoginFilter.java new file mode 100644 index 0000000..dd5a3d7 --- /dev/null +++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/common/LoginFilter.java @@ -0,0 +1,224 @@ +/* + * 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.dubbo.admin.common; + +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.admin.common.util.CoderUtil; +import org.apache.dubbo.admin.common.util.Constants; +import org.apache.dubbo.admin.model.domain.User; +import org.apache.dubbo.admin.service.UserService; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LoginFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class); + private static Pattern PARAMETER_PATTERN = Pattern.compile("(\\w+)=[\"]?([^,\"]+)[\"]?[,]?\\s*"); + private static final String BASIC_CHALLENGE = "Basic"; + private static final String DIGEST_CHALLENGE = "Digest"; + private static final String CHALLENGE = BASIC_CHALLENGE; + private static final String REALM = User.REALM; + + @Autowired + private UserService userService; + private String logout = "/api/dev/logout"; + private String logoutCookie = "logout"; + + + + static Map<String, String> parseParameters(String query) { + Matcher matcher = PARAMETER_PATTERN.matcher(query); + Map<String, String> map = new HashMap<String, String>(); + while (matcher.find()) { + String key = matcher.group(1); + String value = matcher.group(2); + map.put(key, value); + } + return map; + } + + static byte[] readToBytes(InputStream in) throws IOException { + byte[] buf = new byte[in.available()]; + in.read(buf); + return buf; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse) response; + if (logger.isInfoEnabled()) { + logger.info("AuthorizationValve of uri: " + req.getRequestURI()); + } + String uri = req.getRequestURI(); + String contextPath = req.getContextPath(); + if (contextPath != null && contextPath.length() > 0 && !"/".equals(contextPath)) { + uri = uri.substring(contextPath.length()); + } + if (uri.equals(logout)) { + if (!isLogout(req)) { + setLogout(true, resp); + showLoginForm(resp); + } else { + setLogout(false, resp); +// resp.sendRedirect(contextPath == null || contextPath.length() == 0 ? "/" : contextPath); + } + return; + } + User user = null; + String authType = null; + String authorization = req.getHeader("Authorization"); + if (authorization != null && authorization.length() > 0) { + int i = authorization.indexOf(' '); + if (i >= 0) { + authType = authorization.substring(0, i); + String authPrincipal = authorization.substring(i + 1); + if (BASIC_CHALLENGE.equalsIgnoreCase(authType)) { + user = loginByBase(authPrincipal); + } else if (DIGEST_CHALLENGE.equalsIgnoreCase(authType)) { + user = loginByDigest(authPrincipal, req); + } + } + } + if (user == null || user.getUsername() == null || user.getUsername().length() == 0) { + showLoginForm(resp); + return; + //pipelineContext.breakPipeline(1); + } + if (user != null && StringUtils.isNotEmpty(user.getUsername())) { + req.getSession().setAttribute("currentUser", user); + chain.doFilter(request, response); + } + + } + + @Override + public void destroy() { + + } + + private void showLoginForm(HttpServletResponse response) throws IOException { + if (DIGEST_CHALLENGE.equals(CHALLENGE)) { + response.setHeader("WWW-Authenticate", CHALLENGE + " realm=\"" + REALM + "\", qop=\"auth\", nonce=\"" + + UUID.randomUUID().toString().replace("-", "") + "\", opaque=\"" + + CoderUtil.MD5_32bit(REALM) + "\""); + } else { + response.setHeader("WWW-Authenticate", CHALLENGE + " realm=\"" + REALM + "\""); + } + response.setHeader("Cache-Control", "must-revalidate,no-cache,no-store"); + response.setHeader("Content-Type", "text/html; charset=iso-8859-1"); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + + private User getUser(String username) { + return userService.findUser(username); + } + + private User loginByBase(String authorization) { + authorization = CoderUtil.decodeBase64(authorization); + int i = authorization.indexOf(':'); + String username = authorization.substring(0, i); + if (username != null && username.length() > 0) { + String password = authorization.substring(i + 1); + if (password != null && password.length() > 0) { + String passwordDigest = CoderUtil.MD5_32bit(username + ":" + REALM + ":" + password); + User user = getUser(username); + if (user != null) { + String pwd = user.getPassword(); + if (pwd != null && pwd.length() > 0) { + if (passwordDigest.equals(pwd)) { + return user; + } + } + } + } + } + return null; + } + + private User loginByDigest(String value, HttpServletRequest request) throws IOException { + Map<String, String> params = parseParameters(value); + String username = params.get("username"); + if (username != null && username.length() > 0) { + String passwordDigest = params.get("response"); + if (passwordDigest != null && passwordDigest.length() > 0) { + User user = getUser(username); + if (user != null) { + String pwd = user.getPassword(); + // A valid user, validate password + if (pwd != null && pwd.length() > 0) { + String uri = params.get("uri"); + String nonce = params.get("nonce"); + String nc = params.get("nc"); + String cnonce = params.get("cnonce"); + String qop = params.get("qop"); + String method = request.getMethod(); + String a1 = pwd; + + String a2 = "auth-int".equals(qop) + ? CoderUtil.MD5_32bit(method + ":" + uri + ":" + CoderUtil.MD5_32bit(readToBytes(request.getInputStream()))) + : CoderUtil.MD5_32bit(method + ":" + uri); + String digest = "auth".equals(qop) || "auth-int".equals(qop) + ? CoderUtil.MD5_32bit(a1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2) + : CoderUtil.MD5_32bit(a1 + ":" + nonce + ":" + a2); + if (digest.equals(passwordDigest)) { + return user; + } + } + } + } + } + return null; + } + + private boolean isLogout(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (cookie != null && logoutCookie.equals(cookie.getName())) { + return "true".equals(cookie.getValue()); + } + } + } + return false; + } + + private void setLogout(boolean logoutValue, HttpServletResponse response) { + response.addCookie(new Cookie(logoutCookie, String.valueOf(logoutValue))); + } +} diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/User.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/User.java index 4038e12..1209fad 100644 --- a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/User.java +++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/model/domain/User.java @@ -27,7 +27,7 @@ import java.util.List; */ public class User extends Entity { - public static final String REALM = "dubbo"; + public static final String REALM = "dubbo admin"; public static final String ROOT = "R"; public static final String ADMINISTRATOR = "A"; public static final String MANAGER = "M"; diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/UserService.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/UserService.java new file mode 100644 index 0000000..fd85f13 --- /dev/null +++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/UserService.java @@ -0,0 +1,52 @@ +/* + * 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.dubbo.admin.service; + +import org.apache.dubbo.admin.model.domain.User; + +import java.util.List; + +public interface UserService { + + List<User> findAllUsers(); + + User findUser(String username); + + String currentUserName(); + + void setCurrentUser(String name); + + User findById(Long id); + + void createUser(User user); + + void updateUser(User user); + + void modifyUser(User user); + + boolean updatePassword(User user, String oldPassword); + + void resetPassword(User user); + + void enableUser(User user); + + void disableUser(User user); + + void deleteUser(User user); + +} diff --git a/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/UserServiceImpl.java b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..3045149 --- /dev/null +++ b/dubbo-admin-backend/src/main/java/org/apache/dubbo/admin/service/impl/UserServiceImpl.java @@ -0,0 +1,139 @@ +/* + * 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.dubbo.admin.service.impl; + +import org.apache.dubbo.admin.common.util.CoderUtil; +import org.apache.dubbo.admin.model.domain.User; +import org.apache.dubbo.admin.service.UserService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +@Component +public class UserServiceImpl implements UserService { + @Value("${admin.root.password}") + private String rootPassword; + @Value("${admin.guest.password}") + private String guestPassword; + + private String currentUser; + + public void setRootPassword(String password) { + this.rootPassword = (password == null ? "" : password); + } + + public void setGuestPassword(String password) { + this.guestPassword = (password == null ? "" : password); + } + + public User findUser(String username) { + if ("guest".equals(username)) { + User user = new User(); + user.setUsername(username); + user.setPassword(CoderUtil.MD5_32bit(username + ":" + User.REALM + ":" + guestPassword)); + user.setName(username); + user.setRole(User.GUEST); + user.setEnabled(true); + user.setLocale("zh"); + user.setServicePrivilege(""); + return user; + } else if ("root".equals(username)) { + User user = new User(); + user.setUsername(username); + user.setPassword(CoderUtil.MD5_32bit(username + ":" + User.REALM + ":" + rootPassword)); + user.setName(username); + user.setRole(User.ROOT); + user.setEnabled(true); + user.setLocale("zh"); + user.setServicePrivilege("*"); + return user; + } + return null; + } + + @Override + public String currentUserName() { + return this.currentUser; + } + + public void setCurrentUser(String userName) { + this.currentUser = userName; + } + + public List<User> findAllUsers() { + // TODO Auto-generated method stub + return null; + } + + public Map<String, User> findAllUsersMap() { + // TODO Auto-generated method stub + return null; + } + + public User findById(Long id) { + // TODO Auto-generated method stub + return null; + } + + public void createUser(User user) { + // TODO Auto-generated method stub + + } + + public void updateUser(User user) { + // TODO Auto-generated method stub + + } + + public void modifyUser(User user) { + // TODO Auto-generated method stub + + } + + public boolean updatePassword(User user, String oldPassword) { + // TODO Auto-generated method stub + return false; + } + + public void resetPassword(User user) { + // TODO Auto-generated method stub + + } + + public void enableUser(User user) { + // TODO Auto-generated method stub + + } + + public void disableUser(User user) { + // TODO Auto-generated method stub + + } + + public void deleteUser(User user) { + // TODO Auto-generated method stub + + } + + public List<User> findUsersByServiceName(String serviceName) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/dubbo-admin-frontend/src/components/public/Toolbar.vue b/dubbo-admin-frontend/src/components/public/Toolbar.vue index 66fdd35..639c7ab 100644 --- a/dubbo-admin-frontend/src/components/public/Toolbar.vue +++ b/dubbo-admin-frontend/src/components/public/Toolbar.vue @@ -37,11 +37,42 @@ <v-spacer></v-spacer> - <!--settings button--> - <v-btn icon v-if="false"> - <v-icon>settings</v-icon> + <!--sign in button--> + <v-btn flat v-if="!signIn" @click="login"> + Sign In </v-btn> + <v-menu offset-y origin="center center" :nudge-bottom="10" transition="scale-transition" v-if="signIn"> + <v-btn icon large flat slot="activator"> + <v-avatar size="30px"> + <img src="@/assets/avatar.png" alt="Logined User" /> + </v-avatar> + </v-btn> + <v-card> + <v-card-text> + User: {{userName}} + </v-card-text> + </v-card> + <v-list class="pa-0"> + <v-list-tile @click="logout"> + <v-list-tile-action> + <v-icon>exit_to_app</v-icon> + </v-list-tile-action> + <v-list-tile-content> + <v-list-tile-title>logout</v-list-tile-title> + </v-list-tile-content> + </v-list-tile> + <!--<v-list-tile v-for="(item,index) in items" :to="!item.href ? { name: item.name } : null" :href="item.href" @click="item.click" ripple="ripple" :disabled="item.disabled" :target="item.target" rel="noopener" :key="index">--> + <!--<v-list-tile-action v-if="item.icon">--> + <!--<v-icon>{{ item.icon }}</v-icon>--> + <!--</v-list-tile-action>--> + <!--<v-list-tile-content>--> + <!--<v-list-tile-title>{{ item.title }}</v-list-tile-title>--> + <!--</v-list-tile-content>--> + <!--</v-list-tile>--> + </v-list> + </v-menu> + <!--full screen button--> <v-btn icon @click="handleFullScreen()"> <v-icon>fullscreen</v-icon> @@ -72,24 +103,6 @@ <!--<notification-list></notification-list>--> </v-menu> - <!--login user info--> - <v-menu offset-y origin="center center" :nudge-bottom="10" transition="scale-transition" v-if="false"> - <v-btn icon large flat slot="activator"> - <v-avatar size="30px"> - <img src="@/assets/avatar.png" alt="Logined User" /> - </v-avatar> - </v-btn> - <v-list class="pa-0"> - <v-list-tile v-for="(item,index) in items" :to="!item.href ? { name: item.name } : null" :href="item.href" @click="item.click" ripple="ripple" :disabled="item.disabled" :target="item.target" rel="noopener" :key="index"> - <v-list-tile-action v-if="item.icon"> - <v-icon>{{ item.icon }}</v-icon> - </v-list-tile-action> - <v-list-tile-content> - <v-list-tile-title>{{ item.title }}</v-list-tile-title> - </v-list-tile-content> - </v-list-tile> - </v-list> - </v-menu> </v-toolbar> </template> <script> @@ -99,35 +112,35 @@ data: () => ({ selectedLang: '', global: '', + userName: 'root', + signIn: true, lang: [ '简体中文', 'English' ], items: [ - { - icon: 'account_circle', - href: '#', - title: 'Profile', - click: (e) => { - console.log(e) - } - }, - { - icon: 'settings', - href: '#', - title: 'Settings', - click: (e) => { - console.log(e) - } - }, - { - icon: 'fullscreen_exit', - href: '#', - title: 'Logout', - click: (e) => { - window.getApp.$emit('APP_LOGOUT') - } - } + // { + // icon: 'account_circle', + // href: '#', + // title: 'Profile', + // click: (e) => { + // console.log(e) + // } + // }, + // { + // icon: 'settings', + // href: '#', + // title: 'Settings', + // click: (e) => { + // console.log(e) + // } + // }, + // { + // icon: 'exit_to_app', + // href: '#', + // title: 'Logout', + // click: logout + // } ] }), methods: { @@ -140,6 +153,12 @@ } this.global = '' }, + logout () { + this.$axios.get('/logout') + .then(response => { + + }) + }, handleDrawerToggle () { window.getApp.$emit('DRAWER_TOGGLED') },