* Add auth server implement. * Modify auth server: class name
This commit is contained in:
parent
c771c5d2b9
commit
76ac84344c
15
auth/pom.xml
15
auth/pom.xml
@ -45,12 +45,27 @@
|
|||||||
<artifactId>nacos-sys</artifactId>
|
<artifactId>nacos-sys</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tomcat.embed</groupId>
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
<artifactId>tomcat-embed-core</artifactId>
|
<artifactId>tomcat-embed-core</artifactId>
|
||||||
|
@ -41,8 +41,9 @@ public interface AuthService {
|
|||||||
* @param identityContext where we can find the user information.
|
* @param identityContext where we can find the user information.
|
||||||
* @param permission permission to auth.
|
* @param permission permission to auth.
|
||||||
* @return Boolean if the user has the resource authority.
|
* @return Boolean if the user has the resource authority.
|
||||||
|
* @throws AccessException authority authentication error.
|
||||||
*/
|
*/
|
||||||
Boolean authorityAccess(IdentityContext identityContext, Permission permission);
|
Boolean authorityAccess(IdentityContext identityContext, Permission permission) throws AccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuthService Name which for conveniently find AuthService instance.
|
* AuthService Name which for conveniently find AuthService instance.
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jwt auth fail point.
|
||||||
|
*
|
||||||
|
* @author wfnuser
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
LOGGER.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI());
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
103
auth/src/main/java/com/alibaba/nacos/auth/JwtTokenManager.java
Normal file
103
auth/src/main/java/com/alibaba/nacos/auth/JwtTokenManager.java
Normal file
@ -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;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.common.AuthConfigs;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT token manager.
|
||||||
|
*
|
||||||
|
* @author wfnuser
|
||||||
|
* @author nkorange
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class JwtTokenManager {
|
||||||
|
|
||||||
|
private static final String AUTHORITIES_KEY = "auth";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthConfigs authConfigs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create token.
|
||||||
|
*
|
||||||
|
* @param authentication auth info
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
public String createToken(Authentication authentication) {
|
||||||
|
return createToken(authentication.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create token.
|
||||||
|
*
|
||||||
|
* @param userName auth info
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
public String createToken(String userName) {
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Date validity;
|
||||||
|
validity = new Date(now + authConfigs.getTokenValidityInSeconds() * 1000L);
|
||||||
|
|
||||||
|
Claims claims = Jwts.claims().setSubject(userName);
|
||||||
|
return Jwts.builder().setClaims(claims).setExpiration(validity)
|
||||||
|
.signWith(Keys.hmacShaKeyFor(authConfigs.getSecretKeyBytes()), SignatureAlgorithm.HS256).compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get auth Info.
|
||||||
|
*
|
||||||
|
* @param token token
|
||||||
|
* @return auth info
|
||||||
|
*/
|
||||||
|
public Authentication getAuthentication(String token) {
|
||||||
|
Claims claims = Jwts.parserBuilder().setSigningKey(authConfigs.getSecretKeyBytes()).build()
|
||||||
|
.parseClaimsJws(token).getBody();
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = AuthorityUtils
|
||||||
|
.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
|
||||||
|
|
||||||
|
User principal = new User(claims.getSubject(), "", authorities);
|
||||||
|
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validate token.
|
||||||
|
*
|
||||||
|
* @param token token
|
||||||
|
*/
|
||||||
|
public void validateToken(String token) {
|
||||||
|
Jwts.parserBuilder().setSigningKey(authConfigs.getSecretKeyBytes()).build().parseClaimsJws(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.roles.NacosAuthRoleServiceImpl;
|
||||||
|
import com.alibaba.nacos.auth.roles.RoleInfo;
|
||||||
|
import com.alibaba.nacos.auth.users.NacosUserDetails;
|
||||||
|
import com.alibaba.nacos.auth.users.NacosAuthUserDetailsServiceImpl;
|
||||||
|
import com.alibaba.nacos.auth.users.User;
|
||||||
|
import com.alibaba.nacos.auth.util.PasswordEncoderUtil;
|
||||||
|
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.naming.CommunicationException;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.ldap.InitialLdapContext;
|
||||||
|
import javax.naming.ldap.LdapContext;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.alibaba.nacos.auth.roles.NacosAuthRoleServiceImpl.GLOBAL_ADMIN_ROLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP auth provider.
|
||||||
|
*
|
||||||
|
* @author zjw
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class LdapAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProvider.class);
|
||||||
|
|
||||||
|
private static final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
|
||||||
|
|
||||||
|
private static final String TIMEOUT = "com.sun.jndi.ldap.connect.timeout";
|
||||||
|
|
||||||
|
private static final String DEFAULT_PASSWORD = "nacos";
|
||||||
|
|
||||||
|
private static final String LDAP_PREFIX = "LDAP_";
|
||||||
|
|
||||||
|
private static final String DEFAULT_SECURITY_AUTH = "simple";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NacosAuthUserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NacosAuthRoleServiceImpl nacosRoleService;
|
||||||
|
|
||||||
|
@Value("${nacos.core.auth.ldap.url:ldap://localhost:389}")
|
||||||
|
private String ldapUrl;
|
||||||
|
|
||||||
|
@Value("${nacos.core.auth.ldap.timeout:3000}")
|
||||||
|
private String time;
|
||||||
|
|
||||||
|
@Value("${nacos.core.auth.ldap.userdn:cn={0},ou=user,dc=company,dc=com}")
|
||||||
|
private String userNamePattern;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
String username = (String) authentication.getPrincipal();
|
||||||
|
String password = (String) authentication.getCredentials();
|
||||||
|
|
||||||
|
if (isAdmin(username)) {
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
if (PasswordEncoderUtil.matches(password, userDetails.getPassword())) {
|
||||||
|
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ldapLogin(username, password)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDetails userDetails;
|
||||||
|
try {
|
||||||
|
userDetails = userDetailsService.loadUserByUsername(LDAP_PREFIX + username);
|
||||||
|
} catch (UsernameNotFoundException exception) {
|
||||||
|
String nacosPassword = PasswordEncoderUtil.encode(DEFAULT_PASSWORD);
|
||||||
|
userDetailsService.createUser(LDAP_PREFIX + username, nacosPassword);
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(LDAP_PREFIX + username);
|
||||||
|
user.setPassword(nacosPassword);
|
||||||
|
userDetails = new NacosUserDetails(user);
|
||||||
|
}
|
||||||
|
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin(String username) {
|
||||||
|
List<RoleInfo> roleInfos = nacosRoleService.getRoles(username);
|
||||||
|
if (CollectionUtils.isEmpty(roleInfos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (RoleInfo roleinfo : roleInfos) {
|
||||||
|
if (GLOBAL_ADMIN_ROLE.equals(roleinfo.getRole())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ldapLogin(String username, String password) throws AuthenticationException {
|
||||||
|
Hashtable<String, String> env = new Hashtable<>();
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
|
||||||
|
env.put(Context.PROVIDER_URL, ldapUrl);
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, DEFAULT_SECURITY_AUTH);
|
||||||
|
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, userNamePattern.replace("{0}", username));
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, password);
|
||||||
|
env.put(TIMEOUT, time);
|
||||||
|
LdapContext ctx = null;
|
||||||
|
try {
|
||||||
|
ctx = new InitialLdapContext(env, null);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
LOG.error("LDAP Service connect timeout:{}", e.getMessage());
|
||||||
|
throw new RuntimeException("LDAP Service connect timeout");
|
||||||
|
} catch (javax.naming.AuthenticationException e) {
|
||||||
|
LOG.error("login error:{}", e.getMessage());
|
||||||
|
throw new RuntimeException("login error!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Exception cause by:{}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
closeContext(ctx);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> aClass) {
|
||||||
|
return aClass.equals(UsernamePasswordAuthenticationToken.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeContext(DirContext ctx) {
|
||||||
|
if (ctx != null) {
|
||||||
|
try {
|
||||||
|
ctx.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Exception closing context", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
auth/src/main/java/com/alibaba/nacos/auth/NacosAuthConfig.java
Normal file
132
auth/src/main/java/com/alibaba/nacos/auth/NacosAuthConfig.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.common.AuthConfigs;
|
||||||
|
import com.alibaba.nacos.auth.common.AuthSystemTypes;
|
||||||
|
import com.alibaba.nacos.auth.users.NacosAuthUserDetailsServiceImpl;
|
||||||
|
import com.alibaba.nacos.common.utils.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.BeanIds;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.cors.CorsUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring security config.
|
||||||
|
*
|
||||||
|
* @author Nacos
|
||||||
|
*/
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
|
public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
|
||||||
|
|
||||||
|
public static final String LOGIN_ENTRY_POINT = "/v1/auth/login";
|
||||||
|
|
||||||
|
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**";
|
||||||
|
|
||||||
|
public static final String TOKEN_PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/";
|
||||||
|
|
||||||
|
public static final String UPDATE_PASSWORD_ENTRY_POINT = CONSOLE_RESOURCE_NAME_PREFIX + "user/password";
|
||||||
|
|
||||||
|
private static final String DEFAULT_ALL_PATH_PATTERN = "/**";
|
||||||
|
|
||||||
|
private static final String PROPERTY_IGNORE_URLS = "nacos.security.ignore.urls";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtTokenManager tokenProvider;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthConfigs authConfigs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NacosAuthUserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LdapAuthenticationProvider ldapAuthenticationProvider;
|
||||||
|
|
||||||
|
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
|
||||||
|
@Override
|
||||||
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
|
return super.authenticationManagerBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(WebSecurity web) {
|
||||||
|
|
||||||
|
String ignoreUrls = null;
|
||||||
|
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
ignoreUrls = DEFAULT_ALL_PATH_PATTERN;
|
||||||
|
} else if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
ignoreUrls = DEFAULT_ALL_PATH_PATTERN;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
ignoreUrls = env.getProperty(PROPERTY_IGNORE_URLS, DEFAULT_ALL_PATH_PATTERN);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(ignoreUrls)) {
|
||||||
|
for (String each : ignoreUrls.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
|
||||||
|
web.ignoring().antMatchers(each.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
||||||
|
} else if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
auth.authenticationProvider(ldapAuthenticationProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
|
||||||
|
http.csrf().disable().cors()// We don't need CSRF for JWT based authentication
|
||||||
|
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
|
.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
|
||||||
|
.antMatchers(LOGIN_ENTRY_POINT).permitAll().and().authorizeRequests()
|
||||||
|
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated().and().exceptionHandling()
|
||||||
|
.authenticationEntryPoint(new JwtAuthenticationEntryPoint());
|
||||||
|
// disable cache
|
||||||
|
http.headers().cacheControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.api.common.Constants;
|
||||||
|
import com.alibaba.nacos.auth.context.IdentityContext;
|
||||||
|
import com.alibaba.nacos.auth.exception.AccessException;
|
||||||
|
import com.alibaba.nacos.auth.model.Permission;
|
||||||
|
import com.alibaba.nacos.auth.roles.NacosAuthRoleServiceImpl;
|
||||||
|
import com.alibaba.nacos.auth.roles.RoleInfo;
|
||||||
|
import com.alibaba.nacos.common.utils.StringUtils;
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builtin access control entry of Nacos.
|
||||||
|
*
|
||||||
|
* @author wuyfee
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class NacosAuthServiceImpl implements AuthService {
|
||||||
|
|
||||||
|
private static final String TOKEN_PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
private static final String PARAM_USERNAME = "username";
|
||||||
|
|
||||||
|
private static final String PARAM_PASSWORD = "password";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtTokenManager jwtTokenManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NacosAuthRoleServiceImpl roleService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityContext login(IdentityContext identityContext) throws AccessException {
|
||||||
|
String username = (String) identityContext.getParameter(Constants.USERNAME);
|
||||||
|
String password = (String) identityContext.getParameter(PARAM_PASSWORD);
|
||||||
|
String finalName;
|
||||||
|
Authentication authenticate;
|
||||||
|
try {
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,
|
||||||
|
password);
|
||||||
|
authenticate = authenticationManager.authenticate(authenticationToken);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
throw new AccessException("unknown user!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == authenticate || StringUtils.isBlank(authenticate.getName())) {
|
||||||
|
finalName = username;
|
||||||
|
} else {
|
||||||
|
finalName = authenticate.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = jwtTokenManager.createToken(finalName);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(jwtTokenManager.getAuthentication(token));
|
||||||
|
|
||||||
|
IdentityContext authResult = new IdentityContext();
|
||||||
|
authResult.setParameter(Constants.USERNAME, finalName);
|
||||||
|
authResult.setParameter(Constants.ACCESS_TOKEN, token);
|
||||||
|
authResult.setParameter(Constants.GLOBAL_ADMIN, false);
|
||||||
|
List<RoleInfo> roleInfoList = roleService.getRoles(username);
|
||||||
|
if (roleInfoList != null) {
|
||||||
|
for (RoleInfo roleInfo : roleInfoList) {
|
||||||
|
if (roleInfo.getRole().equals(NacosAuthRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||||
|
authResult.setParameter(Constants.GLOBAL_ADMIN, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean authorityAccess(IdentityContext identityContext, Permission permission) throws AccessException {
|
||||||
|
String token;
|
||||||
|
String bearerToken = (String) identityContext.getParameter(NacosAuthConfig.AUTHORIZATION_HEADER);
|
||||||
|
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||||
|
token = bearerToken.substring(7);
|
||||||
|
} else {
|
||||||
|
token = (String) identityContext.getParameter(Constants.ACCESS_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
String username;
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
username = (String) login(identityContext).getParameter(Constants.USERNAME);
|
||||||
|
} else {
|
||||||
|
username = getUsernameFromToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roleService.hasPermission(username, permission)) {
|
||||||
|
throw new AccessException("authorization failed!");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthServiceName() {
|
||||||
|
return "NacosAuthServiceImpl";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get username from token.
|
||||||
|
*/
|
||||||
|
private String getUsernameFromToken(String token) throws AccessException {
|
||||||
|
try {
|
||||||
|
jwtTokenManager.validateToken(token);
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
throw new AccessException("token expired!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AccessException("token invalid!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication authentication = jwtTokenManager.getAuthentication(token);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
|
return authentication.getName();
|
||||||
|
}
|
||||||
|
}
|
82
auth/src/main/java/com/alibaba/nacos/auth/model/Page.java
Normal file
82
auth/src/main/java/com/alibaba/nacos/auth/model/Page.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page.
|
||||||
|
* copy from config module for resolving circular dependency.
|
||||||
|
*/
|
||||||
|
public class Page<E> implements Serializable {
|
||||||
|
|
||||||
|
static final long serialVersionUID = -1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* totalCount.
|
||||||
|
*/
|
||||||
|
private int totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pageNumber.
|
||||||
|
*/
|
||||||
|
private int pageNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pagesAvailable.
|
||||||
|
*/
|
||||||
|
private int pagesAvailable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pageItems.
|
||||||
|
*/
|
||||||
|
private List<E> pageItems = new ArrayList<E>();
|
||||||
|
|
||||||
|
public void setPageNumber(int pageNumber) {
|
||||||
|
this.pageNumber = pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPagesAvailable(int pagesAvailable) {
|
||||||
|
this.pagesAvailable = pagesAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageItems(List<E> pageItems) {
|
||||||
|
this.pageItems = pageItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCount() {
|
||||||
|
return totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalCount(int totalCount) {
|
||||||
|
this.totalCount = totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageNumber() {
|
||||||
|
return pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPagesAvailable() {
|
||||||
|
return pagesAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<E> getPageItems() {
|
||||||
|
return pageItems;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PermissionInfo model.
|
||||||
|
*
|
||||||
|
* @author nkorange
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
public class PermissionInfo implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 388813573388837395L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role name.
|
||||||
|
*/
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource.
|
||||||
|
*/
|
||||||
|
private String resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action on resource.
|
||||||
|
*/
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(String role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResource() {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResource(String resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(String action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.persist;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.model.Page;
|
||||||
|
import com.alibaba.nacos.auth.model.PermissionInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permission CRUD service.
|
||||||
|
*
|
||||||
|
* @author nkorange
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.AbstractMethodOrInterfaceMethodMustUseJavadocRule")
|
||||||
|
public interface PermissionPersistService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the permissions of role by page.
|
||||||
|
*
|
||||||
|
* @param role role
|
||||||
|
* @param pageNo pageNo
|
||||||
|
* @param pageSize pageSize
|
||||||
|
* @return permissions page info
|
||||||
|
*/
|
||||||
|
Page<PermissionInfo> getPermissions(String role, int pageNo, int pageSize);
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.persist;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.model.Page;
|
||||||
|
import com.alibaba.nacos.auth.roles.RoleInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Role CRUD service.
|
||||||
|
*
|
||||||
|
* @author nkorange
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.AbstractMethodOrInterfaceMethodMustUseJavadocRule")
|
||||||
|
public interface RolePersistService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* query the user's roles by username.
|
||||||
|
*
|
||||||
|
* @param username username
|
||||||
|
* @param pageNo pageNo
|
||||||
|
* @param pageSize pageSize
|
||||||
|
* @return roles page info
|
||||||
|
*/
|
||||||
|
Page<RoleInfo> getRolesByUserName(String username, int pageNo, int pageSize);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.alibaba.nacos.auth.persist;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.model.Page;
|
||||||
|
import com.alibaba.nacos.auth.users.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User CRUD service.
|
||||||
|
*
|
||||||
|
* @author nkorange
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.AbstractMethodOrInterfaceMethodMustUseJavadocRule")
|
||||||
|
public interface UserPersistService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create user.
|
||||||
|
*
|
||||||
|
* @param username username
|
||||||
|
* @param password password
|
||||||
|
*/
|
||||||
|
void createUser(String username, String password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* query user by username.
|
||||||
|
*
|
||||||
|
* @param username username
|
||||||
|
* @return user
|
||||||
|
*/
|
||||||
|
User findUserByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get users by page.
|
||||||
|
*
|
||||||
|
* @param pageNo pageNo
|
||||||
|
* @param pageSize pageSize
|
||||||
|
* @return user page info
|
||||||
|
*/
|
||||||
|
Page<User> getUsers(int pageNo, int pageSize);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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.roles;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.auth.NacosAuthConfig;
|
||||||
|
import com.alibaba.nacos.auth.common.AuthConfigs;
|
||||||
|
import com.alibaba.nacos.auth.model.Page;
|
||||||
|
import com.alibaba.nacos.auth.model.Permission;
|
||||||
|
import com.alibaba.nacos.auth.model.PermissionInfo;
|
||||||
|
import com.alibaba.nacos.auth.persist.PermissionPersistService;
|
||||||
|
import com.alibaba.nacos.auth.persist.RolePersistService;
|
||||||
|
import com.alibaba.nacos.common.utils.ConcurrentHashSet;
|
||||||
|
import com.alibaba.nacos.common.utils.StringUtils;
|
||||||
|
import io.jsonwebtoken.lang.Collections;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nacos builtin role service.
|
||||||
|
*
|
||||||
|
* @author nkorange
|
||||||
|
* @since 1.2.0
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class NacosAuthRoleServiceImpl {
|
||||||
|
|
||||||
|
public static final Logger LOGGER = LoggerFactory.getLogger(NacosAuthRoleServiceImpl.class);
|
||||||
|
|
||||||
|
public static final String GLOBAL_ADMIN_ROLE = "ROLE_ADMIN";
|
||||||
|
|
||||||
|
private static final int DEFAULT_PAGE_NO = 1;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthConfigs authConfigs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RolePersistService rolePersistService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PermissionPersistService permissionPersistService;
|
||||||
|
|
||||||
|
private volatile Set<String> roleSet = new ConcurrentHashSet<>();
|
||||||
|
|
||||||
|
private volatile Map<String, List<RoleInfo>> roleInfoMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private volatile Map<String, List<PermissionInfo>> permissionInfoMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
|
||||||
|
private void reload() {
|
||||||
|
try {
|
||||||
|
Page<RoleInfo> roleInfoPage = rolePersistService
|
||||||
|
.getRolesByUserName(StringUtils.EMPTY, DEFAULT_PAGE_NO, Integer.MAX_VALUE);
|
||||||
|
if (roleInfoPage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> tmpRoleSet = new HashSet<>(16);
|
||||||
|
Map<String, List<RoleInfo>> tmpRoleInfoMap = new ConcurrentHashMap<>(16);
|
||||||
|
for (RoleInfo roleInfo : roleInfoPage.getPageItems()) {
|
||||||
|
if (!tmpRoleInfoMap.containsKey(roleInfo.getUsername())) {
|
||||||
|
tmpRoleInfoMap.put(roleInfo.getUsername(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
tmpRoleInfoMap.get(roleInfo.getUsername()).add(roleInfo);
|
||||||
|
tmpRoleSet.add(roleInfo.getRole());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<PermissionInfo>> tmpPermissionInfoMap = new ConcurrentHashMap<>(16);
|
||||||
|
for (String role : tmpRoleSet) {
|
||||||
|
Page<PermissionInfo> 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.
|
||||||
|
*
|
||||||
|
* <p>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<RoleInfo> 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<PermissionInfo> 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<RoleInfo> getRoles(String username) {
|
||||||
|
List<RoleInfo> roleInfoList = roleInfoMap.get(username);
|
||||||
|
if (!authConfigs.isCachingEnabled() || roleInfoList == null) {
|
||||||
|
Page<RoleInfo> roleInfoPage = getRolesFromDatabase(username, DEFAULT_PAGE_NO, Integer.MAX_VALUE);
|
||||||
|
if (roleInfoPage != null) {
|
||||||
|
roleInfoList = roleInfoPage.getPageItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roleInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<RoleInfo> getRolesFromDatabase(String userName, int pageNo, int pageSize) {
|
||||||
|
Page<RoleInfo> roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize);
|
||||||
|
if (roles == null) {
|
||||||
|
return new Page<>();
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PermissionInfo> getPermissions(String role) {
|
||||||
|
List<PermissionInfo> permissionInfoList = permissionInfoMap.get(role);
|
||||||
|
if (!authConfigs.isCachingEnabled() || permissionInfoList == null) {
|
||||||
|
Page<PermissionInfo> permissionInfoPage = getPermissionsFromDatabase(role, DEFAULT_PAGE_NO,
|
||||||
|
Integer.MAX_VALUE);
|
||||||
|
if (permissionInfoPage != null) {
|
||||||
|
permissionInfoList = permissionInfoPage.getPageItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return permissionInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<PermissionInfo> getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
|
||||||
|
Page<PermissionInfo> pageInfo = permissionPersistService.getPermissions(role, pageNo, pageSize);
|
||||||
|
if (pageInfo == null) {
|
||||||
|
return new Page<>();
|
||||||
|
}
|
||||||
|
return pageInfo;
|
||||||
|
}
|
||||||
|
}
|
@ -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 + '\'' + '}';
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, User> userMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserPersistService userPersistService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthConfigs authConfigs;
|
||||||
|
|
||||||
|
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
|
||||||
|
private void reload() {
|
||||||
|
try {
|
||||||
|
Page<User> users = getUsersFromDatabase(1, Integer.MAX_VALUE);
|
||||||
|
if (users == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, User> 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<User> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
49
auth/src/main/java/com/alibaba/nacos/auth/users/User.java
Normal file
49
auth/src/main/java/com/alibaba/nacos/auth/users/User.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package com.alibaba.nacos.auth.common;
|
|||||||
import com.alibaba.nacos.auth.AuthPluginManager;
|
import com.alibaba.nacos.auth.AuthPluginManager;
|
||||||
import com.alibaba.nacos.auth.AuthService;
|
import com.alibaba.nacos.auth.AuthService;
|
||||||
import com.alibaba.nacos.auth.context.IdentityContext;
|
import com.alibaba.nacos.auth.context.IdentityContext;
|
||||||
|
import com.alibaba.nacos.auth.exception.AccessException;
|
||||||
import com.alibaba.nacos.auth.model.Permission;
|
import com.alibaba.nacos.auth.model.Permission;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -72,7 +73,7 @@ public class AuthPluginManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFindAuthServiceSpiImpl() {
|
public void testFindAuthServiceSpiImpl() throws AccessException {
|
||||||
Mockito.when(authService.authorityAccess(identityContext, permission)).thenReturn(true);
|
Mockito.when(authService.authorityAccess(identityContext, permission)).thenReturn(true);
|
||||||
Mockito.when(authService.getAuthServiceName()).thenReturn(TYPE);
|
Mockito.when(authService.getAuthServiceName()).thenReturn(TYPE);
|
||||||
Optional<AuthService> authServiceImpl = authPluginManager.findAuthServiceSpiImpl(TYPE);
|
Optional<AuthService> authServiceImpl = authPluginManager.findAuthServiceSpiImpl(TYPE);
|
||||||
|
Loading…
Reference in New Issue
Block a user