This commit is contained in:
wangkang
2017-08-21 19:36:08 +08:00
parent 1e080c2e4e
commit 5da9955b08
17 changed files with 51 additions and 624 deletions
@@ -1,40 +0,0 @@
package org.itkk.udf.auth;
import lombok.Data;
import org.itkk.udf.auth.meta.ExcludeServiceMeta;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* AuthProperties
*/
@Component
@Data
@ConfigurationProperties(prefix = "org.itkk.auth.properties")
public class AuthProperties {
/**
* 用户服务排除
*/
private Map<String, ExcludeServiceMeta> userExcludeService;
/**
* 客户端服务排除
*/
private Map<String, ExcludeServiceMeta> clientExcludeServiceMeta;
/**
* 构造方法
*/
public AuthProperties() {
userExcludeService = new HashMap<>();
ExcludeServiceMeta authentication = new ExcludeServiceMeta();
authentication.setMethod(Constant.USER_AUTHENTICATION_METHOD);
authentication.setUrl(Constant.USER_AUTHENTICATION_URL);
userExcludeService.put("AUTH_USER_1", authentication);
}
}
@@ -13,36 +13,6 @@ package org.itkk.udf.auth;
*/
public class Constant {
/**
* 描述 : 用户鉴权url
*/
public static final String USER_AUTHENTICATION_URL = "/service/user";
/**
* 描述 : 用户鉴权http方法
*/
public static final String USER_AUTHENTICATION_METHOD = "POST";
/**
* 描述 : 头信息(用户类型)
*/
public static final String HEADER_USER_TYPE = "userType";
/**
* 描述 : 头信息(token)
*/
public static final String HEADER_ACCESS_TOKEN = "accessToken";
/**
* 身份认证顺序
*/
public static final int ORDER_IDENTITY = 10000;
/**
* 权限认证顺序
*/
public static final int ORDER_PURVIEW = 10001;
/**
* rms认证顺序
*/
@@ -1,38 +0,0 @@
package org.itkk.udf.auth;
/**
* IUserAuthenticationService
*/
public interface IUserAuthenticationService {
/**
* 用户鉴权
* <p>
* 需要处理单例登录的情况
* </p>
* <p>
* 在单例登录的情况下,需要处理强制登录的情况
* </p>
*
* @param account 账号
* @param password 密码
* @param singleton 是否单例
* @param forced 是否强制
* @return accessToken
*/
String authentication(String account, String password, boolean singleton, boolean forced);
/**
* toke校验
* <p>
* 需要处理单例登录的情况
* </p>
* <p>
* 在单例登录的情况下,需要处理强制登录的情况
* </p>
*
* @param accessToken accessToken
*/
void verification(String accessToken);
}
@@ -1,9 +1,9 @@
package org.itkk.udf.auth.filter.rms;
package org.itkk.udf.auth;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.itkk.udf.auth.filter.AbstractBaseZuulFilter;
import org.itkk.udf.core.exception.AuthException;
import org.itkk.udf.rms.Constant;
import org.itkk.udf.rms.RmsProperties;
@@ -22,7 +22,7 @@ import static com.netflix.zuul.context.RequestContext.getCurrentContext;
*/
@Component
@Slf4j
public class RmsFilter extends AbstractBaseZuulFilter {
public class RmsFilter extends ZuulFilter {
/**
* 描述 : 应用名称
@@ -1,56 +0,0 @@
package org.itkk.udf.auth;
import java.util.HashMap;
import java.util.Map;
/**
* 描述 : UserType
*
* @author wangkang
*/
public enum UserType {
/**
* 描述 : 用户
*/
USER,
/**
* 描述 : 客户端
*/
CLIENT;
/**
* mappings
*/
private static final Map<String, UserType> MAPPINGS = new HashMap<>();
/**
* 静态块
*/
static {
for (UserType userType : values()) {
MAPPINGS.put(userType.name(), userType);
}
}
/**
* 转换为枚举类型
*
* @param userType 用户类型
* @return 枚举
*/
public static UserType resolve(String userType) {
return (userType != null ? MAPPINGS.get(userType) : null);
}
/**
* 匹配
*
* @param userType 用户类型
* @return 匹配结果
*/
public boolean matches(String userType) {
return (this == resolve(userType));
}
}
@@ -1,49 +0,0 @@
package org.itkk.udf.auth.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import java.io.Serializable;
/**
* UserAuthenticationModel
*/
@ApiModel(description = "用户鉴权输入模型")
@Data
@Validated
public class UserAuthenticationModel implements Serializable {
/**
* 描述 : ID
*/
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
@ApiModelProperty(value = "用户名", required = true, dataType = "string")
@NotBlank
private String account;
/**
* 密码
*/
@ApiModelProperty(value = "密码", required = true, dataType = "string")
@NotBlank
private String password;
/**
* 是否单例登录默认false(如果为true,则同一账号,只能同时一个人登录)
*/
@ApiModelProperty(value = "是否单例登录默认false(如果为true,则同一账号,只能同时一个人登录)", required = false, dataType = "boolean")
private boolean singleton = false;
/**
* 强制登录(只在禁止同时登录的模式下有效)
*/
@ApiModelProperty(value = "强制登录(只在禁止同时登录的模式下有效)", required = false, dataType = "boolean")
private boolean forced = false;
}
@@ -1,20 +0,0 @@
package org.itkk.udf.auth.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* UserAuthenticationResult
*/
@ApiModel(description = "用户鉴权响应模型")
@Data
@AllArgsConstructor
public class UserAuthenticationResult {
/**
* 授权token
*/
@ApiModelProperty(value = "授权token", required = true, dataType = "string")
private String accessToken;
}
@@ -1,116 +0,0 @@
package org.itkk.udf.auth.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.itkk.udf.auth.AuthProperties;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.UserType;
import org.itkk.udf.auth.meta.ExcludeServiceMeta;
import org.itkk.udf.core.exception.AuthException;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static com.netflix.zuul.context.RequestContext.getCurrentContext;
/**
* AbstractBaseZuulFilter
*/
public abstract class AbstractBaseZuulFilter extends ZuulFilter {
/**
* authProperties
*/
@Autowired
private AuthProperties authProperties;
/**
* 获得token
*
* @return token
*/
public String getAccessToken() {
//获得request
RequestContext ctx = getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获得accessToken
String accessToken = request.getHeader(Constant.HEADER_ACCESS_TOKEN);
if (StringUtils.isBlank(accessToken)) {
accessToken = request.getParameter(Constant.HEADER_ACCESS_TOKEN);
}
//判空
if (StringUtils.isBlank(accessToken)) {
throw new AuthException("missing required authentication parameters (accessToken)");
}
//返回
return accessToken;
}
/**
* 判断排除
*
* @param targetUserType 目标用户类型
* @return 是否匹配
*/
public boolean checkExclude(UserType targetUserType) {
//获得request
RequestContext ctx = getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获得url和method
String url = request.getRequestURI();
String method = request.getMethod();
//获得排除列表
Map<String, ExcludeServiceMeta> map = new HashMap<>();
if (targetUserType.equals(UserType.USER)) {
map = authProperties.getUserExcludeService();
} else if (targetUserType.equals(UserType.CLIENT)) {
map = authProperties.getClientExcludeServiceMeta();
}
//判空 & 遍历 & 匹配排除规则
if (MapUtils.isNotEmpty(map)) {
Iterator<String> keys = map.keySet().iterator();
while (keys.hasNext()) {
ExcludeServiceMeta item = map.get(keys.next());
if (item.getUrl().startsWith(url) && item.getMethod().equals(method)) {
return false;
}
}
}
//返回
return true;
}
/**
* 判断用户类型
* s
*
* @param targetUserType 目标用户类型
* @return 是否匹配
*/
public boolean checkUserType(UserType targetUserType) {
//获得request
RequestContext ctx = getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获得用户类型
String userType = request.getHeader(Constant.HEADER_USER_TYPE);
if (StringUtils.isBlank(userType)) {
userType = request.getParameter(Constant.HEADER_USER_TYPE);
}
//判空
if (StringUtils.isBlank(userType)) {
throw new AuthException("must be set userType");
}
//匹配
UserType userTypeEnum = UserType.resolve(userType);
if (userTypeEnum == null) {
throw new AuthException("unknow userType");
}
//判断是否拦截 & 返回
return userTypeEnum.equals(targetUserType);
}
}
@@ -1,40 +0,0 @@
package org.itkk.udf.auth.filter.client;
import lombok.extern.slf4j.Slf4j;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.UserType;
import org.itkk.udf.auth.filter.AbstractBaseZuulFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
/**
* ClientIdentityFilter
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "org.itkk.auth.filter.client.identity.enabled", matchIfMissing = true)
public class ClientIdentityFilter extends AbstractBaseZuulFilter {
@Override
public Object run() {
log.info("ClientIdentityFilter.run");
return null;
}
@Override
public boolean shouldFilter() {
return this.checkUserType(UserType.CLIENT) && this.checkExclude(UserType.CLIENT);
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return Constant.ORDER_IDENTITY;
}
}
@@ -1,40 +0,0 @@
package org.itkk.udf.auth.filter.client;
import lombok.extern.slf4j.Slf4j;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.UserType;
import org.itkk.udf.auth.filter.AbstractBaseZuulFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
/**
* PurviewFilter
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "org.itkk.auth.filter.client.purviews.enabled", matchIfMissing = true)
public class ClientPurviewFilter extends AbstractBaseZuulFilter {
@Override
public Object run() {
log.info("ClientPurviewFilter.run");
return null;
}
@Override
public boolean shouldFilter() {
return this.checkUserType(UserType.CLIENT) && this.checkExclude(UserType.CLIENT);
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return Constant.ORDER_PURVIEW;
}
}
@@ -1,58 +0,0 @@
package org.itkk.udf.auth.filter.user;
import lombok.extern.slf4j.Slf4j;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.IUserAuthenticationService;
import org.itkk.udf.auth.UserType;
import org.itkk.udf.auth.filter.AbstractBaseZuulFilter;
import org.itkk.udf.core.exception.SystemRuntimeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
/**
* IdentityFilter
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "org.itkk.auth.filter.user.identity.enabled", matchIfMissing = true)
public class UserIdentityFilter extends AbstractBaseZuulFilter {
/**
* userAuthenticationService
*/
@Autowired(required = false)
private IUserAuthenticationService userAuthenticationService;
@Override
public Object run() {
log.info("UserIdentityFilter.run");
//判空
if (userAuthenticationService == null) {
throw new SystemRuntimeException("You need to implement the IUserAuthenticationService interface");
}
//获得accessToken
String accessToken = this.getAccessToken();
//校验token
userAuthenticationService.verification(accessToken);
//返回
return null;
}
@Override
public boolean shouldFilter() {
return this.checkUserType(UserType.USER) && this.checkExclude(UserType.USER);
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return Constant.ORDER_IDENTITY;
}
}
@@ -1,40 +0,0 @@
package org.itkk.udf.auth.filter.user;
import lombok.extern.slf4j.Slf4j;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.UserType;
import org.itkk.udf.auth.filter.AbstractBaseZuulFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
/**
* PurviewFilter
*/
@Component
@Slf4j
@ConditionalOnProperty(value = "org.itkk.auth.filter.user.purview.enabled", matchIfMissing = true)
public class UserPurviewFilter extends AbstractBaseZuulFilter {
@Override
public Object run() {
log.info("UserPurviewFilter.run");
return null;
}
@Override
public boolean shouldFilter() {
return this.checkUserType(UserType.USER) && this.checkExclude(UserType.USER);
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return Constant.ORDER_PURVIEW;
}
}
@@ -1,19 +0,0 @@
package org.itkk.udf.auth.meta;
import lombok.Data;
/**
* ExcludeServiceMeta
*/
@Data
public class ExcludeServiceMeta {
/**
* url
*/
private String url;
/**
* 请求方法
*/
private String method;
}
@@ -1,30 +0,0 @@
package org.itkk.udf.auth.web;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.itkk.udf.auth.Constant;
import org.itkk.udf.auth.domain.UserAuthenticationModel;
import org.itkk.udf.auth.domain.UserAuthenticationResult;
import org.itkk.udf.core.RestResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* IUserAuthenticationController
*/
@Api(value = "用户鉴权服务", consumes = "application/json", produces = "application/json", protocols = "http")
@RequestMapping(value = Constant.USER_AUTHENTICATION_URL)
public interface IUserAuthenticationController { //NOSONAR
/**
* 用户鉴权
*
* @param model 用户鉴权输入模型
* @return 用户鉴权响应模型
*/
@ApiOperation(value = "AUTH_USER_1", notes = "用户鉴权")
@PostMapping
RestResponse<UserAuthenticationResult> authentication(@RequestBody UserAuthenticationModel model);
}
@@ -1,32 +0,0 @@
package org.itkk.udf.auth.web;
import lombok.extern.slf4j.Slf4j;
import org.itkk.udf.auth.IUserAuthenticationService;
import org.itkk.udf.auth.domain.UserAuthenticationModel;
import org.itkk.udf.auth.domain.UserAuthenticationResult;
import org.itkk.udf.core.RestResponse;
import org.itkk.udf.core.exception.SystemRuntimeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* UserAuthenticationController
*/
@RestController
@Slf4j
public class UserAuthenticationController implements IUserAuthenticationController {
/**
* userAuthenticationService
*/
@Autowired(required = false)
private IUserAuthenticationService userAuthenticationService;
@Override
public RestResponse<UserAuthenticationResult> authentication(@RequestBody UserAuthenticationModel model) {
if (userAuthenticationService == null) {
throw new SystemRuntimeException("You need to implement the IUserAuthenticationService interface");
}
return new RestResponse<>(new UserAuthenticationResult(userAuthenticationService.authentication(model.getAccount(), model.getPassword(), model.isSingleton(), model.isForced())));
}
}
@@ -21,7 +21,10 @@ import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* ErrorController
@@ -80,7 +83,11 @@ public class ExceptionController extends AbstractErrorController {
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
RestResponse<String> restResponse = this.getRestResponse(request, status, model);
model.put("restResponse", restResponse);
model.put("exception", restResponse.getError().getType());
model.put("message", restResponse.getError().getMessage());
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
@@ -96,13 +103,40 @@ public class ExceptionController extends AbstractErrorController {
@ResponseBody
public ResponseEntity<RestResponse<String>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
String path = body.containsKey("path") ? body.get("path").toString() : "unknow path";
HttpStatus status = getStatus(request);
return new ResponseEntity<>(getRestResponse(request, status, body), status);
}
/**
* 获得RestResponse
*
* @param request request
* @param status status
* @param body body
* @return RestResponse
*/
private RestResponse<String> getRestResponse(HttpServletRequest request, HttpStatus status, Map<String, Object> body) {
ErrorResult errorResult = new ErrorResult();
errorResult.setType(NoHandlerFoundException.class.getName());
errorResult.setMessage("No handler found for " + request.getMethod() + " --> " + path);
if (status == HttpStatus.NOT_FOUND) { //404处理
errorResult.setType(NoHandlerFoundException.class.getName());
errorResult.setMessage(body.get("path").toString());
} else { //非404处理
Object object = request.getAttribute("javax.servlet.error.exception");
if (object != null && object instanceof Exception) { //上下文中能拿到异常的情况
Exception exception = (Exception) object;
//ZuulException异常特殊处理,去除ZuulException的包裹 (不用instanceof的原因是不想因为这里的判断而引入zuul的依赖在core包中)
if (exception.getClass().getName().equals("com.netflix.zuul.exception.ZuulException")) { //NOSONAR
errorResult = ExceptionHandle.buildError(applicationConfig, exception.getCause());
} else {
errorResult = ExceptionHandle.buildError(applicationConfig, exception);
}
} else { //上下文中拿不到异常的情况
errorResult.setType(body.get("exception").toString());
errorResult.setMessage(body.get("message").toString());
}
}
errorResult.setDate(new Date());
return new ResponseEntity<>(new RestResponse<>(status, errorResult), status);
return new RestResponse<>(status, errorResult);
}
/**
@@ -55,7 +55,7 @@ public class ExceptionHandle extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
HttpStatus localHttpStatus = status;
ErrorResult errorResult = this.buildError(ex);
ErrorResult errorResult = buildError(applicationConfig, ex);
if (ex instanceof PermissionException) { //权限异常
localHttpStatus = HttpStatus.FORBIDDEN;
} else if (ex instanceof AuthException) { //认证异常
@@ -74,24 +74,25 @@ public class ExceptionHandle extends ResponseEntityExceptionHandler {
throw new SystemRuntimeException(e);
}
}
log.error(ex.getClass().getName(), ex);
return super.handleExceptionInternal(ex, new RestResponse<>(localHttpStatus, errorResult), headers, localHttpStatus, request);
}
/**
* 描述 : 构造错误响应对象
*
* @param exception 异常
* @param applicationConfig 系统配置
* @param throwable 异常
* @return 错误响应对象
*/
private ErrorResult buildError(Exception exception) {
public static ErrorResult buildError(ApplicationConfig applicationConfig, Throwable throwable) {
ErrorResult error = new ErrorResult();
error.setType(exception.getClass().getName());
error.setMessage(ExceptionUtils.getMessage(exception));
error.setType(throwable.getClass().getName());
error.setMessage(ExceptionUtils.getMessage(throwable));
if (applicationConfig.isOutputExceptionStackTrace()) {
error.setStackTrace(ExceptionUtils.getStackTrace(exception));
error.setStackTrace(ExceptionUtils.getStackTrace(throwable));
}
error.setDate(new Date());
log.error(exception.getClass().getName(), exception);
return error;
}
}