微服务网关与用户身份识别,JWT+Spring Security进行网关安全认证

编程入门 行业动态 更新时间:2024-10-18 18:22:13

微服务<a href=https://www.elefans.com/category/jswz/34/1769301.html style=网关与用户身份识别,JWT+Spring Security进行网关安全认证"/>

微服务网关与用户身份识别,JWT+Spring Security进行网关安全认证

JWT+Spring Security进行网关安全认证

JWT和Spring Security相结合进行系统安全认证是目前使用比较多的一种安全认证组合。疯狂创客圈crazy-springcloud微服务开发脚手架使用JWT身份令牌结合Spring Security的安全认证机制完成用户请求的安全权限认证。整个用户认证的过程大致如下:

(1)前台(如网页富客户端)通过REST接口将用户名和密码发送到UAA用户账号与认证微服务进行登录。

(2)UAA服务在完成登录流程后,将Session ID作为JWT的负载(payload),生成JWT身份令牌后发送给前台。

(3)前台可以将JWT令牌存到localStorage或者sessionStorage中,当然,退出登录时,前端必须删除保存的JWT令牌。

(4)前台每次在请求微服务提供者的REST资源时,将JWT令牌放到请求头中。crazy-springcloud脚手架做了管理端和用户端的前台区分,管理端前台的令牌头为Authorization,用户端前台的令牌头为token。

(5)在请求到达Zuul网关时,Zuul会结合Spring Security进行拦截,从而验证JWT的有效性。

(6)Zuul验证通过后才可以访问微服务所提供的REST资源。

需要说明的是,在crazy-springcloud微服务开发脚手架中,Provider微服务提供者自身不需要进行单独的安全认证,Provider之间的内部远程调用也是不需要安全认证的,安全认证全部由网关负责。严格来说,这套安全机制是能够满足一般的生产场景安全认证要求的。如果觉得这个安全级别不是太高,单个的Provider微服务也需要进行独立的安全认证,那么实现起来也是很容易的,只需要导入公共的安全认证模块base-auth即可。实际上早期的crazy-springcloud脚手架也是这样做的,后期发现这样做纯属多虑,而且大大降低了Provider服务提供者模块的可复用性和可移植性(这是微服务架构的巨大优势之一)。所以,crazy-springcloud后来将整体架构调整为由网关(如Zuul或者Nginx)负责安全认证,去掉了Provider服务提供者的安全认证能力。

JWT安全令牌规范详解

JWT(JSON Web Token)是一种用户凭证的编码规范,是一种网络环境下编码用户凭证的JSON格式的开放标准(RFC 7519)。JWT令牌的格式被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)、用户身份认证等场景。

一个编码之后的JWT令牌字符串分为三部分:header+payload+signature。这三部分通过点号“.”连接,第一部分常被称为头部(header),第二部分常被称为负载(payload),第三部分常被称为签名(signature)。

1.JWT的header

编码之前的JWT的header部分采用JSON格式,一个完整的头部就像如下的JSON内容:

{"typ":"JWT","alg":"HS256"
}

其中,"typ"是type(类型)的简写,值为"JWT"代表JWT类型;"alg"是加密算法的简写,值为"HS256"代表加密方式为HS256。

采用JWT令牌编码时,header的JSON字符串将进行Base64编码,编码之后的字符串构成了JWT令牌的第一部分。

2.JWT的playload

编码之前的JWT的playload部分也是采用JSON格式,playload是存放有效信息的部分,一个简单的playload就像如下的JSON内容:

{"sub":"session id","exp":1579315717,"iat":1578451717
}

采用JWT令牌编码时,playload的JSON字符串将进行Base64编码,编码之后的字符串构成了JWT令牌的第二部分。

3.JWT的signature

JWT的第三部分是一个签名字符串,这一部分是将header的Base64编码和payload的Base64编码使用点号(.)连接起来之后,通过header声明的加密算法进行加密所得到的密文。为了保证安全,加密时需要加入盐(salt)。

下面是一个演示用例:用Java代码生成JWT令牌,然后对令牌的header部分字符串和payload部分字符串进行Base64解码,并输出解码后的JSON。

package com.crazymaker.demo.auth;
//省略import
@Slf4j
public class JwtDemo
{@Testpublic void testBaseJWT(){try{/***JWT的演示内容*/String subject = "session id";/***签名的加密盐*/String salt = "user password";/***签名的加密算法*/Algorithm algorithm = Algorithm.HMAC256(salt);//签发时间long start = System.currentTimeMillis() - 60000;//过期时间,在签发时间的基础上加上一个有效时长Date end = new Date(start + SessionConstants.SESSION_TIME_OUT *1000);/***获取编码后的JWT令牌*/String token = JWT.create().withSubject(subject).withIssuedAt(new Date(start)).withExpiresAt(end).sign(algorithm);log.info("token=" + token);//编码后输出demo为://token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzZXNza
W9uIGlkIiwiZXhwIjoxNTc5MzE1NzE3LCJpYXQiOjE1Nzg0NTE3MTd9.iANh9Fa0B_6H5TQ11bLCWcEpmWxuCwa2Rt6rnzBWteI//以.分隔令牌String[] parts = token.split("\\." );/***对第一部分和第二部分进行解码*解码后的第一部分:header*/String headerJson =
StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));log.info("parts[0]=" + headerJson);//解码后的第一部分输出的示例为: //parts[0]={"typ":"JWT","alg":"HS256"}/***解码后的第二部分:payload*/String payloadJson;payloadJson = StringUtils.newStringUtf8
(Base64.decodeBase64(parts[1]));log.info("parts[1]=" + payloadJson);//输出的示例为://解码后的第二部分:parts[1]={"sub":"session id","exp":1579315535,"iat":
1578451535}} catch (Exception e){e.printStackTrace();}}...
}

在编码前的JWT中,payload部分JSON中的属性被称为JWT的声明。JWT的声明分为两类:

(1)公有的声明(如iat)。

(2)私有的声明(自定义的JSON属性)。

公有的声明也就是JWT标准中注册的声明,主要为以下JSON属性:

(1)iss:签发人。

(2)sub:主题。

(3)aud:用户。

(4)iat:JWT的签发时间。

(5)exp:JWT的过期时间,这个过期时间必须要大于签发时间。

(6)nbf:定义在什么时间之前该JWT是不可用的。

私有的声明是除了公有声明之外的自定义JSON字段,私有的声明可以添加任何信息,一般添加用户的相关信息或其他业务需要的必要信息。下面的JSON例子中的uid、user_name、nick_name等都是私有声明。

{"uid": "123...","sub": "session id","user_name": "admin","nick_name": "管理员","exp": 1579317358,"iat": 1578453358
}

下面是一个向JWT令牌添加私有声明的实例,代码如下:

package com.crazymaker.demo.auth;
//省略import
@Slf4j
public class JwtDemo
{/***测试私有声明*/@Testpublic void testJWTWithClaim(){try{String subject = "session id";String salt = "user password";/***签名的加密算法*/Algorithm algorithm = Algorithm.HMAC256(salt);//签发时间long start = System.currentTimeMillis() - 60000;//过期时间,在签发时间的基础上加上一个有效时长Date end = new Date(start + SessionConstants.SESSION_TIME_OUT *1000);/***JWT建造者*/JWTCreator.Builder builder = JWT.create();/***增加私有声明*/builder.withClaim("uid", "123...");builder.withClaim("user_name", "admin");builder.withClaim("nick_name","管理员");/***获取编码后的JWT令牌*/String token =builder.withSubject(subject).withIssuedAt(new Date(start)).withExpiresAt(end).sign(algorithm);log.info("token=" + token);//以.分隔,这里需要转义String[] parts = token.split("\\." );String payloadJson;/***解码payload*/payloadJson = StringUtils.newStringUtf8
(Base64.decodeBase64(parts[1]));log.info("parts[1]=" + payloadJson);//输出demo为:parts[1]=//{"uid":"123...","sub":"session id","user_name":"admin",
"nick_name":"管理员","exp":1579317358,"iat":1578453358}} catch (Exception e){e.printStackTrace();}}
}

由于JWT的payload声明(JSON属性)是可以解码的,属于明文信息,因此不建议添加敏感信息。

 JWT+Spring Security认证处理流程

实际开发中如何使用JWT进行用户认证呢?疯狂创客圈的crazy-springcloud开发脚手架将JWT令牌和Spring Security相结合,设计了一个公共的、比较方便复用的用户认证模块base-auth。一般来说,在Zuul网关或者微服务提供者进行用户认证时导入这个公共的base-auth模块即可。

这里还是按照6.4.2节中请求认证处理流程的5个步骤介绍base-auth模块中JWT令牌的认证处理流程。

首先看第一步:定制一个凭证/令牌类,封装用户信息和JWT认证信息。

package com.crazymaker.springcloud.base.security.token;
//省略import
public class JwtAuthenticationToken extends AbstractAuthenticationToken
{private static final long serialVersionUID = 3981518947978158945L;//封装用户信息:用户id、密码private UserDetails userDetails;//封装的JWT认证信息private DecodedJWT decodedJWT;...
}

再看第二步:定制一个认证提供者类和凭证/令牌类进行配套,并完成对自制凭证/令牌实例的验证。

package com.crazymaker.springcloud.base.security.provider;
//省略import
public class JwtAuthenticationProvider implements AuthenticationProvider
{//用于通过session id查找用户信息private RedisOperationsSessionRepository sessionRepository;public JwtAuthenticationProvider(RedisOperationsSessionRepository sessionRepository){this.sessionRepository = sessionRepository;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException{//判断JWT令牌是否过期JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) authentication;DecodedJWT jwt =jwtToken.getDecodedJWT();if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())){throw new NonceExpiredException("认证过期");}//取得session idString sid = jwt.getSubject();//取得令牌字符串,此变量将用于验证是否重复登录String newToken = jwt.getToken();//获取sessionSession session = null;try{session = sessionRepository.findById(sid);} catch (Exception e){e.printStackTrace();}if (null == session){throw new NonceExpiredException("还没有登录,请登录系统!");}String json = session.getAttribute(G_USER);if (StringUtils.isBlank(json)){throw new NonceExpiredException("认证有误,请重新登录");}//取得session中的用户信息UserDTO userDTO = JsonUtil.jsonToPojo(json, UserDTO.class);if (null == userDTO){throw new NonceExpiredException("认证有误,请重新登录");}
判断是否在其他地方已经登录 //判断是否在其他地方已经登录if (null == newToken || !newToken.equals(userDTO.getToken())){throw new NonceExpiredException("您已经在其他的地方登录!");}String userID = null;if (null == userDTO.getUserId()){userID = String.valueOf(userDTO.getId());} else{userID = String.valueOf(userDTO.getUserId());}UserDetails userDetails = User.builder().username(userID).password(userDTO.getPassword()).authorities(SessionConstants.USER_INFO).build();try{//用户密码的密文作为JWT的加密盐String encryptSalt = userDTO.getPassword();Algorithm algorithm = Algorithm.HMAC256(encryptSalt);//创建验证器JWTVerifier verifier = JWT.require(algorithm).withSubject(sid).build();//进行JWTtoken验证verifier.verify(newToken);} catch (Exception e){throw new BadCredentialsException("认证有误:令牌校验失败,请重新登录", e);}//返回认证通过的token,包含用户信息,如user id等JwtAuthenticationToken passedToken =new JwtAuthenticationToken(userDetails, jwt, userDetails.getAuthorities());passedToken.setAuthenticated(true);return passedToken;}//支持自定义的令牌JwtAuthenticationToken@Overridepublic boolean supports(Class<?> authentication){return authentication.isAssignableFrom(JwtAuthenticationToken.class);}
}

JwtAuthenticationProvider负责对传入的JwtAuthenticationToken凭证/令牌实例进行多方面的验证:(1)验证解码后的DecodedJWT实例是否过期;(2)由于本演示中JWT的subject(主题)信息存放的是用户的Session ID,因此还要判断会话是否存在;(3)使用会话中的用户密码作为盐,对JWT令牌进行安全性校验。

如果以上验证都顺利通过,就构建一个新的JwtAuthenticationToken令牌,将重要的用户信息(UserID)放入令牌并予以返回,供后续操作使用。

第三步:定制一个过滤器类,从请求中获取用户信息组装成JwtAuthenticationToken凭证/令牌,交给认证管理者。在crazy-springcloud脚手架中,前台有用户端和管理端的两套界面,所以,将认证头部信息区分成管理端和用户端两类:管理端的头部字段为Authorization;用户端的认证信息头部字段为token。

过滤器从请求中获取认证的头部字段,解析之后组装成JwtAuthenticationToken令牌实例,提交给AuthenticationManager进行验证。

package com.crazymaker.springcloud.base.security.filter;
//省略import
public class JwtAuthenticationFilter extends OncePerRequestFilter
{...@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws
{
...Authentication passedToken = null;AuthenticationException failed = null;//从HTTP请求取得JWT令牌的头部字段 String token = null;//用户端存放的JWT的HTTP头部字段为tokenString sessionIDStore = SessionHolder.getSessionIDStore();if (sessionIDStore.equals(SessionConstants.SESSION_STORE)){token = request.getHeader(SessionConstants.AUTHORIZATION_HEAD);}//管理端存放的JWT的HTTP头部字段为Authorizationelse if (sessionIDStore.equals
(SessionConstants.ADMIN_SESSION_STORE)){token = request.getHeader
(SessionConstants.ADMIN_AUTHORIZATION_HEAD);}//没有取得头部,报异常else{failed = new InsufficientAuthenticationException("请求头认证消息为空" );unsuccessfulAuthentication(request, response, failed);return;}token = StringUtils.removeStart(token, "Bearer " );try{if (StringUtils.isNotBlank(token)){//组装令牌JwtAuthenticationToken authToken = new JwtAuthenticationToken(JWT.decode(token));//提交给AuthenticationManager进行令牌验证,获取认证后的令牌passedToken = this.getAuthenticationManager()
.authenticate(authToken);//取得认证后的用户信息,主要是用户idUserDetails details = (UserDetails) passedToken.getDetails();//通过details.getUsername()获取用户id,并作为请求属性进行缓存request.setAttribute(SessionConstants.USER_IDENTIFIER, details.getUsername());} else{failed = new InsufficientAuthenticationException("请求头认证消息为空" );}} catch (JWTDecodeException e){...}...filterChain.doFilter(request, response);}...
}

AuthenticationManager将调用注册在内部的JwtAuthenticationProvider认证提供者,对JwtAuthenticationToken进行验证。

为了使得过滤器能够生效,必须将过滤器加入HTTP请求的过滤处理责任链,这一步可以通过实现一个AbstractHttpConfigurer配置类来完成。

第四步:定制一个HTTP的安全认证配置类(AbstractHttpConfigurer子类),将上一步定制的过滤器加入请求的过滤处理责任链。

package com.crazymaker.springcloud.base.security.configurer;
...
public class JwtAuthConfigurer<T extends JwtAuthConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigu
{private JwtAuthenticationFilter jwtAuthenticationFilter;public JwtAuthConfigurer(){//创建认证过滤器this.jwtAuthenticationFilter = new JwtAuthenticationFilter();}//将过滤器加入http过滤处理责任链@Overridepublic void configure(B http) throws Exception{//获取Spring Security共享的AuthenticationManager实例//将其设置到jwtAuthenticationFilter认证过滤器 jwtAuthenticationFilter.setAuthenticationManager(http.getSharedObjectjwtAuthenticationFilter.setAuthenticationFailureHandler(new AuthFailureHandler());JwtAuthenticationFilter filter = postProcess(jwtAuthenticationFilter);//将过滤器加入http过滤处理责任链http.addFilterBefore(filter, LogoutFilter.class);}...
}

第五步:定义一个Spring Security安全配置类(
WebSecurityConfigurerAdapter子类),对Web容器的HTTP安全认证机制进行配置。这是最后一步,有两项工作:一是在HTTP安全策略上应用JwtAuthConfigurer配置实例;二是构造AuthenticationManagerBuilder认证管理者实例。这一步可以通过继承WebSecurityConfigurerAdapter适配器来完成。

package com.crazymaker.springcloud.cloud.center.zuul.config;
...
@ConditionalOnWebApplication
@EnableWebSecurity()
public class ZuulWebSecurityConfig extends WebSecurityConfigurerAdapter
{//注入session存储实例,用于查找session(根据session id)@ResourceRedisOperationsSessionRepository sessionRepository;//配置HTTP请求的安全策略,应用DemoAuthConfigurer配置类实例@Overrideprotected void configure(HttpSecurity http) throws Exception{http.csrf().disable()....authorizeRequests().and().authorizeRequests().anyRequest().authenticated().and().formLogin().disable().sessionManagement().disable().cors().and()//在HTTP安全策略上应用JwtAuthConfigurer配置类实例.apply(new JwtAuthConfigurer<>()) .tokenValidSuccessHandler(jwtRefreshSuccessHandler()).permissi.and().logout().disable().sessionManagement().disable();}//配置认证Builder,由其负责构造AuthenticationManager实例//Builder所构造的AuthenticationManager实例将作为HTTP请求的共享对象//可以通过http.getSharedObject(AuthenticationManager.class)来获取@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{//在Builder实例中加入自定义的Provider认证提供者实例auth.authenticationProvider(jwtAuthenticationProvider());}//创建一个JwtAuthenticationProvider提供者实例@DependsOn({"sessionRepository"})@Bean("jwtAuthenticationProvider")protected AuthenticationProvider jwtAuthenticationProvider(){return new JwtAuthenticationProvider(sessionRepository);}...
}

至此,一个基于JWT+Spring Security的用户认证处理流程就定义完了。但是,此流程仅仅涉及JWT令牌的认证,没有涉及JWT令牌的生成。一般来说,JWT令牌的生成需要由系统的UAA(用户账号与认证)服务(或者模块)负责完成。

Zuul网关与UAA微服务的配合

crazy-springcloud脚手架通过Zuul网关和UAA微服务相互结合来完成整个用户的登录与认证闭环流程。二者的关系大致为:

(1)登录时,UAA微服务负责用户名称和密码的验证并且将用户信息(包括令牌加密盐)放在分布式Session中,然后返回JWT令牌(含Session ID)给前台。

(2)认证时,前台请求带上JWT令牌,Zuul网关能根据令牌中的Session ID取出分布式Session中的加密盐,对JWT令牌进行验证。在crazy-springcloud脚手架的会话架构中,Zuul网关必须能和UAA微服务进行会话的共享,如图6-7所示。

图6-7 Zuul网关和UAA微服务进行会话的共享

在crazy-springcloud的UAA微服务提供者crazymaker-uaa实现模块中,controller(控制层)的REST登录接口的定义如下:

@Api(value = "用户端登录与退出", tags = {"用户信息、基础学习DEMO"})
@RestController
@RequestMapping("/api/session" )
public class SessionController
{//用户端会话服务@Resourceprivate FrontUserEndSessionServiceImpl userService;//用户端的登录REST接口@PostMapping("/login/v1" )@ApiOperation(value = "用户端登录" )public RestOut<LoginOutDTO> login(@RequestBody LoginInfoDTO loginInfoDTO, HttpServlet{//调用服务层登录方法获取令牌LoginOutDTO dto = userService.login(loginInfoDTO);response.setHeader("Content-Type", "text/html;charset=utf-8" );response.setHeader(SessionConstants.AUTHORIZATION_HEAD, dto.getToken());return RestOut.success(dto);}...
}

用户登录时,在服务层,客户端会话服务
FrontUserEndSessionServiceImpl负责从用户数据库中获取用户,然后进行密码验证。

package com.crazymaker.springcloud.user.info.service.impl;
//省略import
@Slf4j
@Service
public class FrontUserEndSessionServiceImpl
{//Dao Bean,用于查询数据库用户@ResourceUserDao userDao;//加密器@Resourceprivate PasswordEncoder passwordEncoder;//缓存操作服务@ResourceRedisRepository redisRepository;//Redis会话存储服务@Resourceprivate RedisOperationsSessionRepository sessionRepository;/***登录处理*@param dto用户名、密码*@return登录成功的dto*/public LoginOutDTO login(LoginInfoDTO dto){String username = dto.getUsername();//从数据库获取用户List<UserPO> list = userDao.findAllByUsername(username);if (null == list || list.size() <= 0){throw BusinessException.builder().errMsg("用户名或者密码错误" );}UserPO userPO = list.get(0);//进行密码的验证//String encode = passwordEncoder.encode(dto.getPassword());String encoded = userPO.getPassword();String raw = dto.getPassword();boolean matched = passwordEncoder.matches(raw, encoded);if (!matched){throw BusinessException.builder().errMsg("用户名或者密码错误" );}//设置session,方便Spring Security进行权限验证return setSession(userPO);}/***1:将userid -> session id作为键-值对(Key-Value Pair)缓存起来,防止频繁创建session*2:将用户信息保存到分布式Session*3:创建JWT token,提供给Spring Security进行权限验证*@param userPO用户信息*@return登录的输出信息*/private LoginOutDTO setSession(UserPO userPO){if (null == userPO){throw BusinessException.builder().errMsg("用户不存在或者密码错误" ).build();}/***根据用户id查询之前保存的session id*防止频繁登录的时候session被大量创建*/String uid = String.valueOf(userPO.getUserId());String sid = redisRepository.getSessionId(uid);Session session = null;try{/***查找现有的session*/session = sessionRepository.findById(sid);} catch (Exception e){//e.printStackTrace();log.info("查找现有的session失败,将创建一个新的session" );}if (null == session){session = sessionRepository.createSession();//新的session id和用户id一起作为键-值对进行保存//用户访问的时候可以根据用户id查找session idsid = session.getId();redisRepository.setSessionId(uid, sid);}String salt = userPO.getPassword();
构建 //构建JWT tokenString token = AuthUtils.buildToken(sid, salt);/***将用户信息缓存到分布式Session*/UserDTO cacheDto = new UserDTO();BeanUtils.copyProperties(userPO, cacheDto);cacheDto.setToken(token);session.setAttribute(G_USER, JsonUtil.pojoToJson(cacheDto));LoginOutDTO outDTO = new LoginOutDTO();BeanUtils.copyProperties(cacheDto, outDTO);return outDTO;}
}

如果用户验证通过,那么前端会话服务
FrontUserEndSessionServiceImpl在setSession方法中创建Redis分布式Session(如果不存在旧Session),然后将用户信息(密码为令牌的salt)缓存起来。如果用户存在旧Session,那么旧Session的ID将通过用户的uid查找到,然后通过sessionRepository找到旧Session,做到在频繁登录的场景下不会导致Session被大量创建。

最终,uaa-provider微服务将返回JWT令牌(subject设置为Session ID)给前台。由于Zuul网关和uaa-provider微服务共享分布式Session,在进行请求认证时,Zuul网关能通过JWT令牌中的Session ID取出分布式Session中的用户信息和加密盐,对JWT令牌进行验证。

使用Zuul过滤器添加代理请求的用户标识

完成用户认证后,Zuul网关的代理请求将转发给上游的微服务Provider实例。此时,代理请求仍然需要带上用户的身份标识,而此时身份标识不一定是Session ID,而是和上游的Provider强相关:

(1)如果Provider是将JWT令牌作为用户身份标识(和Zuul一

样),那么Zuul网关将JWT令牌传给Provider微服务提供者。(2)如果Provider是将Session ID作为用户身份标识,那么Zuul需要将JWT令牌的subject中的Session ID解析出来,然后传给Provider微服务提供者。

(3)如果Provider是将用户ID作为用户身份标识,那么Zuul既不能将JWT令牌传给Provider,又不能将Session ID传给Provider,而是要将会话中缓存的用户ID传给Provider。

前两种用户身份标识的传递方案都要求Provider微服务和网关共享会话,而实际场景中,这种可能性不是100%。另外,负责安全认证的网关可能不是Zuul,而是性能更高的OpenResty(甚至是Kong),如果这样,共享Session技术难度就会更大。总之,为了使程序的可扩展性和可移植性更好,建议使用第三种用户身份标识的代理传递方案。

crazy-springcloud脚手架采用的是第三种用户标识传递方案。

JWT令牌被验证成功后,网关的代理请求被加上"USER-ID"头,将用户ID作为用户身份标识添加到请求头部,传递给上游Provider。这个功能使用了一个Zuul过滤器实现,代码如下:

package com.crazymaker.springcloud.cloud.center.zuul.filter;
//省略import@Component
@Slf4j
public class ModifyRequestHeaderFilter extends ZuulFilter
{/***根据条件判断是否需要路由,是否需要执行该过滤器*/@Overridepublic boolean shouldFilter(){RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();/***存在用户端认证token*/String token = request.getHeader(SessionConstants
.AUTHORIZATION_HEAD);if (!StringUtils.isEmpty(token)){return true;}/***存在管理端认证token*/token = request.getHeader(SessionConstants
.ADMIN_AUTHORIZATION_HEAD);if (!StringUtils.isEmpty(token)){return true;}return false;}/***调用上游微服务之前修改请求头,加上USER-ID头**@return*@throws ZuulException*/@Overridepublic Object run() throws ZuulException{RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();//认证成功,请求的"USER-ID"(USER_IDENTIFIER)属性被设置String identifier = (String) request.getAttribute
(SessionConstants.USER_IDENTIFIER);//代理请求加上 "USER-ID" 头if (StringUtils.isNotBlank(identifier)){ctx.addZuulRequestHeader(SessionConstants.USER_IDENTIFIER, identifier);}return null;}@Overridepublic String filterType(){return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder(){return 1;}
}

更多推荐

微服务网关与用户身份识别,JWT+Spring Security进行网关安全认证

本文发布于:2024-02-07 02:23:31,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1752566.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:网关   安全认证   身份   用户   JWT

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!