刘祖明

刘祖明 查看完整档案

武汉编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

刘祖明 发布了文章 · 2017-05-12

mysql大表优化记录

概览

  1. 关系型数据库原理

  2. mysql日志分析工具

  3. 如何设计索引

  4. 如何优化sql

关系型数据库原理

这里主要是我阅读过的相关文章和书籍

mysql日志分析工具

我主要使用的是 pt-query-digest,如果需要了解更多mysql日志分析工具,请参考这篇文章mysql日志分析,如何使用 pt-query-digest请参考pt-query-digest用法

如何设计优化索引

主要参考文献为高性能mysql第三版我主要阅读第五章

如何优化sql

未完待续,慢慢补充。

查看原文

赞 0 收藏 0 评论 0

刘祖明 发布了文章 · 2017-05-12

API编写规范

为什么设计api详情规范

主要是为了保证公司的小伙伴写出的api尽量少出bug,加快研发速度。

api 设计规范

  1. 绝大部分api都请务必加入用户id限定,主要为了防止某个用户可以请求所有的数据库数据;

  2. 管理员api,必须加入 @Secured(AuthorityConstants.ADMIN) 保证该api只能使用管理员账号才能访问;

  3. 所有的api都必须加入严格的参数验证机制,保证参数不合法的时候请求快速报错,减少数据库以及微服务的压力;

  4. 所有的api都必须要保证api可以向前兼容,api以增加为主、修改为辅;

  5. api控制层,只做参数验证,不做业务逻辑,所有的业务逻辑都应该放到业务层;

查看原文

赞 1 收藏 2 评论 0

刘祖明 关注了用户 · 2017-05-08

Lrwin @lrwin

关注 58

刘祖明 发布了文章 · 2017-04-28

Spring Cloud Netflix Eureka: 多网卡环境下Eureka服务注册IP选择问题

今天遇到 Spring Cloud Netflix Eureka 的多网卡环境下Eureka服务注册IP选择问题,在研究了半天源码之后完全没有解决思路,就差点想着重新编译源码了。google到这篇文章完美解决我的问题。

http://blog.csdn.net/neosmith...

看到别人能想到网卡忽略的配置文件,为啥我想不到呢。

查看原文

赞 0 收藏 0 评论 0

刘祖明 回答了问题 · 2017-04-27

解决Spring boot配置https后,如何通过自制根证书实现在浏览器访问没有安全提示??

这个可以实现啊,当前12306就是你这么干的啊,还记得最初开始使用12306的时候,官网上还有一个教程,就是教我们如何导入证书。所以你这个问题按照你这个思路是肯定没有问题的,问题是:
1、你需要让用户知道你这个网站访问是需要证书的
2、你需要让用户知道如何导入你的证书
3、你需要让用户有动力去导入证书,比如:12306 网站

关注 3 回答 3

刘祖明 发布了文章 · 2017-04-27

记录cors跨域错误

由于公司使用的是spring boot + spring cloud 将开发全部微服务化了,在微服务的过程中将前后端完全分离了。我们公司前端使用一个域名、后端api使用一个域名,这样前后端之间就产生了跨域问题。

常见跨域问题

我们最常见的跨域问题就是浏览器提示在A域名下不可以访问B域名的api,这样的错误很好解决,我们的解决方式是基于springboot框架的。

这样的问题我们只需要在后端加入cors配置,就可以解决这类跨域问题,我们在springmvc的java配置中加入以下代码即可

    @Bean
    @ConditionalOnProperty(name = jhipster.cors.allowed-origins")
    public CorsFilter corsFilter() {
        log.debug("Registering CORS filter");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = jhipsterProperties.getCors();
        source.registerCorsConfiguration("/api/**", config);
        source.registerCorsConfiguration("/v2/api-docs", config);
        source.registerCorsConfiguration("/oauth/**", config);
        source.registerCorsConfiguration("/*/api/**", config);
        source.registerCorsConfiguration("/*/oauth/**", config);
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

多值跨域问题

错误信息:

The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:7080,http://localhost:7080', but only one is allowed. Origin 'http://localhost:7080' is therefore not allowed access.

错误信息很好理解,就是说Access-Control-Allow-Origin有两个值,但是浏览器只准许有一个值,所以报错。

我们查看浏览器中的网络请求:
图片描述

我们发现在 response headersAccess-Control-Allow-Origin 出现了两次。由于是在 response 中这说明我们是在后端出现的问题。

由于我们的后端属于标准的微服务架构,也就是:
front -> api gateway -> other Micro service
由于我已经在 api gateway 项目中已经加入了 cors,那么在 api 网关(gateway)后面的微服务不在需要加入 cors 了。由于小伙伴在后端的微服务项目中也同样加入了cors,这样在返回的response中就加入了两个相同的跨域header,这样浏览器发现多个Access-Control-Allow-Origin于是就报错了。

找到原因错误就很好解决了,我们只需要在小伙伴的后端将cors跨域配置删除即可,只保留API gateway 项目的cors配置。

查看原文

赞 1 收藏 4 评论 2

刘祖明 发布了文章 · 2017-02-22

spring boot 全局错误处理

这两天在做 spring cloud 的 API gateway 的时候,遇到了一个全局错误处理的坑,我在在 spring security 中加入了一个 filter,该 filter 用来验证 token 是否合法,如果该 token 不合法,就抛出自定义错误 InvalidTokenException ,并且要返回的状态码为403,告诉前端该用户未认证。

我开始以为使用 @ControllerAdvice 来定义全局错误处理即可,最后发现过滤器中抛出了错误 @ControllerAdvice 却无法捕获到,并抛出我需要的错误信息。最后阅读 spring 的官方文档发现,spring 的全局错误处理不是只有 @ControllerAdvice

@ControllerAdvice 主要处理的就是 controller 层的错误信息,而没有进入 controller 层的错误 @ControllerAdvice 是无法处理的,那么我需要另外的一个全局错误处理。


@ControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(ConcurrencyFailureException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    @ResponseBody
    public ErrorVM processConcurencyError(ConcurrencyFailureException ex) {
        return new ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorVM processValidationError(MethodArgumentNotValidException ex) {
        BindingResult result = ex.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        return processFieldErrors(fieldErrors);
    }



    @ExceptionHandler(CustomParameterizedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ParameterizedErrorVM processParameterizedValidationError(CustomParameterizedException ex) {
        return ex.getErrorVM();
    }

    @ExceptionHandler(InvalidTokenException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public ErrorVM processInvalidTokenException(InvalidTokenException ex) {
        return new ErrorVM(ErrorConstants.INVALID_TOKEN, ex.getMessage());
    }

    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public ErrorVM processAccessDeniedException(AccessDeniedException e) {
        return new ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.getMessage());
    }

    private ErrorVM processFieldErrors(List<FieldError> fieldErrors) {
        ErrorVM dto = Objects.nonNull(fieldErrors.get(0)) ? new ErrorVM(ErrorConstants.ERR_VALIDATION, fieldErrors.get(0).getDefaultMessage()) : new ErrorVM(ErrorConstants.ERR_VALIDATION);

        for (FieldError fieldError : fieldErrors) {
            dto.add(fieldError.getObjectName(), fieldError.getField(), fieldError.getCode());
        }

        return dto;
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ErrorVM processMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
        return new ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, exception.getMessage());
    }

    @ExceptionHandler(CustomException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.IM_USED)
    public ErrorVM processCustomException(CustomException ex) {
        return new ErrorVM(ErrorConstants.ERR_CUSTOM, ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorVM> processRuntimeException(Exception ex) {
        BodyBuilder builder;
        ErrorVM errorVM;
        ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
        if (responseStatus != null) {
            builder = ResponseEntity.status(responseStatus.value());
            errorVM = new ErrorVM("error." + responseStatus.value().value(), responseStatus.reason());
        } else {
            builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
            errorVM = new ErrorVM(ErrorConstants.ERR_INTERNAL_SERVER_ERROR, "Internal server error");
        }
        return builder.body(errorVM);
    }
}

BasicErrorController 这个类就是用来捕获 /error 的所有错误,而过滤器中的错误会被重定向到 /error。我编写一个新的控制层类 TokenErrorController 并继承 BasicErrorController 类,这样错误 json 类型的错误都会被重定向到这个控制层里。


@RestController
public class TokenErrorController extends BasicErrorController {


    public TokenErrorController(){
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    private static final String PATH = "/error";

    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        if (!Strings.isNullOrEmpty((String)body.get("exception")) && body.get("exception").equals(InvalidTokenException.class.getName())){
            body.put("status", HttpStatus.FORBIDDEN.value());
            status = HttpStatus.FORBIDDEN;
        }
        return new ResponseEntity<Map<String, Object>>(body, status);
    }

    @Override
    public String getErrorPath() {
        return PATH;
    }
}
查看原文

赞 3 收藏 7 评论 2

刘祖明 发布了文章 · 2017-01-03

websocket配合spring-security使用token认证

使用框架介绍

  • spring boot 1.4.3.RELEASE

  • spring websocket 4.3.5.RELEASE

  • spring security 4.1.3.RELEASE

  • sockjs-client 1.0.2

  • stompjs 2.3.3

项目介绍

由于公司需要使用websocket主动给前端用户推送消息,公司的项目是使用jhipster自动生成的微服务项目,而spring boot本身就集成了websocket,这样我们不用自己处理所有的网络细节代码。我们的项目主要为:
前端 - nodeJS代理 - 后端 - 计算系统(由于我们公司是做云计算的,计算系统是一个底层系统)
项目的主要流程是:请求以及数据流向

遇到的问题

由于我们使用的是spring security oauth2 来进行认证,而且我们需要吧websocket消息推送给指定用户,这样为了保证websocket和http协议使用的同一套认证系统,我们就必须要把websocket认证集成到spring security中。

第一个问题认证403错误

首先贴出websocket的配置代码


public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    private final static Logger LOG = LoggerFactory.getLogger(WebSocketConfig.class);

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/api/v1/socket/send");  // 推送消息前缀
        registry.setApplicationDestinationPrefixes("/api/v1/socket/req"); // 应用请求前缀
        registry.setUserDestinationPrefix("/user");//推送用户前缀
    }



    /**
     * 建立连接的端点
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/api/v1/socket/fallback").setAllowedOrigins("*").withSockJS().setInterceptors(httpSessionHandshakeInterceptor());
    }
}

第一次在打开websocket的时候发现三次握手都没有出现就直接报403,最后仔细看错误信息发现错误信息的链接为:/api/v1/socket/fallback/info,而且该请求使用的是http请求不是websocket请求。最后发现每一个websocket在连接端点之前都会发送一个http GET请求用于保证该服务是存在的。而该请求是程序自动发送的自能自动携带cookie数据,无法发送自定义header。

spring boot自带的认证是:如果/api/v1/socket/fallback/info该请求通过认证,那么websocket的所有请求以及发送全部自动绑定该认证用户。如果我们想办法让/api/v1/socket/fallback/info请求通过认证,那么接下来所有的问题都将解决。问题是我们的token是使用自定义header实现的认证。所以该方法不成立,所以只能让websocket自己认证。

为了解决/api/v1/socket/fallback/info请求的403问题我在安全配置中加入.authorizeRequests().antMatchers("/api/v1/socket/fallback/**").permitAll()这样第一步判断服务是否存在就解决了,这里离解决websocket的认证问题只是第一步。

第二个问题:如果发送token给后端

stomp 客户端可以直接在websocket请求中加入自定义header,如下:

let socket = new SockJS('/api/v1/socket/fallback')
let stompClient = Stomp.over(socket)
let token = localStorage.getItem('Auth-Token') // eslint-disable-line
stompClient.connect({'Auth-Token': token}, frame => {
  stompClient.subscribe('/user/api/v1/socket/send/greetings', data => {
    // TODO
  })
})

第三个问题:后端如何认证

我们在创建连接的时候前端需要将token发送到后端,现在我们已经将token发送到后端了,但是后端如何接受并处理token得到认证数据呢?带着这个问题开始google吧!http://stackoverflow.com/questions/39422053/spring-4-x-token-based-websocket-sockjs-fallback-authentication这个链接正好解决了我的问题,

UPDATE 2016-12-13 : the issue referenced below is now marked fixed, so the hack below is no longer necessary which Spring 4.3.5 or above. See https://github.com/spring-projects/spring-framework/blob/master/src/asciidoc/web-websocket.adoc#token-based-authentication.

原来这个问题在4.3.5版本中已经被继承进去了,查看自己的版本是4.3.4,不解释直接升级版本到4.3.5,然后加如代码

@EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {

  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.setInterceptors(new ChannelInterceptorAdapter() {

        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {

            StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
                    if (StringUtils.isNotEmpty(jwtToken)) {
                        UserAuthenticationToken authToken = tokenService.retrieveUserAuthToken(jwtToken);
                        SecurityContextHolder.getContext().setAuthentication(authToken);
                        accessor.setUser(authToken);
                    }
            }

            return message;
        }
    });
  }
}

开始测试,发现还是报错MissingCsrfTokenException,然后开始debug代码,发现代码错误的代码为:

public final class CsrfChannelInterceptor extends ChannelInterceptorAdapter {
    private final MessageMatcher<Object> matcher;

    public CsrfChannelInterceptor() {
        this.matcher = new SimpMessageTypeMatcher(SimpMessageType.CONNECT);
    }

    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        if(!this.matcher.matches(message)) {
            return message;
        } else {
            Map sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
            CsrfToken expectedToken = sessionAttributes == null?null:(CsrfToken)sessionAttributes.get(CsrfToken.class.getName());
            if(expectedToken == null) {  // 在这里为null
                throw new MissingCsrfTokenException((String)null);  //报错
            } else {
                String actualTokenValue = SimpMessageHeaderAccessor.wrap(message).getFirstNativeHeader(expectedToken.getHeaderName());
                boolean csrfCheckPassed = expectedToken.getToken().equals(actualTokenValue);
                if(csrfCheckPassed) {
                    return message;
                } else {
                    throw new InvalidCsrfTokenException(expectedToken, actualTokenValue);
                }
            }
        }
    }
}

仔细查看里面的数据,原来这里是需要在header中存放一些数据,于是乎将configureClientInboundChannel方法修正为:


    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(new ChannelInterceptorAdapter() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
                    LOG.debug("webSocket token is {}", jwtToken);
                    if (StringUtils.isNotEmpty(jwtToken)) {
                        Map sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
                        sessionAttributes.put(CsrfToken.class.getName(), new DefaultCsrfToken("Auth-Token", "Auth-Token", jwtToken));
                        UserAuthenticationToken authToken = tokenService.retrieveUserAuthToken(jwtToken);
                        SecurityContextHolder.getContext().setAuthentication(authToken);
                        accessor.setUser(authToken);
                    }
                }
                return message;
            }
        });
    }

然后修改websocket安全配置为:

@Configuration
public class WebsocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages.anyMessage().permitAll();
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

这样websocket 集成spring boot token的认证就搞定了。

查看原文

赞 6 收藏 10 评论 11

认证与成就

  • 获得 12 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-11-14
个人主页被 626 人浏览