微服务授权认证及实践

微服务授权认证机制

  • 1. 背景
  • 2. Overview
  • 3. 认证授权流程
    • 3.1 获取Access Token
      • 实验 - 基于spring security oauth2 获取access token
    • 3.2 令牌转换
    • 3.3. 服务调用
      • 实验 - JWT 生成及验证
  • 4. 安全问题

1. 背景

OAuth2.0是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而JWT是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。OAuth 2.0的模式一共有四种,这里只假设客户直接访问公司自己的门户网站,即第一方Web应用,可以选择OAuth 2.0的资源拥有者凭据模式。 OAuth2令牌 + JWT混合模式,IDP(Identity Provider)要支持OAuth 2.0 授权协议处理,及从OAuth2令牌到JWT令牌的转换。最后以代码方式实现了授权、认证过程。

2. Overview

先做一个概览,下面是Access token + JWT混合模式流程图:
Client : 资源拥有者
IDP:授权服务
Providers: 受保护资源
微服务授权认证及实践_第1张图片

3. 认证授权流程

3.1 获取Access Token

  1. 从身份认证服务器获取身份验证OAuth凭据client id 和 secret
  2. 请求Access Token: Token API in gateway generate OAuth2 access token for the Client Id, Secret and Scopes combination.

实验 - 基于spring security oauth2 获取access token

引入依赖

>
    >
        >org.springframework.security.oauth>
        >spring-security-oauth2>
    >
>

Authorization 配置类:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    public OAuth2AuthorizationConfig(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer) {
        endpointsConfigurer.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
        clientDetailsServiceConfigurer.inMemory()
                .withClient("clientapp")
                .secret("{bcrypt}$2a$10$OL6aAqldM9QMoK/D3Y0xA.KIaWw9kOBs83exhr4DUACcHq5ZeCI7C")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("read_userinfo", "read_contacts");
    }
}

获取access_token实验结果:微服务授权认证及实践_第2张图片
将Resource server 配置类与Authorization集成在同一个ms中,使用access token直接获取资源。但大型分布式微服务的授权和认证还是推荐 OAuth2 + JWT混合模式。

@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/api/**");
    }

}

调用资源API:

@Controller
public class UserController {

    @RequestMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String email = user.getUsername() + "@spring2go.com";
        UserInfo userInfo = new UserInfo();
        userInfo.setName(user.getUsername());
        userInfo.setEmail(email);
        return ResponseEntity.ok(userInfo);
    }
}

实验结果:
通过调用资源API,获取到了用户名和邮箱信息
微服务授权认证及实践_第3张图片

3.2 令牌转换

  • Gateway 验证访问令牌
  • Gateway 根据客户端发送的访问令牌获取的用户详细信息生成JWT
  • JWT将使用RSA算法和私钥进行签名,并将其放在HTTP头中发送
  • Gateway与Providers sidecar共享公钥,以进行JWT签名验证

3.3. 服务调用

  • Gateway进行路由、负载均衡以及限流
  • Provider sidecar将从Gateway获得公钥并用于JWT签名验证
  • 当事务完成时(成功或错误),网关将事务日志发送给日志系统,其中包含一些头的详细信息、运行时间、状态等

实验 - JWT 生成及验证

Java中对JWT的支持可以使用JJWT(实现了JOSE规范)开源库;
import依赖:

>
    >com.auth0>
    >java-jwt>
    >${java-jwt.version}>
>
>
    >io.jsonwebtoken>
    >jjwt-api>
    >${jjwt-api.version}>
>
>
    >io.jsonwebtoken>
    >jjwt-impl>
    >${jjwt-api.version}>
>
>
    >io.jsonwebtoken>
    >jjwt-gson>
    >${jjwt-api.version}>
>

jwt的生成:

private static final String JWT_SECRET = "test_jwt_secret_test_jwt_secret_test_jwt_secret";

public static SecretKey generalKey() {
    byte[] encodeKey = JWT_SECRET.getBytes(StandardCharsets.UTF_8);
    //这里只是简单的实验,生产中要使用非对称加密~
    SecretKey key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    return key;
}

/**
 * 签发JWT, 创建token的方法
 *
 * @param id        jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
 * @param iss       jwt签发者
 * @param subject   jwt所面向的用户, payload中记录的public claims. 即为JWTSubject的信息
 * @param ttlMillis 有效期,单位毫秒
 * @return token
 */
public static String createJWT(String id, String iss, String subject, long ttlMillis) {
    long currentMillis = System.currentTimeMillis();
    Date now = new Date();
    SecretKey secretKey = generalKey();
    JwtBuilder builder = Jwts.builder()
            .serializeToJsonWith(new GsonSerializer<>(new Gson()))
            .setId(id)//身份标识
            .setIssuer(iss)
            .setSubject(subject)
            .setIssuedAt(now)
            .signWith(secretKey);
    if (ttlMillis >= 0) {
        long expMillis = currentMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        builder.setExpiration(expDate);
    }
    return builder.compact();
}

jwt的校验:

public static JWTResult validateJWT(String jwtStr) {
    JWTResult checkResult = new JWTResult();
    Claims claims;
    try {
        claims = parseJWT(jwtStr);
        checkResult.setSuccess(true);
        checkResult.setClaims(claims);
    } catch (ExpiredJwtException e) {
        checkResult.setErrCode(JWT_ERR_CODE_EXPIRE);
        checkResult.setSuccess(false);
    } catch (Exception e) {
        checkResult.setErrCode(JWT_ERR_CODE_FAIL);
        checkResult.setSuccess(false);
    }
    return checkResult;
}

public static Claims parseJWT(String jwt) {
    SecretKey secretKey = generalKey();
    return Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(jwt)
            .getBody();//token中记录的payload数据
}

4. 安全问题

  • 只允许特定身份的客户端访问,令牌请求启用Mutual TLS认证,请求需要同时确认服务端和调用者的身份。
  • 为了避免重放攻击,在token中加入时间戳,保证token快速过期,过期后采用refresh_token刷新获取新的token。
  • 采用公钥、私钥非对称加密,分布式场景下,建议选择 RS256 。
  • 避免敏感信息保存在 JWT 中,JWS 方式下的 JWT 的 Payload 信息是公开的,不能将敏感信息保存在这里,如有需要,请使用 JWE 。

你可能感兴趣的:(微服务,java,架构)