This article mainly introduces the implementation of authentication based on the combination of client token and gateway API.
In this way, the request header or parameter of each request must carry a token, and all requests must pass through the gateway, which effectively hides the micro service.
At present, there are common ways to generate tokens, one is to generate tokens through MD5, and the other is JWT: base64 encoded information + signature.
If you select MD5 to generate a token, use redis as the cache for judgment, take the token as the key, the login information as the value, and set the key expiration time as the login aging.
Select JWT, set the expiration time and login information for authentication.
Let's start with the first:
Generate token in MD5 mode
public class TokenGeneratorUtils { public static String generateValue() { return generateValue(UUID.randomUUID().toString()); } private static final char[] HEX_CODE = "0123456789abcdef".toCharArray(); public static String toHexString(byte[] data) { if (data == null) { return null; } StringBuilder r = new StringBuilder(data.length * 2); for (byte b : data) { r.append(HEX_CODE[(b >> 4) & 0xF]); r.append(HEX_CODE[(b & 0xF)]); } return r.toString(); } public static String generateValue(String param) { try { MessageDigest algorithm = MessageDigest.getInstance("MD5"); algorithm.reset(); algorithm.update(param.getBytes()); byte[] messageDigest = algorithm.digest(); return toHexString(messageDigest); } catch (Exception e) { throw new RenException("token invalid", e); } } }
Save in redis when logging in
private String refreshToken(SmsSysUserDTO userDTO) { //User token String token; //current time Date now = new Date(); //Expiration time Date expireTime = new Date(now.getTime() + EXPIRE * 1000); //Judge whether the token has expired if (userDTO.getExpireTime() == null || userDTO.getExpireTime().getTime() < System.currentTimeMillis()) { //When the token expires, regenerate the token token = TokenGeneratorUtils.generateValue(); } else { token = userDTO.getToken(); } userDTO.setToken(token); userDTO.setExpireTime(expireTime); userDTO.setLastLoginTime(new Date()); smsSysUserService.saveOrUpdateDTO(userDTO); UserShareDTO userShareDTO = new UserShareDTO(userDTO.getId(), userDTO.getUsername(), token, SmsConstant.SmsSystem.SERVER.getValue()); // Cache token redisUtils.set(token, userShareDTO, EXPIRE); return token; }
JWT tool class
My tool class is mainly used to verify authorization scenarios.
Specific business scenario: the third-party system jumps to my system and displays the information on the home page of my system,
Specific implementation scheme:
1. A key is agreed with the third party, and AES symmetric encryption is used to request my interface through post for the information and a uuid
2. I decrypt it here. If the decryption is successful, the information is stored in the database, the uuid is encapsulated, and the JWT returns a signId
3. The third party substitutes the signId and uuid into the url. The front end parses the url and requests my authentication interface. If authentication passes, the information in the database is pulled for display
The specific JWT tools are as follows:
public class JwtTokenUtils implements Serializable { private static final long serialVersionUID = 1L; /** * User name */ private static final String USERNAME = Claims.SUBJECT; public static final String USERNAME_VALUE = "ABCabc-username"; /** * secret key */ private static final String SECRET = "TESTs-jwt"; /** * Creation time */ private static final String CREATED = "created"; /** * Permission list */ private static final String AUTHORITIES = "authorities"; /** * Valid for 12 hours */ public static final long EXPIRE_TIME = 12 * 60 * 60 * 1000; /** * Valid for 5 minutes */ public static final long EXPIRE = 5 * 60 * 1000; /** * Generate token * * @param uuid * @param expire * @return */ public static String generateToken(String uuid, Long expire) { Map<String, Object> claims = new HashMap<>(3); claims.put(USERNAME, USERNAME_VALUE); claims.put(CREATED, new Date()); // Here you can put some authentication information claims.put(AUTHORITIES, uuid); return generateToken(claims, expire); } /** * Get data declaration from token * * @param token token * @return Data declaration */ private static Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } /** * Determine whether the token has expired * * @param token token * @return Expired */ public static Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { throw new RenException("Your token is invalid or expired"); } } /** * Verify the token according to uuid * * @param token * @param uuid */ public static void verifyToken(String token, String uuid) { Claims claims = getClaimsFromToken(token); if (claims == null) { throw new RenException("Your token is invalid or expired"); } if (isTokenExpired(token)) { throw new RenException("Your token is invalid or expired"); } String username = claims.getSubject(); if (username == null) { throw new RenException("Your token is invalid or expired"); } Object authors = claims.get(AUTHORITIES); if (authors instanceof String) { String str = authors.toString(); if (!str.equals(uuid)) { throw new RenException("Your token is invalid or expired"); } } else { throw new RenException("Your token is invalid or expired"); } } /** * Generate token from data claim * * @param claims Data declaration * @param expire Expiration time * @return */ private static String generateToken(Map<String, Object> claims, long expire) { Date expirationDate = new Date(System.currentTimeMillis() + expire); return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact(); } }
Gateway authentication
Take the token generated by MD5 and saved in redis as an example:
Specific steps:
1. Filter white list
2. Take the token as the key from the request header and request redis
3. Packaging parameters
4. AES encryption sending request
5. Encapsulate the current login user object, and obtain the current login user object by parsing the parameters in the request header
@Component @RefreshScope public class GatewayFilter implements GlobalFilter, Ordered { @Autowired private RedisUtils redisUtils; /** * White list */ @Value(value = "${white.urls}") private Set<String> whiteUrls; /** * Interceptor * * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); // Filter white list if (null != whiteUrls.stream().filter(path::contains).findAny().orElse(null)) { return chain.filter(exchange); } // Get token from header String token = exchange.getRequest().getHeaders().getFirst("token"); // Access is blocked without a token or unauthorized if (StringUtils.isBlank(token)) { return TokenException(exchange); } // Cache token Map tokenMap = (Map) redisUtils.get(token); if (MapUtils.isEmpty(tokenMap)) { return TokenException(exchange); } if (MapUtils.isNotEmpty(tokenMap)) { // Encapsulate user UserShareDTO user = new UserShareDTO(); user.setId(MapUtils.getLong(tokenMap, "id")); user.setToken(MapUtils.getString(tokenMap, "token")); user.setSystem(MapUtils.getString(tokenMap, "system")); user.setUsername(MapUtils.getString(tokenMap, "username")); if (StringUtils.isBlank(user.getSystem()) || StringUtils.isBlank(user.getToken()) || StringUtils.isBlank(user.getUsername())) { return TokenException(exchange); } return buildRequest(exchange, chain, JSON.toJSONString(user)); } return chain.filter(exchange); } /** * The authentication information is encrypted with AES to send the request * * @param exchange * @param chain * @param auth * @return */ public Mono<Void> buildRequest(ServerWebExchange exchange, GatewayFilterChain chain, String auth) { String userAESStr = AESUtils.AESencode("abcdssdfsdfsafq", auth); ServerHttpRequest request = exchange.getRequest().mutate() .header("userInfo", userAESStr) .build(); return chain.filter(exchange.mutate().request(request).build()); } /** * token Invalid or expired * * @param exchange * @return */ private Mono<Void> TokenException(ServerWebExchange exchange) { //1. Get the response object ServerHttpResponse response = exchange.getResponse(); //2. Set response header type response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.setStatusCode(HttpStatus.OK); JSONObject jsonObject = new JSONObject(); jsonObject.put("msg", "Your token is invalid or expired"); jsonObject.put("code", HttpStatus.UNAUTHORIZED.value()); jsonObject.put("data", false); DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes()); return response.writeWith(Flux.just(wrap)); } /** * Interceptor sequence * * @return */ @Override public int getOrder() { return 0; } }
Currently logged in user object tool
@Slf4j public class ShareUtils { public static UserShareDTO user; public static UserShareDTO getUser() { updateInfo(); return user; } public static synchronized void updateInfo() { try { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String userInfoStr = request.getHeader("userInfo"); String userInfo = AESUtils.AESdecode("abcdssdfsdfsafq", userInfoStr); if (StringUtils.isNotBlank(userInfo)) { user = JSON.parseObject(userInfo, UserShareDTO.class); } else { user = new UserShareDTO(); } log.info("ShareUtils UserShareDTO ", user); } catch (Exception e) { log.error("ShareUtils HttpServletRequest request is null: " + e.getMessage()); user = new UserShareDTO(); } } }
If this authentication method is used, the feign interceptor needs to be rewritten when feign is called, and the request header will be lost
For specific solutions, please refer to my This article
Personally, I think this authentication method is more flexible. After the gateway redis authentication, the client, server, internal request, external request, etc. can be distinguished in the form of parameters. At the same time, it can be combined with mybaitis plus for data isolation.
Would you please comment on the disadvantages of this method or the areas that need to be improved!