前言
之前通过浏览《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微服务实战》公布了第二版,有趣味的能够看看。