Skip to content

Commit

Permalink
feat($Gateway): implement non-block authorization
Browse files Browse the repository at this point in the history
implement non-block authorization

BREAKING CHANGE: implement non-block authorization
  • Loading branch information
Johnny Miller (锺俊) committed Dec 22, 2020
1 parent 8fe454b commit b94944d
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,21 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
val requesterIpAndPort = RequestUtil.getRequestIpAndPort(request);
val method = request.getMethod();
val requestUrl = request.getRequestURL();
log.info("JWT authentication is filtering requester({}) access. Resource: [{}] {}",
log.info("JWT authentication is filtering requester({}) access. Request URL: [{}] {}",
requesterIpAndPort, method, requestUrl);
if (customConfiguration.getWebSecurityDisabled()) {
log.warn("The web security is disabled! Might face severe web security issue.");
filterChain.doFilter(request, response);
return;
}
if (checkIgnores(request)) {
log.info("The resource can be ignored. Resource: [{}] {}", method, requestUrl);
log.info("The resource can be ignored. Request URL: [{}] {}", method, requestUrl);
filterChain.doFilter(request, response);
return;
}
val passedJwtAuthentication = doJwtAuthentication(request, response);
if (!passedJwtAuthentication) {
log.warn("The requester did not pass JWT authentication. Requester: {}. Resource: [{}] {}", requesterIpAndPort,
log.warn("The requester did not pass JWT authentication. Requester: {}. Request URL: [{}] {}", requesterIpAndPort,
method, requestUrl);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
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.RestController;

import javax.validation.Valid;

Expand All @@ -33,7 +36,7 @@ public ResponseBodyBean<GetPermissionListByRoleIdListResponse> getPermissionList
return ResponseBodyBean.ofSuccess(permissionService.getPermissionListByRoleIdList(payload));
}

@PostMapping("/permissions/{userId}")
@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) {
return ResponseBodyBean.ofSuccess(permissionService.getPermissionListByUserId(userId));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.jmsoftware.maf.common.bean;

import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.jmsoftware.maf.common.constant.HttpStatus;
import com.jmsoftware.maf.common.constant.IUniversalStatus;
Expand All @@ -8,6 +11,8 @@
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.val;
import org.springframework.lang.Nullable;

import java.io.Serializable;
import java.util.Date;
Expand Down Expand Up @@ -266,4 +271,20 @@ public static <ResponseBodyDataType, BaseThrowable extends BaseException> Respon
throws BaseException {
throw throwable;
}

/**
* Of json.
*
* @param message the message
* @param data the data
* @param status the status
* @return the json
* @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 12/22/2020 10:16 AM
*/
public static JSON of(@NonNull String message, @Nullable Object data, @NonNull Integer status) {
val responseBodyBean = ResponseBodyBean.ofStatus(status, message, data);
val config = new JSONConfig();
config.setIgnoreNullValue(false).setDateFormat("yyyy-MM-dd HH:mm:ss");
return JSONUtil.parse(responseBodyBean, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthenticationManager implements ReactiveAuthenticationManager {
public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private final JwtService jwtService;
@Lazy
@Resource
Expand All @@ -49,7 +49,7 @@ public Mono<Authentication> authenticate(Authentication authentication) {
Mono<GetUserByLoginTokenResponse> responseMono = response.map(ResponseBodyBean::getData)
.switchIfEmpty(Mono.error(new BusinessException("Authentication failed! Cause: User not found")));
return responseMono.map(getUserByLoginTokenResponse -> {
log.info("Authentication success. Username: {}", getUserByLoginTokenResponse.getUsername());
log.info("Authentication success! Found username: {}", getUserByLoginTokenResponse.getUsername());
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 @@ -2,7 +2,6 @@

import cn.hutool.core.util.StrUtil;
import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import com.jmsoftware.maf.common.domain.authcenter.permission.GetPermissionListByUserIdResponse;
import com.jmsoftware.maf.common.domain.authcenter.permission.PermissionType;
import com.jmsoftware.maf.common.exception.BusinessException;
import com.jmsoftware.maf.gateway.remoteapi.AuthCenterRemoteApi;
Expand All @@ -19,7 +18,6 @@
import reactor.core.publisher.Mono;

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

/**
Expand All @@ -39,31 +37,35 @@ public class RbacReactiveAuthorizationManager implements ReactiveAuthorizationMa
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
val request = object.getExchange().getRequest();
Mono<Mono<GetPermissionListByUserIdResponse>> map = authentication.map(auth -> {
val userPrincipal = (UserPrincipal) auth.getPrincipal();
log.info("Checking authorization for user: {}, resource: [{}] {}", userPrincipal.getUsername(),
request.getMethod(), request.getURI());
val responseBodyBeanMono =
authCenterRemoteApi.getPermissionListByUserId(userPrincipal.getId());
return responseBodyBeanMono.map(ResponseBodyBean::getData)
.switchIfEmpty(Mono.error(new BusinessException("Permission mustn't be null!")));
});
return map.map(response -> {
val permissionList = Objects.requireNonNull(response.block()).getPermissionList();
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 BusinessException("User not found!")));
});
val zip = Mono.zip(getPermissionListByUserIdResponseMono, userPrincipalMono);
return zip.map(objects -> {
val permissionList = objects.getT1().getPermissionList();
val buttonPermissionList = permissionList.stream()
.filter(permission -> PermissionType.BUTTON.getType().equals(permission.getType()))
.filter(permission -> StrUtil.isNotBlank(permission.getUrl()))
.filter(permission -> StrUtil.isNotBlank(permission.getMethod()))
.collect(Collectors.toList());
String path = request.getURI().getPath();
val path = request.getURI().getPath();
val userPrincipal = objects.getT2();
for (val buttonPermission : buttonPermissionList) {
if (antPathMatcher.match(buttonPermission.getUrl(), path)) {
val userPrincipal = (UserPrincipal) Objects.requireNonNull(authentication.block()).getPrincipal();
log.info("Resource [{}] {} is accessible for user(username: {})", request.getMethod(),
request.getURI(), userPrincipal.getUsername());
return new AuthorizationDecision(true);
}
}
log.warn("Resource [{}] {} is inaccessible for user(username: {})", request.getMethod(),
request.getURI(), userPrincipal.getUsername());
return new AuthorizationDecision(false);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
import reactor.core.publisher.Mono;

/**
* Description: SecurityContextRepository, change description here.
* Description: ReactiveServerSecurityContextRepository, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 12/18/2020 3:38 PM
**/
@Slf4j
@Component
@RequiredArgsConstructor
public class CustomServerSecurityContextRepository implements ServerSecurityContextRepository {
public class ReactiveServerSecurityContextRepository implements ServerSecurityContextRepository {
private static final String TOKEN_PREFIX = "Bearer ";
private final ReactiveAuthenticationManager authenticationManager;

Expand All @@ -37,8 +37,8 @@ public Mono<SecurityContext> load(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StrUtil.isBlank(authorization) || !authorization.startsWith(TOKEN_PREFIX)) {
log.warn("{} in HTTP headers not found! [{}] {}", HttpHeaders.AUTHORIZATION, request.getMethod(),
request.getURI());
log.warn("Authentication failed! Cause: `{}` in HTTP headers not found. Request URL: [{}] {}",
HttpHeaders.AUTHORIZATION, request.getMethod(), request.getURI());
return Mono.empty();
}
String jwt = authorization.replace(TOKEN_PREFIX, "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class ServerAuthenticationEntryPointImpl implements ServerAuthenticationE
public Mono<Void> commence(ServerWebExchange serverWebExchange, AuthenticationException e) {
log.error("Exception occurred when authenticating! Exception message: {}. Request URL: [{}] {}", e.getMessage(),
serverWebExchange.getRequest().getMethod(), serverWebExchange.getRequest().getURI());
log.error("{}", e.getMessage(), e);
return ResponseUtil.renderJson(serverWebExchange, HttpStatus.NETWORK_AUTHENTICATION_REQUIRED, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public class RequestFilter implements WebFilter {
@Override
@SuppressWarnings("NullableProblems")
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
log.info("{} (pre). Requester: {}, resource: [{}] {}",
log.info("{} (pre). Requester: {}, request URL: [{}] {}",
this.getClass().getSimpleName(),
RequestUtil.getRequestIpAndPort(exchange.getRequest()), exchange.getRequest().getMethod(),
exchange.getRequest().getURI());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> log.info("{} (post). Requester: {}, resource: [{}] {}",
Mono.fromRunnable(() -> log.info("{} (post). Requester: {}, request URL: [{}] {}",
this.getClass().getSimpleName(),
RequestUtil.getRequestIpAndPort(exchange.getRequest()),
exchange.getRequest().getMethod(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.jmsoftware.maf.gateway.universal.util;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.jmsoftware.maf.common.bean.ResponseBodyBean;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.val;
Expand All @@ -12,7 +13,6 @@
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
* Description: ResponseUtil, change description here.
Expand All @@ -33,13 +33,8 @@ public static Mono<Void> renderJson(@NonNull ServerWebExchange exchange, @NonNul
@Nullable Object data) {
exchange.getResponse().setStatusCode(httpStatus);
val response = exchange.getResponse();
val responseJson = new JSONObject();
responseJson.getConfig().setIgnoreNullValue(false).setDateFormat("yyyy-MM-dd HH:mm:ss");
responseJson.set("timestamp", new Date())
.set("status", httpStatus.value())
.set("message", httpStatus.getReasonPhrase())
.set("data", data);
val responseBodyBytes = JSONUtil.toJsonStr(responseJson).getBytes(StandardCharsets.UTF_8);
JSON json = ResponseBodyBean.of(httpStatus.getReasonPhrase(), data, httpStatus.value());
val responseBodyBytes = JSONUtil.toJsonStr(json).getBytes(StandardCharsets.UTF_8);
DataBuffer dataBuffer = response.bufferFactory().wrap(responseBodyBytes);
response.setStatusCode(httpStatus);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class RequestFilter extends OncePerRequestFilter {
@SuppressWarnings("NullableProblems")
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
log.info("The requester({}) requested resource. Resource: [{}] {}", RequestUtil.getRequestIpAndPort(request),
log.info("The requester({}) requested resource. Request URL: [{}] {}", RequestUtil.getRequestIpAndPort(request),
request.getMethod(), request.getRequestURL());
filterChain.doFilter(request, response);
}
Expand Down

0 comments on commit b94944d

Please sign in to comment.