博客
关于我
微服务系统设计(13)——统一鉴权服务设计
阅读量:797 次
发布时间:2023-03-29

本文共 4880 字,大约阅读时间需要 16 分钟。

商场停车场景下的JWT认证方案

在商场停车场景中,除了极少数功能不需要用户登录外(如可用车位数),其他功能均需要用户在会话状态下才能正常使用。为实现统一的认证操作,本文将介绍在网关层增加一个公共鉴权功能,采用轻量级解决方案JWT(JSON Web Token)进行认证。


为什么选择JWT?

JSON Web Token(JWT)是一种流行的轻量级跨域认证解决方案。Tomcat的Session方式在分布式环境和多实例多应用场景下并不适用。JWT按一定规则生成并解析,无需存储,只需验证即可,相比于Session的存储方式,JWT的优势凸显得多。此外,JWT生成后只要不过期就可正常使用,但这也带来了一个潜在问题:一旦生成,token无法更改,需要借助第三方手段配置token验证,防止被别有用意的人员利用。JWT的无状态性特点使得服务端实例能够更好地扩展,避免了状态维护带来的额外开销。


JWT的两个特殊应用场景

  • 会话主动退出

    需要结合第三方解决方案(如Redis)来完成。会话主动退出时,将token写入缓存中,后续请求在网关层验证时,先判定缓存中是否存在,若存在则证明token无效,提示用户重新登录。

  • 用户持续在线但JWT失效

    假设JWT有效期为30分钟,如果用户持续在线,直接在30分钟后强制用户登录会影响用户体验。依照Session方式,只要用户活跃,有效期就要延长。然而JWT本身无法更改,这时需要刷新JWT来保证体验流畅性。方案如下:当检测到即将过期或已经过期但用户仍在活跃状态时,生成新token返回给前端,使用新的token进行请求,直到主动退出或失效退出。


  • 使用JWT进行认证

    1. 在网关层引入JJWT依赖

    io.jsonwebtoken
    jjwt
    0.9.1

    2. 编写JWT工具类

    @Slf4jpublic class JWTUtils {    private static final SecretKey SECRET_KEY = new SecretKeySpec(Base64.decodeBase64("your-key-here"), "AES");    public static String createJWT(String id, String subject, long ttlMillis, String key) throws Exception {        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;        long nowMillis = System.currentTimeMillis();        Date now = new Date(nowMillis);        JwtBuilder builder = Jwts.builder()                .setIssuer("")                .setId(id)                .setIssuedAt(now)                .setSubject(subject)                .signWith(signatureAlgorithm, SECRET_KEY);        if (ttlMillis >= 0) {            long expMillis = nowMillis + ttlMillis;            Date exp = new Date(expMillis);            builder.setExpiration(exp);        }        return builder.compact();    }    public static Claims parseJWT(String jwt, String key) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {        SecretKey key = new SecretKeySpec(Base64.decodeBase64(key), "AES");        Claims claims = Jwts.parser()                .setSigningKey(key)                .parseClaimsJws(jwt)                .getBody();        return claims;    }    public static boolean isTokenExpire(String jwt, String key) {        Claims aClaims = parseJWT(jwt, key);        if (LocalDateTime.now().isAfter(                LocalDateTime.now()                        .with(aClaims.getExpiration().toInstant().atOffset(ZoneOffset.ofHours(8)).toLocalDateTime()))) {            return true;        } else {            return false;        }    }}

    3. 验证token

    @Component@Order(-200)public class JWTFilter implements GlobalFilter {    @Autowired    private JWTData jwtData;    private ObjectMapper objectMapper = new ObjectMapper();    @Override    public Mono
    filter(ServerWebExchange exchange, GatewayFilterChain chain) { String url = exchange.getRequest().getURI().getPath(); if (jwtData.getSkipUrls() != null && Arrays.asList(jwtData.getSkipUrls()).contains(url)) { return chain.filter(exchange); } String token = exchange.getRequest().getHeaders().getFirst("token"); ServerHttpResponse resp = exchange.getResponse(); if (StringUtils.isEmpty(token)) { return authError(resp, "请先登录!"); } else { try { JWTUtils.parseJWT(token, jwtData.getTokenKey()); log.info("验证通过"); return chain.filter(exchange); } catch (ExpiredJwtException e) { log.error("token过期", e); return authError(resp, "token过期"); } catch (Exception e) { log.error("认证失败", e); return authError(resp, "认证失败"); } } } private Mono
    authError(ServerHttpResponse resp, String message) { resp.setStatusCode(HttpStatus.UNAUTHORIZED); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); CommonResult
    returnData = new CommonResult<>(HttpStatus.SC_UNAUTHORIZED); returnData.setRespMsg(message); String returnStr = ""; try { returnStr = objectMapper.writeValueAsString(returnData.getRespMsg()); } catch (JsonProcessingException e) { log.error("JSON转换错误", e); } DataBuffer buffer = resp.bufferFactory().wrap(returnStr.getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } @Override public int getOrder() { return -200; }}

    4. 配置

    jwt:    token-key: your-key-here    skip-urls:        - /member-service/member/bindMobile        - /member-service/member/logout@Data@ConfigurationProperties(prefix = "jwt")public class JWTData {    public String tokenKey;    private String[] skipUrls;}

    测试可用性

    本次测试主要验证token的可用性。通过Postman工具,使用生成的正常token进行“商场用户日常签到功能请求”,验证请求是否成功。


    结语

    以上方案提供了一个轻量级的网关鉴权解决方案,虽然简单但实用。在应对复杂场景时,还需结合其他组件或功能来加固服务安全性。例如,鉴权通过后,确定哪些功能有权操作,这在管理系统中很常见,但本案例中未体现。可以尝试增加角色权限配置来验证,加深对JWT的理解。

    转载地址:http://jmhfk.baihongyu.com/

    你可能感兴趣的文章
    Objective-C实现inversions倒置算法(附完整源码)
    查看>>
    Objective-C实现isalpha函数功能(附完整源码)
    查看>>
    Objective-C实现islower函数功能(附完整源码)
    查看>>
    Objective-C实现isPowerOfTwo算法(附完整源码)
    查看>>
    Objective-C实现isupper函数功能(附完整源码)
    查看>>
    Objective-C实现ItemCF算法(附完整源码)
    查看>>
    Objective-C实现ItemCF算法(附完整源码)
    查看>>
    Objective-C实现iterating through submasks遍历子掩码算法(附完整源码)
    查看>>
    Objective-C实现jaccard similarity相似度无平方因子数算法(附完整源码)
    查看>>
    Objective-C实现Julia集算法(附完整源码)
    查看>>
    Objective-C实现k nearest neighbours k最近邻分类算法(附完整源码)
    查看>>
    Objective-C实现k-Means算法(附完整源码)
    查看>>
    Objective-C实现k-nearest算法(附完整源码)
    查看>>
    Objective-C实现Knapsack problem背包问题算法(附完整源码)
    查看>>
    Objective-C实现knapsack背包问题算法(附完整源码)
    查看>>
    Objective-C实现knapsack背包问题算法(附完整源码)
    查看>>
    Objective-C实现knight tour骑士之旅算法(附完整源码)
    查看>>
    Objective-C实现KNN算法(附完整源码)
    查看>>
    Objective-C实现koch snowflake科赫雪花算法(附完整源码)
    查看>>
    Objective-C实现KPCA(附完整源码)
    查看>>