[译] JWT 与 Spring Cloud 微服务

news/2024/7/3 0:16:23

keyholesoftware.com/2016/06/20/…
作者:THOMAS KENDALL
译者:oopsguy.com

微服务安全是架构的一个重要部分。具体来说,就是认证和授权模式。

微服务认证和授权处理方式有几种选择,但本文只介绍 JSON Web Token 的使用。

JSON Web Token

JSON Web Token(JWT)本质上是一个独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以及您可以存储任何其他信息。任何人都可以轻松读取和解析,并使用密钥来验证真实性。有关 JSON Web Token 的简要介绍,请查看此页面。

微服务使用 JSON Web Token 的一个优点是,我们可以配置它以便包含用户拥有的任何权限。这意味着每个服务不需要与授权服务交互才能授权用户。

JWT 的另一个优点是它们是可序列化的,足够短的长度使得它可以放置在请求头中。

工作原理

JWT 的工作流程相当简单。第一次请求是一个带有用户名和密码的无身份验证端点的 POST。

认证成功后,响应将包含 JWT。之后所有的请求都附带一个 HTTP 头,其包含了 JWT 令牌:Authorization: xxxxx.yyyyy.zzzzz

任何服务间的请求都要通过该头,以便其他服务可以应用授权。

开始编码

我们需要做的第一件事是弄清楚如何生成 JWT。幸运的是,我们不是第一个踩坑的人,有几个现成的类库可供我们选择。

我选择了 Java JWT。这是我的实现:

public class JsonWebTokenUtility {private SignatureAlgorithm signatureAlgorithm;private Key secretKey;public JsonWebTokenUtility() {// 这不是一个安全的实践// 为了简化,我存储了一个静态的 key 在这里// 实际上,在微服务环境中,key 是由配置服务器持有的signatureAlgorithm = SignatureAlgorithm.HS512;String encodedKey = "L7A/6zARSkK1j7Vd5SDD9pSSqZlqF7mAhiOgRbgv9Smce6tf4cJnvKOjtKPxNNnWQj+2lQEScm3XIUjhW+YVZg==";secretKey = deserializeKey(encodedKey);}public String createJsonWebToken(AuthTokenDetailsDTO authTokenDetailsDTO) {String token = Jwts.builder().setSubject(authTokenDetailsDTO.userId).claim("email", authTokenDetailsDTO.email).claim("roles", authTokenDetailsDTO.roleNames).setExpiration(authTokenDetailsDTO.expirationDate).signWith(getSignatureAlgorithm(), getSecretKey()).compact();return token;}private Key deserializeKey(String encodedKey) {byte[] decodedKey = Base64.getDecoder().decode(encodedKey);Key key = new SecretKeySpec(decodedKey, getSignatureAlgorithm().getJcaName());return key;}private Key getSecretKey() {return secretKey;}public SignatureAlgorithm getSignatureAlgorithm() {return signatureAlgorithm;}public AuthTokenDetailsDTO parseAndValidate(String token) {AuthTokenDetailsDTO authTokenDetailsDTO = null;try {Claims claims = Jwts.parser().setSigningKey(getSecretKey()).parseClaimsJws(token).getBody();String userId = claims.getSubject();String email = (String) claims.get("email");List roleNames = (List) claims.get("roles");Date expirationDate = claims.getExpiration();authTokenDetailsDTO = new AuthTokenDetailsDTO();authTokenDetailsDTO.userId = userId;authTokenDetailsDTO.email = email;authTokenDetailsDTO.roleNames = roleNames;authTokenDetailsDTO.expirationDate = expirationDate;} catch (JwtException ex) {System.out.println(ex);}return authTokenDetailsDTO;}private String serializeKey(Key key) {String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());return encodedKey;}}复制代码

现在我们有了这个工具类,之后需要在每个微服务中配置 Spring Security。

为此,我们需要自定义一个验证过滤器,如果存在请求头,则读取它。Spring 有一个认证过滤器 RequestHeaderAuthenticationFilter,我们可以继承它。

public class JsonWebTokenAuthenticationFilter extends RequestHeaderAuthenticationFilter {public JsonWebTokenAuthenticationFilter() {// Don't throw exceptions if the header is missingthis.setExceptionIfHeaderMissing(false);// This is the request header it will look forthis.setPrincipalRequestHeader("Authorization");}@Override@Autowiredpublic void setAuthenticationManager(AuthenticationManager authenticationManager) {super.setAuthenticationManager(authenticationManager);}
}复制代码

此时,头已经以 PreAuthenticatedAuthenticationToken 的形式转换为 Spring Authentication 对象。

我们现在需要一个 AuthenticationProvider 用于读取令牌,从而进行身份验证,并将其转换为我们自己自定义的 Authentication 对象。

public class JsonWebTokenAuthenticationProvider implements AuthenticationProvider {private JsonWebTokenUtility tokenService = new JsonWebTokenUtility();@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {Authentication authenticatedUser = null;// Only process the PreAuthenticatedAuthenticationTokenif (authentication.getClass().isAssignableFrom(PreAuthenticatedAuthenticationToken.class)&& authentication.getPrincipal() != null) {String tokenHeader = (String) authentication.getPrincipal();UserDetails userDetails = parseToken(tokenHeader);if (userDetails != null) {authenticatedUser = new JsonWebTokenAuthentication(userDetails, tokenHeader);}} else {// It is already a JsonWebTokenAuthenticationauthenticatedUser = authentication;}return authenticatedUser;}private UserDetails parseToken(String tokenHeader) {UserDetails principal = null;AuthTokenDetailsDTO authTokenDetails = tokenService.parseAndValidate(tokenHeader);if (authTokenDetails != null) {List<GrantedAuthority> authorities = authTokenDetails.roleNames.stream().map(roleName -> new SimpleGrantedAuthority(roleName)).collect(Collectors.toList());principal = new User(authTokenDetails.email, "", authorities);}return principal;}@Overridepublic boolean supports(Class<?> authentication) {return authentication.isAssignableFrom(PreAuthenticatedAuthenticationToken.class)|| authentication.isAssignableFrom(JsonWebTokenAuthentication.class);}}复制代码

有了这些组件,我们可以在 Spring Security 中使用 JWT 了。 在进行服务间通信时,我们需要传递 JWT。

我使用了一个 Feign 客户端,把 JWT 作为参数。

@FeignClient("user-management-service")
public interface UserManagementServiceAPI {@RequestMapping(value = "/authenticate", method = RequestMethod.POST)AuthTokenDTO authenticateUser(@RequestBody AuthenticationDTO authenticationDTO);@RequestMapping(method = RequestMethod.POST, value = "/roles")RoleDTO createRole(@RequestHeader("Authorization") String authorizationToken, @RequestBody RoleDTO roleDTO);@RequestMapping(method = RequestMethod.POST, value = "/users")UserDTO createUser(@RequestHeader("Authorization") String authorizationToken, @RequestBody UserDTO userDTO);@RequestMapping(method = RequestMethod.DELETE, value = "/roles/{id}")void deleteRole(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);@RequestMapping(method = RequestMethod.DELETE, value = "/users/{id}")void deleteUser(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);@RequestMapping(method = RequestMethod.GET, value = "/roles")Collection<RoleDTO> findAllRoles(@RequestHeader("Authorization") String authorizationToken);@RequestMapping(method = RequestMethod.GET, value = "/users")Collection<UserDTO> findAllUsers(@RequestHeader("Authorization") String authorizationToken);@RequestMapping(method = RequestMethod.GET, value = "/roles/{id}", produces = "application/json", consumes = "application/json")RoleDTO findRoleById(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);@RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = "application/json", consumes = "application/json")UserDTO findUserById(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);@RequestMapping(method = RequestMethod.GET, value = "/users/{id}/roles")Collection<RoleDTO> findUserRoles(@RequestHeader("Authorization") String authorizationToken,@PathVariable("id") int id);@RequestMapping(method = RequestMethod.PUT, value = "/roles/{id}")void updateRole(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id,@RequestBody RoleDTO roleDTO);@RequestMapping(method = RequestMethod.PUT, value = "/users/{id}")void updateUser(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id,@RequestBody UserDTO userDTO);
}复制代码

为了传递 JWT,我在控制器的 Spring Security 中抓取它:

private String getAuthorizationToken() {String token = null;Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication != null && authentication.getClass().isAssignableFrom(JsonWebTokenAuthentication.class)) {JsonWebTokenAuthentication jwtAuthentication = (JsonWebTokenAuthentication) authentication;token = jwtAuthentication.getJsonWebToken();}return token;
}复制代码

JWT 可以很好地适应分布式微服务环境,并提供了大量功能。 如果您正想为下一个微服务项目设计一个安全架构,请考虑使用 JSON Web Token。


http://lihuaxi.xjx100.cn/news/249565.html

相关文章

mac 拷贝文件时报错 8060 解决方案

解决如下&#xff1a; 即某文件夹下出现多重子目录&#xff0c;级数很多&#xff0c;删除多余的子文件夹即可。 至于如何产生的&#xff0c;有人说是xcode升级导致&#xff0c;不过没有见证 。我的不属于这类情况的。 &#xff08;参见&#xff1a;http://macosx.com/forums/ma…

CVPR 2022 视频全景分割新 Benchmark:VIPSeg

关注公众号&#xff0c;发现CV技术之美今天向大家分享 CVPR 2022 论文『Large-scale Video Panoptic Segmentation in the Wild: A Benchmark』,介绍一个新的视频全景分割&#xff08;Video Panoptic Segmentation&#xff09;领域 Benchmark&#xff1a;VIPSeg。论文链接&…

使用CruiseControl.Net全面实现持续集成

持续集成想必大家很多人都听说过&#xff0c;甚至都实践过&#xff0c;最近我又一次亲历了一次持续集成&#xff0c;现将我的经验分享给大家。关于持续集成的理论在本文概不涉及&#xff0c;本文的主要目的是实战CruiseControl.Net&#xff0c;用它来全面实现持续集成。 在配置…

c库的rand/random随机数产生函数性能差?

有网文称c标准库的rand/random随机数产生函数性能极差。一直信以为真&#xff0c;但从没做过验证。最近因其他因缘&#xff0c;写了些代码专门验证rand/random的性能。结果大出意料&#xff0c;颠覆之前的成见。 结论如下&#xff1a; 1) rand/random性极佳。在64位机器上&…

EXCHANGE证书

证书&#xff1a; CA&#xff08;证书颁发机构&#xff09;和证书有什么区别&#xff1f; CA&#xff1a;是服务器中的一个服务&#xff0c;主要是用来为计算机&#xff08;用户&#xff09;来颁发证书&#xff0c;安装CA的服务器称为证书服务器&#xff0c; 证书&#xff1a;从…

Pascal's Triangle

帕斯卡三角形&#xff0c;主要考察vector的用法。 vector<vector<int> > generate(int numRows){vector<vector<int> > result;vector<int> tmp;result.clear();tmp.clear();int i,j;if(numRows 0)return result;else if(numRows 1){tmp.push_…

替换 RHEL5的yum源为CentOS5源,亲测线上系统可用

最近安装nagiospnp&#xff0c;各种依赖包啊。rrdtool肿么装的这么费劲&#xff0c;后来实在扛不住了&#xff0c;还是修改rhel的源吧&#xff0c;把yum源搞成centos的不就ok了&#xff01;哈哈。然后就从网上一顿猛搜&#xff0c;发现“Ayou”老师的文章很靠谱&#xff0c;很有…

#天天复制,今天写一个# 把文字转为图片

/*** 把文字转为图片* * param text* 要写的内容* throws IOException*/public static void textToImg(String text) throws IOException {int len text.length();int fontSize 1000;int width len * fontSize;Font font new Font("楷体", Font2D.NAT…