引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的完结篇,前面三篇已经将认证鉴权与API权限控制的流程和主要细节讲解完。本文比较长,对这个系列进行收尾,主要内容包括对授权和鉴权流程之外的endpoint以及Spring Security过滤器部分踩坑的经历。欢迎阅读本系列文章。

1. 前文回顾

首先还是照例对前文进行回顾。在第一篇 认证鉴权与API权限控制在微服务架构中的设计与实现(一)介绍了该项目的背景以及技术调研与最后选型。第二篇认证鉴权与API权限控制在微服务架构中的设计与实现(二)画出了简要的登录和校验的流程图,并重点讲解了用户身份的认证与token发放的具体实现。第三篇认证鉴权与API权限控制在微服务架构中的设计与实现(三)先介绍了资源服务器配置,以及其中涉及的配置类,后面重点讲解了token以及API级别的鉴权。

本文将会讲解剩余的两个内置端点:注销和刷新token。注销token端点的处理与Spring Security默认提供的有些’/logout’有些区别,不仅清空SpringSecurityContextHolder中的信息,还要增加对存储token的清空。另一个刷新token端点其实和之前的请求授权是一样的API,只是参数中的grant_type不一样。

除了以上两个内置端点,后面将会重点讲下几种Spring Security过滤器。API级别的操作权限校验本来设想是通过Spring Security的过滤器实现,特地把这边学习了一遍,踩了一遍坑。

最后是本系列的总结,并对于存在的不足和后续工作进行论述。

2. 其他端点

2.1 注销端点

在第一篇中提到了Auth系统内置的注销端点 /logout,如果还记得第三篇资源服务器的配置,下面的关于/logout配置一定不陌生。

 //...
.and().logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.addLogoutHandler(customLogoutHandler());

上面配置的主要作用是:

  • 设置注销的URL
  • 清空Authentication信息
  • 设置注销成功的处理方式
  • 设置自定义的注销处理方式

当然在LogoutConfigurer中还有更多的设置选项,笔者此处列出项目所需要的配置项。这些配置项围绕着LogoutFilter过滤器。顺带讲一下Spring Security的过滤器。其使用了springSecurityFillterChian作为了安全过滤的入口,各种过滤器按顺序具体如下:

  • SecurityContextPersistenceFilter:与SecurityContext安全上下文信息有关
  • HeaderWriterFilter:给http响应添加一些Header
  • CsrfFilter:防止csrf攻击,默认开启
  • LogoutFilter:处理注销的过滤器
  • UsernamePasswordAuthenticationFilter:表单认证过滤器
  • RequestCacheAwareFilter:缓存request请求
  • SecurityContextHolderAwareRequestFilter:此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
  • AnonymousAuthenticationFilter:匿名身份过滤器
  • SessionManagementFilter:session相关的过滤器,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
  • ExceptionTranslationFilter:异常处理过滤器
  • FilterSecurityInterceptor:web应用安全的关键Filter

各种过滤器简单标注了作用,在下一节重点讲其中的几个过滤器。注销过滤器排在靠前的位置,我们一起看下LogoutFilter的UML类图。

类图和我们之前配置时的思路是一致的,HttpSecurity创建了LogoutConfigurer,我们在这边配置了LogoutConfigurer的一些属性。同时LogoutConfigurer根据这些属性创建了LogoutFilter

LogoutConfigurer的配置,第一和第二点就不用再详细解释了,一个是设置端点,另一个是清空认证信息。
对于第三点,配置注销成功的处理方式。由于项目是前后端分离,客户端只需要知道执行成功该API接口的状态,并不用返回具体的页面或者继续向下传递请求。因此,这边配置了默认的HttpStatusReturningLogoutSuccessHandler,成功直接返回状态码200。
对于第四点配置,自定义注销处理的方法。这边需要借助TokenStore,对token进行操作。TokenStore在之前文章的配置中已经讲过,使用的是JdbcTokenStore。首先校验请求的合法性,如果合法则对其进行操作,先后移除refreshTokenexistingAccessToken

 public class CustomLogoutHandler implements LogoutHandler {
//...
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//确定注入了tokenStore
Assert.notNull(tokenStore, "tokenStore must be set");
//获取头部的认证信息
String token = request.getHeader("Authorization");
Assert.hasText(token, "token must be set");
//校验token是否符合JwtBearer格式
if (isJwtBearerToken(token)) {
token = token.substring(6);
OAuth2AccessToken existingAccessToken = tokenStore.readAccessToken(token);
OAuth2RefreshToken refreshToken;
if (existingAccessToken != null) {
if (existingAccessToken.getRefreshToken() != null) {
LOGGER.info("remove refreshToken!", existingAccessToken.getRefreshToken());
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
LOGGER.info("remove existingAccessToken!", existingAccessToken);
tokenStore.removeAccessToken(existingAccessToken);
}
return;
} else {
throw new BadClientCredentialsException();
}
}
//...
}

执行如下请求:

method: get
url: http://localhost:9000/logout
header:
{
Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}

注销成功则会返回200,将token和SecurityContextHolder进行清空。

2.2 刷新端点

在第一篇就已经讲过,由于token的时效一般不会很长,而refresh token一般周期会很长,为了不影响用户的体验,可以使用refresh token去动态的刷新token。刷新token主要与RefreshTokenGranter有关,CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,refresh_ token对应的granter就是RefreshTokenGranter,而granter内部则是通过grantType来区分是否是各自的授权类型。执行如下请求:

method: post
url: http://localhost:12000/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE
header:
{
Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=
}

在refresh_ token正确的情况下,其返回的response和/oauth/token得到正常的响应是一样的。具体的代码可以参阅第二篇的讲解。

3. Spring Security过滤器

在上一节我们介绍了内置的两个端点的实现细节,还提到了HttpSecurity过滤器,因为注销端点的实现就是通过过滤器的作用。核心的过滤器主要有:

  • FilterSecurityInterceptor
  • UsernamePasswordAuthenticationFilter
  • SecurityContextPersistenceFilter
  • ExceptionTranslationFilter

这一节将重点介绍其中的UsernamePasswordAuthenticationFilterFilterSecurityInterceptor

3.1 UsernamePasswordAuthenticationFilter

笔者在刚开始看关于过滤器的文章,对于UsernamePasswordAuthenticationFilter有不少的文章介绍。如果只是引入Spring-Security,必然会与/login端点熟悉。SpringSecurity强制要求我们的表单登录页面必须是以POST方式向/login URL提交请求,而且要求用户名和密码的参数名必须是username和password。如果不符合,则不能正常工作。原因在于,当我们调用了HttpSecurity对象的formLogin方法时,其最终会给我们注册一个过滤器UsernamePasswordAuthenticationFilter。看一下该过滤器的源码。

 public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//用户名、密码
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
//post请求/login
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
//实现抽象类AbstractAuthenticationProcessingFilter的抽象方法,尝试验证
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request); //···
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
//···
return this.getAuthenticationManager().authenticate(authRequest);
}
}
 public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
//... //调用requiresAuthentication,判断请求是否需要authentication,如果需要则调用attemptAuthentication
//有三种结果可能返回:
//1.Authentication对象
//2. AuthenticationException
//3. Authentication对象为空
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//不需要校验,继续传递
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
//...
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
//实际执行的authentication,继承类必须实现该抽象方法
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
//成功authentication的默认行为
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//...
}
//失败authentication的默认行为
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
//...
}
...
//设置AuthenticationManager
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
...
}

UsernamePasswordAuthenticationFilter因为继承了AbstractAuthenticationProcessingFilter才拥有过滤器的功能。AbstractAuthenticationProcessingFilter要求设置一个authenticationManager,authenticationManager的实现类将实际处理请求的认证。AbstractAuthenticationProcessingFilter将拦截符合过滤规则的request,并试图执行认证。子类必须实现 attemptAuthentication 方法,这个方法执行具体的认证。
认证之后的处理和上注销的差不多。如果认证成功,将会把返回的Authentication对象存放在SecurityContext,并调用SuccessHandler,也可以设置指定的URL和指定自定义的处SuccessHandler。如果认证失败,默认会返回401代码给客户端,也可以设置URL,指定自定义的处理FailureHandler。

基于UsernamePasswordAuthenticationFilter自定义的AuthenticationFilte还是挺多案例的,这边推荐一篇博文Spring Security(五)–动手实现一个IP_Login,写得比较详细。

3.2 FilterSecurityInterceptor

FilterSecurityInterceptor是filterchain中比较复杂,也是比较核心的过滤器,主要负责web应用安全授权的工作。首先看下对于自定义的FilterSecurityInterceptor配置。

 @Override
public void configure(HttpSecurity http) throws Exception { ...
//添加CustomSecurityFilter,过滤器的顺序放在FilterSecurityInterceptor
http.antMatcher("/oauth/check_token").addFilterAt(customSecurityFilter(), FilterSecurityInterceptor.class);
}
//提供实例化的自定义过滤器
@Bean
public CustomSecurityFilter customSecurityFilter() {
return new CustomSecurityFilter();
}

从上述配置可以看到,在FilterSecurityInterceptor的位置注册了CustomSecurityFilter,对于匹配到/oauth/check_token,则会调用该进入该过滤器。下图为FilterSecurityInterceptor的类图,在其中还添加了CustomSecurityFilter和相关实现的接口的类,方便读者对比着看。

CustomSecurityFilter是模仿FilterSecurityInterceptor实现,继承AbstractSecurityInterceptor和实现Filter接口。整个过程需要依赖AuthenticationManagerAccessDecisionManagerFilterInvocationSecurityMetadataSource
AuthenticationManager是认证管理器,实现用户认证的入口;AccessDecisionManager是访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源;FilterInvocationSecurityMetadataSource是资源源数据定义,即定义某一资源可以被哪些角色访问。
从上面的类图中可以看到自定义的CustomSecurityFilter同时又实现了
AccessDecisionManagerFilterInvocationSecurityMetadataSource。分别为SecureResourceFilterInvocationDefinitionSourceSecurityAccessDecisionManager。下面分析下主要的配置。

 //通过一个实现的filter,对HTTP资源进行安全处理
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//被filter chain真实调用的方法,通过invoke代理
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
//代理的方法
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//...省略
}
}

上述代码是FilterSecurityInterceptor中的实现,具体实现细节就没列出了,我们这边重点在于对自定义的实现进行讲解。

 public class CustomSecurityFilter extends AbstractSecurityInterceptor implements Filter {

     @Autowired
SecureResourceFilterInvocationDefinitionSource invocationSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecurityAccessDecisionManager decisionManager;
//设置父类中的属性
@PostConstruct
public void init() {
super.setAccessDecisionManager(decisionManager);
super.setAuthenticationManager(authenticationManager);
}
//主要的过滤方法,与原来的一致
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//logger.info("doFilter in Security ");
//构造一个FilterInvocation,封装request, response, chain
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
//beforeInvocation会调用SecureResourceDataSource中的逻辑,类似于aop中的before
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
//完成后续工作,类似于aop中的after
super.afterInvocation(token, null);
}
} //... //资源源数据定义,设置为自定义的SecureResourceFilterInvocationDefinitionSource
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return invocationSource;
}
}

上面自定义的CustomSecurityFilter,与我们之前的讲解是一样的流程。主要依赖的三个接口都有在实现中实例化注入。看下父类的beforeInvocation方法,其中省略了一些不重要的代码片段。

 protected InterceptorStatusToken beforeInvocation(Object object) {
//根据SecurityMetadataSource获取配置的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//...
//判断是否需要对认证实体重新认证,默认为否
Authentication authenticated = authenticateIfRequired(); // Attempt authorization
try {
//决策管理器开始决定是否授权,如果授权失败,直接抛出AccessDeniedException
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException)); throw accessDeniedException;
}
}

上面代码可以看出,第一步是根据SecurityMetadataSource获取配置的权限属性,accessDecisionManager会用到权限列表信息。然后判断是否需要对认证实体重新认证,默认为否。第二步是接着决策管理器开始决定是否授权,如果授权失败,直接抛出AccessDeniedException。

(1). 获取配置的权限属性

 public class SecureResourceFilterInvocationDefinitionSource implements FilterInvocationSecurityMetadataSource, InitializingBean {
private PathMatcher matcher;
//map保存配置的URL对应的权限集
private static Map<String, Collection<ConfigAttribute>> map = new HashMap<>();
//根据传入的对象URL进行循环
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
logger.info("getAttributes");
//应该做instanceof
FilterInvocation filterInvocation = (FilterInvocation) o;
//String method = filterInvocation.getHttpRequest().getMethod();
String requestURI = filterInvocation.getRequestUrl();
//循环资源路径,当访问的Url和资源路径url匹配时,返回该Url所需要的权限
for (Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iterator = map.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<String, Collection<ConfigAttribute>> entry = iterator.next();
String url = entry.getKey();
if (matcher.match(url, requestURI)) {
return map.get(requestURI);
}
}
return null;
} //... //设置权限集,即上述的map
@Override
public void afterPropertiesSet() throws Exception {
logger.info("afterPropertiesSet");
//用来匹配访问资源路径
this.matcher = new AntPathMatcher();
//可以有多个权限
Collection<ConfigAttribute> atts = new ArrayList<>();
ConfigAttribute c1 = new SecurityConfig("ROLE_ADMIN");
atts.add(c1);
map.put("/oauth/check_token", atts);
}
}

上面是getAttributes()实现的具体细节,将请求的URL取出进行匹配事先设定的受限资源,最后返回需要的权限、角色。系统在启动的时候就会读取到配置的map集合,对于拦截到请求进行匹配。代码中注释比较详细,这边不多说。

(2). 决策管理器

 public class SecurityAccessDecisionManager implements AccessDecisionManager {
//... @Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
logger.info("decide url and permission");
//集合为空
if (collection == null) {
return;
}
Iterator<ConfigAttribute> ite = collection.iterator();
//判断用户所拥有的权限,是否符合对应的Url权限,如果实现了UserDetailsService,则用户权限是loadUserByUsername返回用户所对应的权限
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ca.getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {
logger.info("GrantedAuthority: {}", ga);
if (needRole.equals(ga.getAuthority())) {
return;
}
}
}
logger.error("AccessDecisionManager: no right!");
throw new AccessDeniedException("no right!");
} //...
}

上面的代码是决策管理器的实现,其逻辑也比较简单,将请求所具有的权限与设定的受限资源所需的进行匹配,如果具有则返回,否则抛出没有正确的权限异常。默认提供的决策管理器有三种,分别为AffirmativeBased、ConsensusBased、UnanimousBased,篇幅有限,我们这边不再扩展了。

补充一下,所具有的权限是通过之前配置的认证方式,有password认证和client认证两种。我们之前在授权服务器中配置了withClientDetails,所以用frontend身份验证获得的权限是我们预先配置在数据库中的authorities。

4. 总结

Auth系统主要功能是授权认证和鉴权。项目微服务化后,原有的单体应用基于HttpSession认证鉴权不能满足微服务架构下的需求。每个微服务都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限,尤其当有多个客户端,包括web端、移动端等等,单体应用架构下的鉴权方式就不是特别合适了。权限服务作为基础的公共服务,也需要微服务化。

笔者的设计中,Auth服务一方面进行授权认证,另一方面是基于token进行身份合法性和API级别的权限校验。对于某个服务的请求,经过网关会调用Auth服务,对token合法性进行验证。同时笔者根据当前项目的整体情况,存在部分遗留服务,这些遗留服务又没有足够的时间和人力立马进行微服务改造,而且还需要继续运行。为了适配当前新的架构,采取的方案就是对这些遗留服务的操作API,在Auth服务进行API级别的操作权限鉴定。API级别的操作权限校验需要的上下文信息需要结合业务,与客户端进行商定,应该在token能取到相应信息,传递给Auth服务,不过应尽量减少在header取上下文校验的信息。

笔者将本次开发Auth系统所涉及的大部分代码及源码进行了解析,至于没有讲到的一些内容和细节,读者可以自行扩展。

5. 不足与后续工作

5.1 存在的不足

  • API级别操作权限校验的通用性

    (1). 对于API级别操作权限校验,需要在网关处调用时构造相应的上下文信息。上下文信息基本依赖于 token中的payload,如果信息太多引起token太长,导致每次客户端的请求头部长度变长。

    (2). 并不是所有的操作接口都能覆盖到,这个问题是比较严重的,根据上下文集合很可能出现好多接口 的权限没法鉴定,最后的结果就是API级别操作权限校验失败的是绝对没有权限访问该接口,而通过不一定能访问,因为该接口涉及到的上下文根本没法完全得到。我们的项目在现阶段,定义的最小上下文集合能勉强覆盖到,但是对于后面扩增的服务接口真的是不乐观。

    (3). 每个服务的每个接口都在Auth服务注册其所需要的权限,太过麻烦,Auth服务需要额外维护这样的信息。

  • 网关处调用Auth服务带来的系统吞吐量瓶颈

    (1). 这个其实很容易理解,Auth服务作为公共的基础服务,大多数服务接口都会需要鉴权,Auth服务需要经过复杂。

    (2). 网关调用Auth服务,阻塞调用,只有等Auth服务返回校验结果,才会做进一步处理。虽说Auth服务可以多实例部署,但是并发量大了之后,其瓶颈明显可见,严重可能会造成整个系统的不可用。

5.2 后续工作

  • 从整个系统设计角度来讲,API级别操作权限后期将会分散在各个服务的接口上,由各个接口负责其所需要的权限、身份等。Spring Security对于接口级别的权限校验也是支持的,之所以采用这样的做法,也是为了兼容新服务和遗留的服务,主要是针对遗留服务,新的服务采用的是分散在各个接口之上。
  • 将API级别操作权限分散到各个服务接口之后,相应的能提升Auth服务的响应。网关能够及时的对请求进行转发或者拒绝。
  • API级别操作权限所需要的上下文信息对各个接口真的设计的很复杂,这边我们确实花了时间,同时管理移动服务的好几百操作接口所对应的权限,非常烦。!

本文的源码地址:
GitHub:https://github.com/keets2012/Auth-service
码云: https://gitee.com/keets/Auth-Service


参考

    1. 配置表单登录
    2. Spring Security3源码分析-FilterSecurityInterceptor分析
    3. Core Security Filters
    4. Spring Security(四)–核心过滤器源码分析

来源:http://blueskykong.com/2017/10/26/security4/

认证鉴权与API权限控制在微服务架构中的设计与实现(四)的更多相关文章

  1. 微服务架构中API网关的角色

    [上海尚学堂的话]:本文主要讲述了Mashape的首席技术执行官Palladino对API网关的详细介绍,以及API网关在微服务中所起的作用,同时介绍了Mashape的一款开源API网关Kong. A ...

  2. 使用微服务架构思想,设计部署OAuth2.0授权认证框架

    1,授权认证与微服务架构 1.1,由不同团队合作引发的授权认证问题 去年的时候,公司开发一款新产品,但人手不够,将B/S系统的Web开发外包,外包团队使用Vue.js框架,调用我们的WebAPI,但是 ...

  3. 微服务架构下的API网关

    顾名思义,是出现在系统边界上的一个面向API的.串行集中式的强管控服务,这里的边界是企业IT系统的边界,主要起到隔离外部访问与内部系统的作用.在微服务概念的流行之前,API网关的实体就已经诞生了,例如 ...

  4. 微服务架构之「 API网关 」

    在微服务架构的系列文章中,前面已经通过文章<架构设计之「服务注册 」>介绍过了服务注册的原理和应用,今天这篇文章我们来聊一聊「 API网关 」. 「 API网关 」是任何微服务架构的重要组 ...

  5. 微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍

    微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍 一.为什么会有 API Gateway 网关 随着微服务架构的流行,很多公司把原有的单 ...

  6. Spring Security 接口认证鉴权入门实践指南

    目录 前言 SpringBoot 示例 SpringBoot pom.xml SpringBoot application.yml SpringBoot IndexController SpringB ...

  7. 基于Springboot集成security、oauth2实现认证鉴权、资源管理

    1.Oauth2简介 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAu ...

  8. API权限控制与安全管理

     摘自网上 一.API权限控制范围 1.首先验证web端请求参数: (1)web请求参数:渠道.ServiceName.版本.Airline.时间戳(yyyyMMddhhmmssSSS).reqXML ...

  9. spring cloud+dotnet core搭建微服务架构:Api授权认证(六)

    前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...

随机推荐

  1. [转] React 中组件间通信的几种方式

    在使用 React 的过程中,不可避免的需要组件间进行消息传递(通信),组件间通信大体有下面几种情况: 父组件向子组件通信 子组件向父组件通信 跨级组件之间通信 非嵌套组件间通信 下面依次说下这几种通 ...

  2. [转] meta标签的作用及整理

    meta的标签的使用是我在前端学习中曾经困惑过一段时间的问题.一方面不是很了解meta标签的用途,另一方面是对于meta标签里的属性和值不是懂,也不知道从哪里冒出来的,所以这篇文章专门整理下meta标 ...

  3. jquery .On()绑定事件的触发机制

    选择器只能选择已存在元素,其他元素需要作为参数传递给on

  4. Summary of continuous function spaces

    In general differential calculus, we have learned the definitions of function continuity, such as fu ...

  5. 2018牛客网暑假ACM多校训练赛(第五场)H subseq 树状数组

    原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round5-H.html 题目传送门 - https://www.no ...

  6. JavaSE| 集合

    集合 l  Collection 层次结构中的根接口.Collection 表示一组对象,这些对象也称为 collection 的元素.一些 collection 允许有重复的元素,而另一些则不允许. ...

  7. 使用 PySide2 开发 Maya 插件系列一:QT Designer 设计GUI, pyside-uic 把 .ui 文件转为 .py 文件

    使用 PySide2 开发 Maya 插件系列一:QT Designer 设计GUI, pyside-uic 把 .ui 文件转为 .py 文件 前期准备: 安装 python:https://www ...

  8. 关于mybatis缓存配置讲解

    一级缓存: 一级缓存是默认的. 测试:在WEB页面同一个查询执行两次从日志里面看同样的sql查询执行两次. 2次sql查询,看似我们使用了同一个sqlSession,但是实际上因为我们的dao继承了S ...

  9. CentOS7 Windows双系统 修复引导

    伪前提:先装Windows再装CentOS7(伪前提是因为没试过先装CentOS再装Windows) Windows用U盘安装CentOS7后,开启启动项里面仅有CentOS7的启动项,要修复Wind ...

  10. 【Spring Boot】使用JDBC 获取相关的数据

    使用JDBC 获取相关的数据 什么是JDBC Java Database Connectivity 是一种用于执行SQL语句的Java API,与数据库建立连接.发送 操作数据库的语句并处理结果. S ...