Skip to content

Commit

Permalink
perf($Gateway): refine RBAC process
Browse files Browse the repository at this point in the history
  • Loading branch information
Johnny Miller (锺俊) committed Dec 28, 2020
1 parent 8bfa94f commit fed3ea7
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.jmsoftware.maf.apigateway.remoteapi;

import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import com.jmsoftware.maf.common.domain.authcenter.permission.GetPermissionListByRoleIdListResponse;
import com.jmsoftware.maf.common.domain.authcenter.permission.GetPermissionListByUserIdResponse;
import com.jmsoftware.maf.common.domain.authcenter.role.GetRoleListByUserIdResponse;
import com.jmsoftware.maf.common.domain.authcenter.user.GetUserByLoginTokenResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import reactivefeign.spring.config.ReactiveFeignClient;
import reactor.core.publisher.Mono;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

/**
* <h1>AuthCenterRemoteApi</h1>
* <p>
Expand Down Expand Up @@ -47,4 +56,14 @@ public interface AuthCenterRemoteApi {
*/
@GetMapping("/permission-remote-api/permissions/{userId}")
Mono<ResponseBodyBean<GetPermissionListByUserIdResponse>> getPermissionListByUserId(@PathVariable Long userId);

/**
* Get permission list by role id list
*
* @param roleIdList the role id list
* @return the response body bean
*/
@RequestMapping(value = "/permission-remote-api/permissions", method = GET)
Mono<ResponseBodyBean<GetPermissionListByRoleIdListResponse>> getPermissionListByRoleIdList(
@Valid @RequestParam("roleIdList") List<@NotNull Long> roleIdList);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Mono<Authentication> authenticate(Authentication authentication) {
val responseMono = response.map(ResponseBodyBean::getData)
.switchIfEmpty(Mono.error(new BusinessException("Authentication failure! Cause: User not found")));
return responseMono.map(getUserByLoginTokenResponse -> {
log.info("Authentication success! Found username: {}", getUserByLoginTokenResponse.getUsername());
log.info("Authentication success! Found {}", getUserByLoginTokenResponse);
UserPrincipal userPrincipal = UserPrincipal.create(getUserByLoginTokenResponse, null, null);
return new UsernamePasswordAuthenticationToken(userPrincipal, null);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import cn.hutool.core.util.StrUtil;
import com.jmsoftware.maf.apigateway.remoteapi.AuthCenterRemoteApi;
import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import com.jmsoftware.maf.common.domain.authcenter.permission.GetPermissionListByRoleIdListPayload;
import com.jmsoftware.maf.common.domain.authcenter.permission.GetPermissionListByRoleIdListResponse;
import com.jmsoftware.maf.common.domain.authcenter.permission.PermissionType;
import com.jmsoftware.maf.common.domain.authcenter.role.GetRoleListByUserIdResponse;
import com.jmsoftware.maf.common.exception.SecurityException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -16,9 +19,11 @@
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
Expand All @@ -39,17 +44,34 @@ public class RbacReactiveAuthorizationManager implements ReactiveAuthorizationMa
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
val request = object.getExchange().getRequest();
val userPrincipalMono = authentication.map(auth -> (UserPrincipal) auth.getPrincipal());
val getPermissionListByUserIdResponseMono =
userPrincipalMono.flatMap(userPrincipal -> {
log.info("Checking authorization for user: {}, request URL: [{}] {}", userPrincipal.getUsername(),
request.getMethod(), request.getURI());
return authCenterRemoteApi
.getPermissionListByUserId(userPrincipal.getId())
.map(ResponseBodyBean::getData)
.switchIfEmpty(
Mono.error(new SecurityException(HttpStatus.UNAUTHORIZED, "Permission not found!")));

// Get role list by user ID, and then convert to Flux<?>
val getRoleListByUserIdResponseFlux = userPrincipalMono
.flatMap(userPrincipal -> authCenterRemoteApi.getRoleListByUserId(userPrincipal.getId())
.map(ResponseBodyBean::getData))
.map(GetRoleListByUserIdResponse::getRoleList)
.flatMapMany(Flux::fromIterable)
.switchIfEmpty(Flux.error(new SecurityException(HttpStatus.UNAUTHORIZED, "Roles not assigned!")));

// Filter "admin" role and then, map Flux<?> to Mono<List<Long>>
Mono<List<Long>> roleIdListMono = getRoleListByUserIdResponseFlux
.filter(role -> StrUtil.equals("admin", role.getName()))
.map(GetRoleListByUserIdResponse.Role::getId).collectList()
.switchIfEmpty(getRoleListByUserIdResponseFlux
.map(GetRoleListByUserIdResponse.Role::getId)
.collectList());

// Get permission list based on the Mono<List<Long>>
// TODO: auth-center should respond /** for role "admin"
Mono<GetPermissionListByRoleIdListResponse> getPermissionListByRoleIdListResponseMono = roleIdListMono.flatMap(
roleIdList -> {
GetPermissionListByRoleIdListPayload payload = new GetPermissionListByRoleIdListPayload();
payload.setRoleIdList(roleIdList);
return authCenterRemoteApi.getPermissionListByRoleIdList(payload.getRoleIdList()).map(ResponseBodyBean::getData);
});
val zip = Mono.zip(getPermissionListByUserIdResponseMono, userPrincipalMono);

// Aggregate 2 Mono
val zip = Mono.zip(getPermissionListByRoleIdListResponseMono, userPrincipalMono);
return zip.map(mapper -> {
val permissionList = mapper.getT1().getPermissionList();
val buttonPermissionList = permissionList.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.jmsoftware.maf.authcenter.universal.configuration;
package com.jmsoftware.maf.apigateway.universal.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public ResponseBodyBean<GetPermissionListByRoleIdListResponse> getPermissionList
@GetMapping("/permissions/{userId}")
@ApiOperation(value = "Get permission list by user id", notes = "Get permission list by user id")
public ResponseBodyBean<GetPermissionListByUserIdResponse> getPermissionListByUserId(@PathVariable Long userId) {
// TODO: auth-center should respond /** for role "admin"
return ResponseBodyBean.ofSuccess(permissionService.getPermissionListByUserId(userId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@
@Component
@ConfigurationProperties(prefix = "jwt.configuration")
public class JwtConfiguration {
public static final String TOKEN_PREFIX = "Bearer ";
/**
* Key prefix of JWT stored in Redis.
*/
private String jwtRedisKeyPrefix;

public JwtConfiguration(ProjectProperty projectProperty) {
this.signingKey = String.format("%s %s", projectProperty.getProjectParentArtifactId(), projectProperty.getVersion());
log.info("Initiated JWT signing key: {}. The specified key byte array is {} bits", this.signingKey,
this.signingKey.getBytes(StandardCharsets.UTF_8).length * 8);
jwtRedisKeyPrefix = String.format("%s:jwt:", projectProperty.getProjectParentArtifactId());
log.warn("Initiated 'jwtRedisKeyPrefix': {}", jwtRedisKeyPrefix);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.jmsoftware.maf.authcenter.universal.configuration.Constants;
import com.jmsoftware.maf.authcenter.universal.configuration.JwtConfiguration;
import com.jmsoftware.maf.authcenter.universal.domain.UserPrincipal;
import com.jmsoftware.maf.authcenter.universal.service.JwtService;
Expand Down Expand Up @@ -80,7 +79,7 @@ public String createJwt(Boolean rememberMe, Long id, String subject, List<String
}
val jwt = builder.compact();
// Store new JWT in Redis
val redisOperationResult = redisService.set(Constants.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl,
val redisOperationResult = redisService.set(jwtConfiguration.getJwtRedisKeyPrefix() + subject, jwt, ttl,
TimeUnit.MILLISECONDS);
if (redisOperationResult) {
return jwt;
Expand Down Expand Up @@ -110,7 +109,7 @@ public Claims parseJwt(String jwt) throws SecurityException {
throw new SecurityException(HttpStatus.UNAUTHORIZED, "The parameter of JWT is invalid");
}
val username = claims.getSubject();
val redisKeyOfJwt = Constants.REDIS_JWT_KEY_PREFIX + username;
val redisKeyOfJwt = jwtConfiguration.getJwtRedisKeyPrefix() + username;
// Check if JWT exists
val expire = redisService.getExpire(redisKeyOfJwt, TimeUnit.MILLISECONDS);
if (ObjectUtil.isNull(expire) || expire <= 0) {
Expand All @@ -131,7 +130,7 @@ public void invalidateJwt(HttpServletRequest request) throws SecurityException {
val jwt = getJwtFromRequest(request);
val username = getUsernameFromJwt(jwt);
// Delete JWT from redis
val deletedKeyNumber = redisService.delete(Constants.REDIS_JWT_KEY_PREFIX + username);
val deletedKeyNumber = redisService.delete(jwtConfiguration.getJwtRedisKeyPrefix() + username);
log.error("Invalidate JWT. Username = {}, deleted = {}", username, deletedKeyNumber);
}

Expand All @@ -149,9 +148,9 @@ public String getUsernameFromRequest(HttpServletRequest request) throws Security

@Override
public String getJwtFromRequest(HttpServletRequest request) {
val bearerToken = request.getHeader(Constants.REQUEST_TOKEN_KEY);
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(Constants.JWT_PREFIX)) {
return bearerToken.substring(Constants.JWT_PREFIX.length());
val bearerToken = request.getHeader(JwtConfiguration.TOKEN_PREFIX);
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(JwtConfiguration.TOKEN_PREFIX)) {
return bearerToken.substring(JwtConfiguration.TOKEN_PREFIX.length());
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.Data;

import javax.validation.constraints.NotEmpty;
import java.util.LinkedList;
import java.util.List;

/**
Expand All @@ -20,5 +19,5 @@ public class GetPermissionListByRoleIdListPayload {
* The Role id list.
*/
@NotEmpty
private final List<Long> roleIdList = new LinkedList<>();
private List<Long> roleIdList;
}

0 comments on commit fed3ea7

Please sign in to comment.