JWT

2020-07-22  本文已影响0人  乌鲁木齐001号程序员

基于 OAuth2, Token 的 SSO 的缺点

JWT | Java Web Token

JWT 改造实现步骤

JWT 举例分析

怎么获取 JWT
JWT 长什么样
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXItc2VydmVyIl0sInVzZXJfbmFtZSI6ImxlaWxlaSIsInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNTk1MzIyMzAxLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6Ijg4ZDk3OTYzLTFmMWMtNGVjOS1hZTNiLWVjODg0Y2ZmODU1NSIsImNsaWVudF9pZCI6ImFkbWluIn0.3MR0sDydaxiJknIdNGNkd5vZJLbZhr994rLMzvf3vMk",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXItc2VydmVyIl0sInVzZXJfbmFtZSI6ImxlaWxlaSIsInNjb3BlIjpbInJlYWQiXSwiYXRpIjoiODhkOTc5NjMtMWYxYy00ZWM5LWFlM2ItZWM4ODRjZmY4NTU1IiwiZXhwIjoxNTk1MzIyMzExLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjM2NjU4ZTZhLTI5MWUtNDg1MS1hODZkLTYzYjY0MGE5Y2MwNiIsImNsaWVudF9pZCI6ImFkbWluIn0.IiJSzEwE9IINUfCW--2lGrxcbwPNmY3r3o_yWzJarWQ",
    "expires_in": 9,
    "scope": "read",
    "jti": "88d97963-1f1c-4ec9-ae3b-ec884cff8555"
}
JWT 的 access_token 字段分析

HEADER:

{
  "alg": "HS256",
  "typ": "JWT"
}

PAYLOAD:

{
  "aud": [
    "order-server" // 拿着这个令牌,可以访问哪些资源服务器,和 oauth_client_details.resource_ids 字段中的值是对应的
  ],
  "user_name": "leilei", // 这个令牌是发给 admin 应用的 leilei 这个用户的
  "scope": [
    "read"
  ],
  "exp": 1595322301,
  "authorities": [
    "ROLE_ADMIN"
  ],
  "jti": "88d97963-1f1c-4ec9-ae3b-ec884cff8555", // 可以认为是这个令牌的 Id
  "client_id": "admin" // 这个令牌是发给哪个应用的
}

VERIFY SIGNATURE:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  
) secret base64 encoded

JWT 实现 | 网关改造

依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
配置文件
security:
  oauth2:
    client:
      # 访问 /oauth/token_key 的时候,必修要身份认证
      client-id: gateway
      client-secret: 123456
    resource:
      jwt:
        # Zuul 在启动的时候,要知道从哪里拿到用于签名的秘钥,这个秘钥在证书(jojo.key)中
        key-uri: http://localhost:9090/oauth/token_key
资源服务器配置
@Configuration
@EnableResourceServer
public class GatewaySecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            // 所有去认证服务器的申请 Token 的请求都放过
            .antMatchers("/token/**").permitAll()
            .anyRequest().authenticated();
    }

}

JWT 实现 | 微服务改造

依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
配置文件
security:
  oauth2:
    client:
      # 访问 /oauth/token_key 的时候,必修要身份认证
      client-id: gateway
      client-secret: 123456
    resource:
      jwt:
        # Zuul 在启动的时候,要知道从哪里拿到用于签名的秘钥,这个秘钥在证书(jojo.key)中
        key-uri: http://localhost:9090/oauth/token_key
启动类
// order 微服务作为 OAuth 的资源服务器
@EnableResourceServer
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}
Controller 层
/**
 * 换用 JWT 之后,就要使用 @AuthenticationPrincipal 注解了
 * @param id
 * @param username
 * @return
 */
@GetMapping("/{id}")
public OrderInfo getInfo(@PathVariable Long id, @AuthenticationPrincipal String username) {
    log.info("user name " + username);
    log.info("orderId is " + id);

    OrderInfo info = new OrderInfo();
    info.setId(id);
    info.setProductId(id * 5);

    return info;
}

JWT 的网关工作流程

使用 JWT 之后的权限控制

类 ACL 的权限控制

缺点:每次修改权限,都要修改代码,都要重启应用;

网关调用权限服务
网关调授权服务.png

先配置,所有的请求都要通过一个方法验证是否有权限:

package com.lixinlei.security.gateway.config;

/**
 * 网关现在作为资源服务器了,即使网关,也是资源服务器
 */
@Configuration
@EnableResourceServer
public class GatewaySecurityConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private GatewayWebSecurityExpressionHandler gatewayWebSecurityExpressionHandler;

    /**
     * 设置:作为资源服务器的 zuul,其 id 是什么,如果这个 id 在客户端应用的 resource_ids 中,
     * 那么发给这个客户端应用的 jwt,就可以用来访问 zuul;
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .resourceId("gateway")
                // 表达式 #permissionService.hasPermission(request, authentication) 谁来解析,是在这个类中指定的,
                // gatewayWebSecurityExpressionHandler 就是继承了 OAuth2WebSecurityExpressionHandler 的类;
                .expressionHandler(gatewayWebSecurityExpressionHandler);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            // 所有去认证服务器申请 Token 的请求都放过
            .antMatchers("/token/**").permitAll()
            // request 就是当前的请求,authentication 就是当前的用户,
            // 对于任何氢气,Zuul 会调一个服务的 hasPermission 方法,
            // 返回 true 就能访问,返回 false 就不能访问;
            // 默认配置下,#permissionService.hasPermission(request, authentication) 这个 Spring Security 是看不懂的,
            // 需要在一个继承了 OAuth2WebSecurityExpressionHandler 的类中,配置让谁来解析表达式 permissionService;
            .anyRequest().access("#permissionService.hasPermission(request, authentication)");
    }

}

配置权限表达式谁来解析:

@Component
public class GatewayWebSecurityExpressionHandler extends OAuth2WebSecurityExpressionHandler {

    @Autowired
    private PermissionService permissionService;

    /**
     * 如果解析表达式 #permissionService.hasPermission(request, authentication),
     * 通过 permissionService 这个注入进来的 Bean
     * @param authentication
     * @param invocation
     * @return
     */
    @Override
    protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication,
                                                                        FilterInvocation invocation) {
        StandardEvaluationContext sec = super.createEvaluationContextInternal(authentication, invocation);
        // 告诉 Spring Security,表达式 permissionService 用这个 Bean permissionService 来解析
        sec.setVariable("permissionService", permissionService);
        return sec;
    }

}

具体用来解析表达式的类:

@Service
public class PermissionServiceImpl implements PermissionService {

    /**
     * 这个方法中,要判断用户是否有权限访问他要访问的 URI;
     * 在这个方法中,就应该调用远程服务,Redis,MySQL,或者 Zuul 启动的时候,就把权限信息都缓存到本地内存了;
     * @param request
     * @param authentication
     * @return
     */
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {

        // TODO: 2020/7/22 方法的实现换成自己业务下的权限控制逻辑就可以了

        System.out.println(request.getRequestURI());
        System.out.println(ReflectionToStringBuilder.toString(authentication));

//        if(authentication instanceof AnonymousAuthenticationToken) {
//            throw new AccessTokenRequiredException(null);
//        }

        return RandomUtils.nextInt() % 2 == 0;
    }

}

微服务之间的权限控制

微服务安全中心是认证服务器 + 授权服务器的统称。

JWT 网关返回 401 的两种可能

不传 token 访问 JWT 网关返回 401 的两种情况

授权成功 | 后端微服务返回 401
授权失败 | 网关返回 401

JWT 网关总结

JWT 网关.png
上一篇 下一篇

猜你喜欢

热点阅读