diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/remoteapi/AuthCenterRemoteApi.java b/api-portal/src/main/java/com/jmsoftware/apiportal/remoteapi/AuthCenterRemoteApi.java
index 42f7d162..45924a88 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/remoteapi/AuthCenterRemoteApi.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/remoteapi/AuthCenterRemoteApi.java
@@ -1,6 +1,9 @@
package com.jmsoftware.apiportal.remoteapi;
+import com.jmsoftware.apiportal.universal.aspect.ValidateArgument;
import com.jmsoftware.common.bean.ResponseBodyBean;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListPayload;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListResponse;
import com.jmsoftware.common.domain.authcenter.role.GetRoleListByUserIdPayload;
import com.jmsoftware.common.domain.authcenter.role.GetRoleListByUserIdResponse;
import com.jmsoftware.common.domain.authcenter.user.GetUserByLoginTokenPayload;
@@ -11,6 +14,8 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import javax.validation.Valid;
+
/**
*
AuthCenterRemoteApi
*
@@ -27,8 +32,9 @@ public interface AuthCenterRemoteApi {
* @param payload the payload
* @return the user by login token
*/
+ @ValidateArgument
@PostMapping("/user-remote-api/get-user-by-login-token")
- ResponseBodyBean getUserByLoginToken(@RequestBody GetUserByLoginTokenPayload payload);
+ ResponseBodyBean getUserByLoginToken(@Valid @RequestBody GetUserByLoginTokenPayload payload);
/**
* Gets role list by user id.
@@ -36,8 +42,9 @@ public interface AuthCenterRemoteApi {
* @param payload the payload
* @return the role list by user id
*/
+ @ValidateArgument
@PostMapping("/role-remote-api/get-role-list-by-user-id")
- ResponseBodyBean getRoleListByUserId(@RequestBody GetRoleListByUserIdPayload payload);
+ ResponseBodyBean getRoleListByUserId(@Valid @RequestBody GetRoleListByUserIdPayload payload);
/**
* Save user for registering response body bean.
@@ -45,6 +52,17 @@ public interface AuthCenterRemoteApi {
* @param payload the payload
* @return the response body bean
*/
+ @ValidateArgument
@PostMapping("/user-remote-api/save-user-for-registering")
- ResponseBodyBean saveUserForRegistering(@RequestBody SaveUserForRegisteringPayload payload);
+ ResponseBodyBean saveUserForRegistering(@Valid @RequestBody SaveUserForRegisteringPayload payload);
+
+ /**
+ * Gets permission list by role id list.
+ *
+ * @param payload the payload
+ * @return the permission list by role id list
+ */
+ @ValidateArgument
+ @PostMapping("/permission-remote-api/get-permission-list-by-role-id-list")
+ ResponseBodyBean getPermissionListByRoleIdList(@Valid @RequestBody GetPermissionListByRoleIdListPayload payload);
}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/SecurityHandlerConfiguration.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/SecurityHandlerConfiguration.java
index e864f741..55843db7 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/SecurityHandlerConfiguration.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/SecurityHandlerConfiguration.java
@@ -2,8 +2,10 @@
import com.jmsoftware.common.constant.HttpStatus;
import com.jmsoftware.common.util.ResponseUtil;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
/**
@@ -14,12 +16,24 @@
* @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
* @date 5/2/20 11:41 PM
**/
+@Slf4j
@Configuration
public class SecurityHandlerConfiguration {
+ @Bean
+ public AuthenticationEntryPoint authenticationEntryPoint() {
+ return ((request, response, authException) -> {
+ log.error("Authentication encountered an exception! Exception message: {}", authException.getMessage(),
+ authException);
+ ResponseUtil.renderJson(response, HttpStatus.FORBIDDEN, null);
+ });
+ }
+
@Bean
public AccessDeniedHandler accessDeniedHandler() {
- return (request, response, accessDeniedException) -> ResponseUtil.renderJson(response,
- HttpStatus.FORBIDDEN,
- null);
+ return ((request, response, accessDeniedException) -> {
+ log.error("Access was denied! Exception message: {}", accessDeniedException.getMessage(),
+ accessDeniedException);
+ ResponseUtil.renderJson(response, HttpStatus.FORBIDDEN, null);
+ });
}
}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/WebSecurityConfiguration.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/WebSecurityConfiguration.java
index 0fc8304c..89eb232c 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/WebSecurityConfiguration.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/configuration/WebSecurityConfiguration.java
@@ -16,6 +16,7 @@
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -36,6 +37,7 @@
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomConfiguration customConfiguration;
private final AccessDeniedHandler accessDeniedHandler;
+ private final AuthenticationEntryPoint authenticationEntryPoint;
private final CustomUserDetailsServiceImpl customUserDetailsServiceImpl;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@@ -83,7 +85,10 @@ protected void configure(HttpSecurity http) throws Exception {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// Exception handling
- .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler);
+ .and()
+ .exceptionHandling()
+ .accessDeniedHandler(accessDeniedHandler)
+ .authenticationEntryPoint(authenticationEntryPoint);
// Add customized JWT filter
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/domain/UserPrincipal.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/domain/UserPrincipal.java
index e861e9a0..24988673 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/domain/UserPrincipal.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/domain/UserPrincipal.java
@@ -2,6 +2,7 @@
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListResponse;
import com.jmsoftware.common.domain.authcenter.user.GetUserByLoginTokenResponse;
import com.jmsoftware.common.domain.authcenter.user.UserStatus;
import lombok.AllArgsConstructor;
@@ -94,7 +95,7 @@ public class UserPrincipal implements UserDetails {
* @return user principal
*/
public static UserPrincipal create(GetUserByLoginTokenResponse user, List roleNameList,
- List permissionList) {
+ List permissionList) {
val authorities =
permissionList.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getPermissionExpression()))
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/filter/JwtAuthenticationFilter.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/filter/JwtAuthenticationFilter.java
index c142856d..89adf1fa 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/filter/JwtAuthenticationFilter.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/filter/JwtAuthenticationFilter.java
@@ -81,9 +81,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
UserDetails userDetails;
try {
userDetails = customUserDetailsServiceImpl.loadUserByUsername(username);
- } catch (UsernameNotFoundException e) {
- log.error("Cannot find user by username: {}", username);
- ResponseUtil.renderJson(response, HttpStatus.UNAUTHORIZED, null);
+ } catch (Exception e) {
+ log.error("Exception occurred when loading user by username! Exception message: {} Username: {}",
+ e.getMessage(), username);
+ if (e instanceof UsernameNotFoundException) {
+ ResponseUtil.renderJson(response, HttpStatus.UNAUTHORIZED, null);
+ return;
+ }
+ ResponseUtil.renderJson(response, HttpStatus.ERROR, e.getMessage());
return;
}
val authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/CustomUserDetailsServiceImpl.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/CustomUserDetailsServiceImpl.java
index 7db8d817..ed843e61 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/CustomUserDetailsServiceImpl.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/CustomUserDetailsServiceImpl.java
@@ -3,10 +3,9 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jmsoftware.apiportal.remoteapi.AuthCenterRemoteApi;
-import com.jmsoftware.apiportal.universal.domain.PermissionPO;
import com.jmsoftware.apiportal.universal.domain.UserPrincipal;
-import com.jmsoftware.apiportal.universal.mapper.PermissionMapper;
import com.jmsoftware.common.constant.HttpStatus;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListPayload;
import com.jmsoftware.common.domain.authcenter.role.GetRoleListByUserIdPayload;
import com.jmsoftware.common.domain.authcenter.role.GetRoleListByUserIdResponse;
import com.jmsoftware.common.domain.authcenter.user.GetUserByLoginTokenPayload;
@@ -19,7 +18,6 @@
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
-import java.util.List;
import java.util.stream.Collectors;
/**
@@ -33,7 +31,6 @@
@Service
@RequiredArgsConstructor
public class CustomUserDetailsServiceImpl implements UserDetailsService {
- private final PermissionMapper permissionMapper;
private final AuthCenterRemoteApi authCenterRemoteApi;
@Override
@@ -54,12 +51,14 @@ public UserDetails loadUserByUsername(String credentials) throws UsernameNotFoun
if (CollUtil.isEmpty(roleList)) {
throw new SecurityException(HttpStatus.ROLE_NOT_FOUND);
}
- List roleIdList = roleList.stream()
- .map(GetRoleListByUserIdResponse.Role::getId)
- .collect(Collectors.toList());
- List permissionList = permissionMapper.selectByRoleIdList(roleIdList);
+ val payload2 = new GetPermissionListByRoleIdListPayload();
+ roleList.forEach(role -> {
+ payload2.getRoleIdList().add(role.getId());
+ });
+ val permissionListByRoleIdListResponse = authCenterRemoteApi.getPermissionListByRoleIdList(payload2);
val roleNameList =
roleList.stream().map(GetRoleListByUserIdResponse.Role::getName).collect(Collectors.toList());
- return UserPrincipal.create(data, roleNameList, permissionList);
+ return UserPrincipal.create(data, roleNameList,
+ permissionListByRoleIdListResponse.getData().getPermissionList());
}
}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/RbacAuthorityServiceImpl.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/RbacAuthorityServiceImpl.java
index 88a04533..8020888d 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/RbacAuthorityServiceImpl.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/service/impl/RbacAuthorityServiceImpl.java
@@ -1,33 +1,33 @@
package com.jmsoftware.apiportal.universal.service.impl;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
+import com.jmsoftware.apiportal.remoteapi.AuthCenterRemoteApi;
import com.jmsoftware.apiportal.universal.configuration.CustomConfiguration;
-import com.jmsoftware.apiportal.universal.domain.PermissionPO;
import com.jmsoftware.apiportal.universal.domain.PermissionType;
import com.jmsoftware.apiportal.universal.domain.RolePO;
import com.jmsoftware.apiportal.universal.domain.UserPrincipal;
-import com.jmsoftware.apiportal.universal.service.PermissionService;
import com.jmsoftware.apiportal.universal.service.RbacAuthorityService;
import com.jmsoftware.apiportal.universal.service.RoleService;
import com.jmsoftware.common.constant.HttpStatus;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListPayload;
+import com.jmsoftware.common.domain.authcenter.permission.GetPermissionListByRoleIdListResponse;
import com.jmsoftware.common.exception.SecurityException;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
-import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -37,62 +37,62 @@
* @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
* @date 2019-03-23 14:25
**/
+@Slf4j
@Service
@RequiredArgsConstructor
public class RbacAuthorityServiceImpl implements RbacAuthorityService {
+ private final AuthCenterRemoteApi authCenterRemoteApi;
+
private final RoleService roleService;
- private final PermissionService permissionService;
private final RequestMappingHandlerMapping mapping;
private final CustomConfiguration customConfiguration;
private final JwtServiceImpl jwtServiceImpl;
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
- String username = jwtServiceImpl.getUsernameFromRequest(request);
+ val username = jwtServiceImpl.getUsernameFromRequest(request);
// Super user has no restriction on any requests.
if (customConfiguration.getSuperUser().equals(username)) {
return true;
}
-
this.checkRequest(request);
-
- Object userInfo = authentication.getPrincipal();
+ val principal = authentication.getPrincipal();
+ if (!(principal instanceof UserDetails)) {
+ log.error("Invalid user principal. {}", principal);
+ return false;
+ }
boolean hasPermission = false;
-
- if (userInfo instanceof UserDetails) {
- UserPrincipal principal = (UserPrincipal) userInfo;
- Long userId = principal.getId();
-
- List rolesByUserId = roleService.getRolesByUserId(userId);
- List roleIds = rolesByUserId.stream()
- .map(RolePO::getId)
- .collect(Collectors.toList());
- List permissionsByRoleId = permissionService.selectByRoleIdList(roleIds);
-
- // Filter button permission for frond-end
- List btnPerms =
- permissionsByRoleId.stream()
- // Sieve out page permissions
- .filter(permission -> Objects.equals(permission.getType(),
- PermissionType.BUTTON.getType()))
- // Sieve out permission that has no URL
- .filter(permission -> StrUtil.isNotBlank(permission.getUrl()))
- // Sieve out permission that has no method
- .filter(permission -> StrUtil.isNotBlank(permission.getMethod()))
- .collect(Collectors.toList());
-
- for (PermissionPO btnPerm : btnPerms) {
- AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod());
- if (antPathMatcher.matches(request)) {
- hasPermission = true;
- break;
- }
+ UserPrincipal userPrincipal = (UserPrincipal) principal;
+ Long userId = userPrincipal.getId();
+ // TODO: auth-center roleService.getRolesByUserId(userId)
+ List rolesByUserId = roleService.getRolesByUserId(userId);
+ val payload = new GetPermissionListByRoleIdListPayload();
+ rolesByUserId.forEach(rolePO -> {
+ payload.getRoleIdList().add(rolePO.getId());
+ });
+ val permissionListByRoleIdListResponse = authCenterRemoteApi.getPermissionListByRoleIdList(payload);
+ val permissionList = permissionListByRoleIdListResponse.getData().getPermissionList();
+ // Filter button permission for frond-end
+ List buttonPermissionList =
+ permissionList.stream()
+ // Sieve out page permissions
+ .filter(permission -> ObjectUtil.equal(permission.getType(),
+ PermissionType.BUTTON.getType()))
+ // Sieve out permission that has no URL
+ .filter(permission -> StrUtil.isNotBlank(permission.getUrl()))
+ // Sieve out permission that has no method
+ .filter(permission -> StrUtil.isNotBlank(permission.getMethod()))
+ .collect(Collectors.toList());
+ for (GetPermissionListByRoleIdListResponse.Permission btnPerm : buttonPermissionList) {
+ // TODO: check is AntPathRequestMatcher supports RESTFul request
+ AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod());
+ if (antPathMatcher.matches(request)) {
+ hasPermission = true;
+ break;
}
-
- return hasPermission;
- } else {
- return false;
}
+ return hasPermission;
+
}
/**
@@ -101,9 +101,8 @@ public boolean hasPermission(HttpServletRequest request, Authentication authenti
* @param request HTTP Request
*/
private void checkRequest(HttpServletRequest request) {
- String currentMethod = request.getMethod();
- Multimap urlMapping = allUrlMapping();
-
+ val currentMethod = request.getMethod();
+ val urlMapping = allUrlMapping();
for (String uri : urlMapping.keySet()) {
// 通过 AntPathRequestMatcher 匹配 url
// 可以通过 2 种方式创建 AntPathRequestMatcher
@@ -112,15 +111,13 @@ private void checkRequest(HttpServletRequest request) {
// 2:new AntPathRequestMatcher(uri) 这种方式不校验请求方法,只校验请求路径
AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri);
if (antPathMatcher.matches(request)) {
- if (!urlMapping.get(uri)
- .contains(currentMethod)) {
+ if (!urlMapping.get(uri).contains(currentMethod)) {
throw new SecurityException(HttpStatus.METHOD_NOT_ALLOWED);
} else {
return;
}
}
}
-
throw new SecurityException(HttpStatus.NOT_FOUND);
}
@@ -130,24 +127,19 @@ private void checkRequest(HttpServletRequest request) {
* @return {@link ArrayListMultimap} 格式的 URL Mapping
*/
private Multimap allUrlMapping() {
- Multimap urlMapping = ArrayListMultimap.create();
-
+ Multimap urlMapping = LinkedListMultimap.create();
// 获取url与类和方法的对应信息
- Map handlerMethods = mapping.getHandlerMethods();
-
+ val handlerMethods = mapping.getHandlerMethods();
handlerMethods.forEach((key, value) -> {
// 获取当前 key 下的获取所有URL
- Set url = key.getPatternsCondition()
- .getPatterns();
+ val url = key.getPatternsCondition().getPatterns();
RequestMethodsRequestCondition method = key.getMethodsCondition();
-
// 为每个URL添加所有的请求方法
url.forEach(item -> urlMapping.putAll(item, method.getMethods()
.stream()
.map(Enum::toString)
.collect(Collectors.toList())));
});
-
return urlMapping;
}
}
diff --git a/common/src/main/java/com/jmsoftware/common/util/ResponseUtil.java b/common/src/main/java/com/jmsoftware/common/util/ResponseUtil.java
index 123769f3..a4fce5d8 100644
--- a/common/src/main/java/com/jmsoftware/common/util/ResponseUtil.java
+++ b/common/src/main/java/com/jmsoftware/common/util/ResponseUtil.java
@@ -5,6 +5,7 @@
import com.jmsoftware.common.constant.HttpStatus;
import com.jmsoftware.common.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
+import lombok.val;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -30,14 +31,21 @@ public class ResponseUtil {
* @param data Data
*/
public static void renderJson(final HttpServletResponse response, final HttpStatus httpStatus, final Object data) {
+ standardizeHttpServletResponse(response, httpStatus);
+ val responseBodyBean = ResponseBodyBean.ofStatus(httpStatus.getCode(),
+ httpStatus.getMessage(), data);
+ try {
+ response.getWriter().write(MAPPER.writeValueAsString(responseBodyBean));
+ } catch (IOException e) {
+ log.error("Error occurred when responding a data JSON.", e);
+ }
+ }
+
+ public static void renderJson(final HttpServletResponse response, final HttpStatus httpStatus,
+ final String message) {
+ standardizeHttpServletResponse(response, httpStatus);
+ val responseBodyBean = ResponseBodyBean.ofStatus(httpStatus.getCode(), message, null);
try {
- ResponseBodyBean