javascript
带有Spring Cloud Microservices的JSON Web令牌
在Keyhole,我們已經(jīng)發(fā)布了幾個(gè)有關(guān)微服務(wù)的博客 。 我們已經(jīng)討論了微服務(wù)環(huán)境中使用的架構(gòu)模式,例如服務(wù)發(fā)現(xiàn)和斷路器 。 我們甚至在平臺(tái)和工具上發(fā)布了博客,例如最近關(guān)于Service Fabric的博客 。
我們已經(jīng)介紹過的架構(gòu)的重要組成部分是圍繞微服務(wù)的安全性。 具體來說,是身份驗(yàn)證和授權(quán)模式。
在考慮微服務(wù)中的身份驗(yàn)證時(shí),有多種選擇,但是此博客將特別關(guān)注使用JSON Web令牌。
JSON Web令牌
本質(zhì)上,JSON Web令牌(JWT)是一個(gè)獨(dú)立的身份驗(yàn)證令牌,可以包含諸如用戶標(biāo)識(shí)符,用戶的角色和權(quán)限以及您可能希望存儲(chǔ)在其中的任何其他信息。 任何人都可以輕松讀取和解析它,并可以使用密鑰驗(yàn)證其真實(shí)性。 有關(guān)JSON Web令牌的簡要介紹,請(qǐng)查看此頁面 。
將JSON Web令牌與微服務(wù)一起使用的一個(gè)優(yōu)勢是,我們可以對(duì)其進(jìn)行設(shè)置,使其已經(jīng)包含用戶擁有的所有權(quán)限。 這意味著每個(gè)服務(wù)都無需伸手我們的授權(quán)服務(wù)即可對(duì)用戶進(jìn)行授權(quán)。
JWT的另一個(gè)優(yōu)點(diǎn)是它們是可序列化的,并且足夠小以適合請(qǐng)求標(biāo)頭。
怎么運(yùn)行的
工作流程非常簡單。 第一個(gè)請(qǐng)求是使用用戶名和密碼的POST到不受保護(hù)的身份驗(yàn)證端點(diǎn)。
成功認(rèn)證后,響應(yīng)將包含JWT。 所有其他請(qǐng)求都帶有一個(gè)HTTP標(biāo)頭,其中包含以Authorization: xxxxx.yyyyy.zzzzz形式的JWT令牌Authorization: xxxxx.yyyyy.zzzzz 。
任何服務(wù)到服務(wù)的請(qǐng)求都將傳遞此標(biāo)頭,以便任何服務(wù)都可以沿途應(yīng)用授權(quán)。
現(xiàn)在,到代碼!
我們需要做的第一件事是弄清楚如何生成這些JWT。 幸運(yùn)的是,我們不是第一個(gè)嘗試此操作的人,并且有多個(gè)庫可供選擇。
我選擇了Java JWT 。 這是我的實(shí)現(xiàn):
public class JsonWebTokenUtility {private SignatureAlgorithm signatureAlgorithm;private Key secretKey;public JsonWebTokenUtility() {// THIS IS NOT A SECURE PRACTICE!// For simplicity, we are storing a static key here.// Ideally, in a microservices environment, this key would kept on a// config server.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;}}現(xiàn)在我們有了這個(gè)實(shí)用程序類,我們需要在我們的每個(gè)微服務(wù)中設(shè)置Spring Security。
為此,我們將需要一個(gè)自定義身份驗(yàn)證過濾器,該過濾器將讀取請(qǐng)求標(biāo)頭(如果存在)。 Spring中已經(jīng)有一個(gè)身份驗(yàn)證過濾器,它已經(jīng)執(zhí)行了此操作,稱為RequestHeaderAuthenticationFilter ,我們可以對(duì)其進(jìn)行擴(kuò)展。
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);} }此時(shí),標(biāo)頭已以PreAuthenticatedAuthenticationToken的形式轉(zhuǎn)換為Spring Authentication對(duì)象。
現(xiàn)在,我們需要一個(gè)身份驗(yàn)證提供程序,它將讀取此令牌,對(duì)其進(jìn)行身份驗(yàn)證并將其轉(zhuǎn)換為我們自己的自定義Authentication對(duì)象。
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);}}有了這些組件,我們現(xiàn)在就可以連接標(biāo)準(zhǔn)的Spring Security以使用JWT。 進(jìn)行服務(wù)到服務(wù)的呼叫時(shí),我們將需要傳遞JWT。
我使用了Feign客戶端,將JWT作為參數(shù)傳遞。
@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非常適合于分布式微服務(wù)環(huán)境,并提供多種功能。 在為下一個(gè)微服務(wù)項(xiàng)目設(shè)計(jì)安全體系結(jié)構(gòu)時(shí),請(qǐng)考慮JSON Web令牌。
翻譯自: https://www.javacodegeeks.com/2016/06/json-web-tokens-spring-cloud-microservices.html
總結(jié)
以上是生活随笔為你收集整理的带有Spring Cloud Microservices的JSON Web令牌的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓单机游戏好玩的手游(安卓单机游戏好玩
- 下一篇: 使用Spring编写和使用SOAP We