前言

之前通过浏览《Spring微服务实战》写过对于spring-cloud+spring-security+oauth2的认证服务和资源服务文章,以及写过对于spring-gateway做token校验的文章,然而在实战过程中还是发现一些问题,于是通过跟敌人沟通播种了不了新常识,之前的框架设计有问题,想通过这篇文章从新梳理下校验和认证流程。

遇到的问题

1、Feign调用问题:之前所有微服务都做成了资源服务,这样feign调用的时候还要校验token,影响执行效率
2、Gateway网关问题:spring-gateway校验了token并把token通过authorization做为申请头下发到上游微服务,上游服务又校验了一遍token,影响执行效率
3、全局信息问题:如获取用户信息,微服务api接口通过OAuth2Authentication获取用户名,再通过UserService获取用户信息,这样做再次升高执行效率

如何去解决?

综合下面三点问题,提出了绝对应的解决方案:

1、微服务不须要做成资源服务(不须要校验authorization),微服务的权限还有对立解决啥的都在网关里做,这样feign调用的时候也就不须要校验token了。
2、下面说过微服务曾经不是资源服务,那么也不存在再次测验token的问题了,尽管如此,然而你能够通过spring-gateway来做对立受权达到管制外界的拜访。
3、spring-gateway校验token和封装用户信息到申请头header中,上游服务通过header中的用户信息对立保留到Context中

留神:
这里有个问题:
A服务有个Controller办法叫saveUserEvent,feign通过/gateway-name/a/saveUserEvent路由调用(feign调用api接口的时候不存在token校验问题),然而没有了资源服务的token限度,里面当然也能够通过gateway调用这个接口,所以这里遇到的问题就是:如何既保证feign的顺利调用又不能让里面申请调用呢?
针对这个问题答案是:通过gateway的路由黑名单把不想裸露给里面的api接口排除到路由里面,这样即保障了里面就再也申请不到这个接口了,又保障了服务内通过feign调用的数据安全性。

操作

针对下面的三个问题,咱们来从新架构一下咱们的微服务。

1、spring-gateway认证服务操作流程
cookie和spring-gateway联合做用户认证服务,这个是通过cookie和set-cookie来达到token传递的成果,前面独自写一篇文章解说。

2、封装用户信息到Context中
留神:封装好的Context能够独自放到context包中,并且每个微服务都必须加。

如何来封装呢?其实我代码曾经写好了,大家能够参考应用。

UserContext.java

@Componentpublic class UserContext {    public static final String CORRELATION_ID = "correlation-id";    public static final String AUTH_TOKEN = "authorization";    public static final String USER = "user";    private static final ThreadLocal<String> correlationId = new ThreadLocal<String>();    private static final ThreadLocal<String> authToken = new ThreadLocal<String>();    private static final ThreadLocal<LoginUser> user = new ThreadLocal<>();    public static String getCorrelationId() {        return correlationId.get();    }    public static void setCorrelationId(String cid) {        correlationId.set(cid);    }    public static String getAuthToken() {        return authToken.get();    }    public static void setAuthToken(String token) {        authToken.set(token);    }    public static LoginUser getUser() {        return user.get();    }    public static void setUser(LoginUser u) {        user.set(u);    }    public static HttpHeaders getHttpHeaders() {        HttpHeaders httpHeaders = new HttpHeaders();        httpHeaders.set(CORRELATION_ID, getCorrelationId());        return httpHeaders;    }}

UserContextFilter.java

@Componentpublic class UserContextFilter implements Filter {    private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class);    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)            throws IOException, ServletException {        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;        ObjectMapper mapper = new ObjectMapper();        String userJson = httpServletRequest.getHeader(UserContext.USER);        if(StringUtils.hasLength(userJson)){            LoginUser userMap = mapper.readValue(userJson, LoginUser.class);            UserContextHolder.getContext().setUser(userMap);        }        UserContextHolder.getContext().setCorrelationId(  httpServletRequest.getHeader(UserContext.CORRELATION_ID) );        UserContextHolder.getContext().setAuthToken( httpServletRequest.getHeader(UserContext.AUTH_TOKEN) );        logger.debug("---Incoming Correlation id: {}---" ,UserContextHolder.getContext().getCorrelationId());//        logger.debug("---Incoming Authorization token: {}---" ,UserContextHolder.getContext().getAuthToken());        filterChain.doFilter(httpServletRequest, servletResponse);    }    @Override    public void init(FilterConfig filterConfig) throws ServletException {}    @Override    public void destroy() {}}

UserContextHolder.java

public class UserContextHolder {    private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();    public static final UserContext getContext(){        UserContext context = userContext.get();        if (context == null) {            context = createEmptyContext();            userContext.set(context);        }        return userContext.get();    }    public static final void setContext(UserContext context) {        Assert.notNull(context, "Only non-null UserContext instances are permitted");        userContext.set(context);    }    public static final UserContext createEmptyContext(){        return new UserContext();    }}

UserContextInterceptor.java

public class UserContextInterceptor implements ClientHttpRequestInterceptor {    private static final Logger logger = LoggerFactory.getLogger(UserContextInterceptor.class);    @Override    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {        HttpHeaders headers = request.getHeaders();        headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());        headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());        LoginUser user = UserContextHolder.getContext().getUser();        ObjectMapper mapper = new ObjectMapper();        String userInfo = mapper.writeValueAsString(user);        headers.add(UserContext.USER, userInfo);        return execution.execute(request, body);    }}

代码就这么多了,接下来咱们看下如何应用?

XxxController.java

public ResponseEntity<?> addLikeUrl(){        LoginUser loginUser = UserContext.getUser();        if (loginUser == null) {            return ResponseEntity.ok(new ResultInfo<>(ResultStatus.USER_NOT_FOUND));        }    }

这里的UserContext.getUser办法就能够获取到全局的登录的用户了。

3、如果你之前参考了我下面的两篇文章构建了认证和资源服务,那么你当初能够把之前的代码去掉了,否则略过该过程。

3.1、去掉@EnableResourceServer

@SpringBootApplication// 资源爱护服务@EnableResourceServer// 服务发现@EnableDiscoveryClient// 启用feign@EnableFeignClients@RefreshScopepublic class AccountServiceApplication {    @Bean    public BCryptPasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    public static void main(String[] args) {        SpringApplication.run(AccountServiceApplication.class,args);    }}

3.2、去掉spring-cloud-security和spring-cloud-starter-oauth2

<dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-security</artifactId>    <version>2.2.5.RELEASE</version></dependency><dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-oauth2</artifactId>    <version>2.2.5.RELEASE</version></dependency>

3、删除security包

总结

1、《Spring微服务实战》是本好书,不过就像hibernate一样,在国外很火到了国内就有了本人的了解了。
2、《Spring微服务实战》公布了第二版,有趣味的能够看看。