微服务授权认证及实践

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: 受保护资源
在这里插入图片描述

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

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>
</dependencies>

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实验结果:在这里插入图片描述
将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.2 令牌转换

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

3.3. 服务调用

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

实验 - JWT 生成及验证

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

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>${java-jwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>${jjwt-api.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>${jjwt-api.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-gson</artifactId>
    <version>${jjwt-api.version}</version>
</dependency>

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 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值