permissionInfoPage = permissionPersistService
+ .getPermissions(role, DEFAULT_PAGE_NO, Integer.MAX_VALUE);
+ tmpPermissionInfoMap.put(role, permissionInfoPage.getPageItems());
+ }
+
+ roleSet = tmpRoleSet;
+ roleInfoMap = tmpRoleInfoMap;
+ permissionInfoMap = tmpPermissionInfoMap;
+ } catch (Exception e) {
+ LOGGER.warn("[LOAD-ROLES] load failed", e);
+ }
+ }
+
+ /**
+ * Determine if the user has permission of the resource.
+ *
+ * Note if the user has many roles, this method returns true if any one role of the user has the desired
+ * permission.
+ *
+ * @param username user info
+ * @param permission permission to auth
+ * @return true if granted, false otherwise
+ */
+ public boolean hasPermission(String username, Permission permission) {
+ //update password
+ if (NacosAuthConfig.UPDATE_PASSWORD_ENTRY_POINT.equals(permission.getResource())) {
+ return true;
+ }
+
+ List roleInfoList = getRoles(username);
+ if (Collections.isEmpty(roleInfoList)) {
+ return false;
+ }
+
+ // Global admin pass:
+ for (RoleInfo roleInfo : roleInfoList) {
+ if (GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) {
+ return true;
+ }
+ }
+
+ // Old global admin can pass resource 'console/':
+ if (permission.getResource().startsWith(NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX)) {
+ return false;
+ }
+
+ // For other roles, use a pattern match to decide if pass or not.
+ for (RoleInfo roleInfo : roleInfoList) {
+ List permissionInfoList = getPermissions(roleInfo.getRole());
+ if (Collections.isEmpty(permissionInfoList)) {
+ continue;
+ }
+ for (PermissionInfo permissionInfo : permissionInfoList) {
+ String permissionResource = permissionInfo.getResource().replaceAll("\\*", ".*");
+ String permissionAction = permissionInfo.getAction();
+ if (permissionAction.contains(permission.getAction()) && Pattern
+ .matches(permissionResource, permission.getResource())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List getRoles(String username) {
+ List roleInfoList = roleInfoMap.get(username);
+ if (!authConfigs.isCachingEnabled() || roleInfoList == null) {
+ Page roleInfoPage = getRolesFromDatabase(username, DEFAULT_PAGE_NO, Integer.MAX_VALUE);
+ if (roleInfoPage != null) {
+ roleInfoList = roleInfoPage.getPageItems();
+ }
+ }
+ return roleInfoList;
+ }
+
+ public Page getRolesFromDatabase(String userName, int pageNo, int pageSize) {
+ Page roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize);
+ if (roles == null) {
+ return new Page<>();
+ }
+ return roles;
+ }
+
+ public List getPermissions(String role) {
+ List permissionInfoList = permissionInfoMap.get(role);
+ if (!authConfigs.isCachingEnabled() || permissionInfoList == null) {
+ Page permissionInfoPage = getPermissionsFromDatabase(role, DEFAULT_PAGE_NO,
+ Integer.MAX_VALUE);
+ if (permissionInfoPage != null) {
+ permissionInfoList = permissionInfoPage.getPageItems();
+ }
+ }
+ return permissionInfoList;
+ }
+
+ public Page getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
+ Page pageInfo = permissionPersistService.getPermissions(role, pageNo, pageSize);
+ if (pageInfo == null) {
+ return new Page<>();
+ }
+ return pageInfo;
+ }
+}
diff --git a/auth/src/main/java/com/alibaba/nacos/auth/roles/RoleInfo.java b/auth/src/main/java/com/alibaba/nacos/auth/roles/RoleInfo.java
new file mode 100644
index 000000000..8c3e050e2
--- /dev/null
+++ b/auth/src/main/java/com/alibaba/nacos/auth/roles/RoleInfo.java
@@ -0,0 +1,55 @@
+/*
+ * 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.nacos.auth.roles;
+
+import java.io.Serializable;
+
+/**
+ * Role Info.
+ *
+ * @author nkorange
+ * @since 1.2.0
+ */
+public class RoleInfo implements Serializable {
+
+ private static final long serialVersionUID = 5946986388047856568L;
+
+ private String role;
+
+ private String username;
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public String toString() {
+ return "RoleInfo{" + "role='" + role + '\'' + ", username='" + username + '\'' + '}';
+ }
+}
diff --git a/auth/src/main/java/com/alibaba/nacos/auth/users/NacosAuthUserDetailsServiceImpl.java b/auth/src/main/java/com/alibaba/nacos/auth/users/NacosAuthUserDetailsServiceImpl.java
new file mode 100644
index 000000000..fa9a232a0
--- /dev/null
+++ b/auth/src/main/java/com/alibaba/nacos/auth/users/NacosAuthUserDetailsServiceImpl.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 1999-2021 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.nacos.auth.users;
+
+import com.alibaba.nacos.auth.common.AuthConfigs;
+import com.alibaba.nacos.auth.model.Page;
+import com.alibaba.nacos.auth.persist.UserPersistService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Custom user service.
+ *
+ * @author wfnuser
+ * @author nkorange
+ */
+@Service
+public class NacosAuthUserDetailsServiceImpl implements UserDetailsService {
+
+ public static final Logger LOGGER = LoggerFactory.getLogger(NacosAuthUserDetailsServiceImpl.class);
+
+ private Map userMap = new ConcurrentHashMap<>();
+
+ @Autowired
+ private UserPersistService userPersistService;
+
+ @Autowired
+ private AuthConfigs authConfigs;
+
+ @Scheduled(initialDelay = 5000, fixedDelay = 15000)
+ private void reload() {
+ try {
+ Page users = getUsersFromDatabase(1, Integer.MAX_VALUE);
+ if (users == null) {
+ return;
+ }
+
+ Map map = new ConcurrentHashMap<>(16);
+ for (User user : users.getPageItems()) {
+ map.put(user.getUsername(), user);
+ }
+ userMap = map;
+ } catch (Exception e) {
+ LOGGER.warn("[LOAD-USERS] load failed", e);
+ }
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ User user = userMap.get(username);
+ if (!authConfigs.isCachingEnabled()) {
+ user = userPersistService.findUserByUsername(username);
+ }
+
+ if (user == null) {
+ throw new UsernameNotFoundException(username);
+ }
+ return new NacosUserDetails(user);
+ }
+
+ public Page getUsersFromDatabase(int pageNo, int pageSize) {
+ return userPersistService.getUsers(pageNo, pageSize);
+ }
+
+ public User getUser(String username) {
+ User user = userMap.get(username);
+ if (!authConfigs.isCachingEnabled() || user == null) {
+ user = getUserFromDatabase(username);
+ }
+ return user;
+ }
+
+ public void createUser(String username, String password) {
+ userPersistService.createUser(username, password);
+ }
+
+ public User getUserFromDatabase(String username) {
+ return userPersistService.findUserByUsername(username);
+ }
+}
diff --git a/auth/src/main/java/com/alibaba/nacos/auth/users/NacosUserDetails.java b/auth/src/main/java/com/alibaba/nacos/auth/users/NacosUserDetails.java
new file mode 100644
index 000000000..2cb43a6a8
--- /dev/null
+++ b/auth/src/main/java/com/alibaba/nacos/auth/users/NacosUserDetails.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 1999-2021 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.nacos.auth.users;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+
+/**
+ * custom user.
+ *
+ * @author wfnuser
+ */
+public class NacosUserDetails implements UserDetails {
+
+ private final User user;
+
+ public NacosUserDetails(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ // TODO: get authorities
+ return AuthorityUtils.commaSeparatedStringToAuthorityList("");
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/alibaba/nacos/auth/users/User.java b/auth/src/main/java/com/alibaba/nacos/auth/users/User.java
new file mode 100644
index 000000000..b42728ecd
--- /dev/null
+++ b/auth/src/main/java/com/alibaba/nacos/auth/users/User.java
@@ -0,0 +1,49 @@
+/*
+ * 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.nacos.auth.users;
+
+import java.io.Serializable;
+
+/**
+ * User.
+ *
+ * @author wfnuser
+ */
+public class User implements Serializable {
+
+ private static final long serialVersionUID = 3371769277802700069L;
+
+ private String username;
+
+ private String password;
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+}
diff --git a/auth/src/main/java/com/alibaba/nacos/auth/util/PasswordEncoderUtil.java b/auth/src/main/java/com/alibaba/nacos/auth/util/PasswordEncoderUtil.java
new file mode 100644
index 000000000..a81723362
--- /dev/null
+++ b/auth/src/main/java/com/alibaba/nacos/auth/util/PasswordEncoderUtil.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2021 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.nacos.auth.util;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * Password encoder tool.
+ *
+ * @author nacos
+ */
+public class PasswordEncoderUtil {
+
+ public static Boolean matches(String raw, String encoded) {
+ return new BCryptPasswordEncoder().matches(raw, encoded);
+ }
+
+ public static String encode(String raw) {
+ return new BCryptPasswordEncoder().encode(raw);
+ }
+}
diff --git a/auth/src/test/java/com/alibaba/nacos/auth/common/AuthPluginManagerTest.java b/auth/src/test/java/com/alibaba/nacos/auth/common/AuthPluginManagerTest.java
index d2bcabb75..c4f23282b 100644
--- a/auth/src/test/java/com/alibaba/nacos/auth/common/AuthPluginManagerTest.java
+++ b/auth/src/test/java/com/alibaba/nacos/auth/common/AuthPluginManagerTest.java
@@ -19,6 +19,7 @@ package com.alibaba.nacos.auth.common;
import com.alibaba.nacos.auth.AuthPluginManager;
import com.alibaba.nacos.auth.AuthService;
import com.alibaba.nacos.auth.context.IdentityContext;
+import com.alibaba.nacos.auth.exception.AccessException;
import com.alibaba.nacos.auth.model.Permission;
import org.junit.Assert;
import org.junit.Before;
@@ -72,7 +73,7 @@ public class AuthPluginManagerTest {
}
@Test
- public void testFindAuthServiceSpiImpl() {
+ public void testFindAuthServiceSpiImpl() throws AccessException {
Mockito.when(authService.authorityAccess(identityContext, permission)).thenReturn(true);
Mockito.when(authService.getAuthServiceName()).thenReturn(TYPE);
Optional authServiceImpl = authPluginManager.findAuthServiceSpiImpl(TYPE);