• 微服务之间的通讯安全(二)-使用JWT优化认证机制


    1、使用JWT来解决认证中存在的问题

      之前说认证中存在的问题是效率低,每次都要取认证服务器进行校验;不安全,传递用户信息是放到请求头中的明文。这两个问题的解决方案就是JWT。JWT官网扫盲连接https://jwt.io/introduction/

      因为我们之前发出去的令牌都是一些无意义的串,而JWT中可以包含一些用户信息,这样前端发请求过来,网关就不需要去认证服务器校验了,我们只需要校验这个JWT是否被串改,并且从里面将用户信息读出来就可以了,往下转发传递和服务与服务之间进行调用时,只需要传递JWT就可以了。并且Spring给我们提供了工具,不用我们自己写代码就可以完成。我们要将架构改成下图:

    2、认证服务器改造,使其发送JWT令牌

    2.1、将之前API安全-https中使用keytool生成的证书copy到resources下

    2.2、OAuth2认证服务器配置类,将tokenStore设置为JwtTokenStore,并对暴露获取令牌签名的验证密钥

    /**
     * OAuth2认证服务器配置类
     * 需要继承AuthorizationServerConfigurerAdapter类,覆盖里面三个configure方法
     * 并添加@EnableAuthorizationServer注解,指定当前应用做为认证服务器
     *
     * @author caofanqi
     * @date 2020/1/31 18:04
     */
    @Configuration
    @EnableAuthorizationServer
    public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    
    
        @Resource
        private AuthenticationManager authenticationManager;
    
        @Resource
        private DataSource dataSource;
    
        @Resource
        private UserDetailsService userDetailsService;
    
        /**
         * 配置授权服务器的安全性
         * checkTokenAccess:验证令牌需要什么条件,isAuthenticated():需要经过身份认证。
         * 此处的passwordEncoders是为client secrets配置的。
         * tokenKeyAccess:设置对获取令牌签名的验证密钥需要通过身份认证
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security
                    .checkTokenAccess("isAuthenticated()")
                    .passwordEncoder(new BCryptPasswordEncoder())
                    .tokenKeyAccess("isAuthenticated()");
        }
    
    
        /**
         * 配置客户端服务
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //从数据库中读取
            clients.jdbc(dataSource);
        }
    
        /**
         * 配置授权服务器终端的非安全特征
         * authenticationManager 校验用户信息是否合法
         * tokenStore:token存储
         * userDetailsService:配合刷新令牌使用
         * tokenEnhancer:令牌增强器
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .authenticationManager(authenticationManager)
    //                .tokenStore(new JdbcTokenStore(dataSource))
                    .tokenStore(new JwtTokenStore(jwtTokenEnhancer()))
                    .tokenEnhancer(jwtTokenEnhancer())
                    .userDetailsService(userDetailsService);
        }
    
    
        /**
         *  jwt令牌增强器,使用KeyPair提高安全度。
         *  声明为spring bean是为了让资源服务器可以获取令牌签名的验证密钥 ,TokenKeyEndpoint类中的 /oauth/token_key
         */
        @Bean
        public JwtAccessTokenConverter jwtTokenEnhancer() {
            JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
            //jwtAccessTokenConverter.setSigningKey("123456");
            KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("cfq.key"), "123456".toCharArray());
            jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("cfq"));
            return jwtAccessTokenConverter;
        }
    
    }

    2.3、启动资源服务器获取令牌

      可以发现,我们现在获取到的令牌比以前长了,我们将他复制到jwt官网,可以看到如下,解析后JWT的PAYLOAD中存放这一些数据,aud:该令牌可以访问的资源服务器,user_name:申请令牌的用户,scope:令牌的scope,exp:令牌的过期时间,authorities:申请令牌用户的角色信息,client_id:申请令牌的客户端id,jti:相当于该令牌的id。当然,我们也可以在这里面加入一些信息,但是不建议,因为jwt只是防篡改,任何人都能看到里面的数据,往里面加入一些业务信息,有可能导致信息泄漏。

    3、网关和资源服务器改造

    3.1、在网关上对JWT进行认证,不再向认证服务器发请求认证

      3.1.1、将之前的一系列过滤器删掉,因为除了流控和审计,剩下的SpringSecurity和SpringSecurityOauth都为我们提供了,我们直接用就好了

      3.1.2、引入oauth2依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>

      3.1.3、application.yml配置获取令牌签名的验证密钥地址,因为认证服务器设置了需要认证,我们还要配上client-id和client-secret

    server:
      port: 9010
    
    zuul:
      routes:
        token:
          url: http://auth.caofanqi.cn:9020
          path: /token/**
        order:
          url: http://order.caofanqi.cn:9080
          path: /order/**
      sensitive-headers:
    
    security:
      oauth2:
        client:
          client-id: gateway
          client-secret: 123456
        resource:
          jwt:
            key-uri: http://auth.caofanqi.cn:9020/oauth/token_key

      3.1.4、网关资源服务器配置,放过申请令牌请求

    /**
     * 网关资源服务器配置
     *
     * @author caofanqi
     * @date 2020/2/8 22:30
     */
    @Configuration
    @EnableResourceServer
    public class GatewayResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId("gateway");
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    //放过申请令牌的请求不需要身份认证
                    .antMatchers("/token/**").permitAll()
                    .anyRequest().authenticated();
        }
    
    }

      3.1.5、Order和Price资源服务器配置,也是需要引入oauth2依赖,配置获取令牌签名的验证密钥地址,client-id和client-secret,但是调用服务的请求需要由RestTemplate替换为OAuth2RestTemplate,这样就会将在我们调用别的服务时,将jwt一并传递过去。获取用户信息,通过@AuthenticationPrincipal注解进行获取。

    /**
     * 订单微服务
     *
     * @author caofanqi
     * @date 2020/1/31 14:22
     */
    @EnableResourceServer
    @SpringBootApplication
    public class OrderApiApplication {
    
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApiApplication.class,args);
        }
    
        /**
         *  将OAuth2RestTemplate声明为spring bean,OAuth2ProtectedResourceDetails,OAuth2ClientContext springboot会自动帮我们注入
         */
        @Bean
        public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context){
            return new OAuth2RestTemplate(resource,context);
        }
    
    }
    /**
     * 订单控制层
     *
     * @author caofanqi
     * @date 2020/1/31 14:26
     */
    @Slf4j
    @RestController
    @RequestMapping("/orders")
    public class OrderController {
    
        @Resource
        private OAuth2RestTemplate oAuth2RestTemplate;
    
        @PostMapping
        public OrderDTO create(@RequestBody OrderDTO orderDTO, @AuthenticationPrincipal String username) {
            log.info("username is :{}", username);
            PriceDTO price = oAuth2RestTemplate.getForObject("http://127.0.0.1:9070/prices/" + orderDTO.getProductId(), PriceDTO.class);
            log.info("price is : {}", price.getPrice());
            return orderDTO;
        }
    
    
        @GetMapping("/{id}")
        public OrderDTO get(@PathVariable Long id, @AuthenticationPrincipal String username) {
            log.info("username is :{}", username);
            OrderDTO orderDTO = new OrderDTO();
            orderDTO.setId(id);
            orderDTO.setProductId(5 * id);
            return orderDTO;
        }
    
    }

      3.1.6、测试,需要先启动认证服务器,因为各资源服务器需要在启动时获取令牌签名的验证密钥。

      获取令牌,通过网关创建订单,报错403,是因为我们通过webApp申请的令牌可以访问的资源服务器没有添加gateway,

    我们可以在resource_ids添加上gateway,也可以什么都不填,这样发出去的令牌就可以访问所有的资源服务器了。

    我们这里,什么都不填写,然后重新申请令牌,再次通过网关创建订单,可以正常创建,并且在订单服务和价格服务中可以获取到username

      

      我们传一个错误的令牌或者不传令牌进行访问,会返回401,这说明我们之前写的逻辑SpringSecurity和SpringSecurityOauth都已经帮我们实现好了。

     

     项目源码:https://github.com/caofanqi/study-security/tree/dev-jwt-authentication

  • 相关阅读:
    WCF学习笔记之传输安全
    JavaScript –type
    详解android的号码匹配
    list和用vector区别(Vector相当于是数组,读写快,插入慢)
    拒绝收购邀请,三年专注开发,开源的私有云盘“迷你云”(十人团队在三年时间里靠自筹资金专注开发出来的作品)
    无功不受禄
    如何判断是否安装了VC RUNTIME
    GExpert 1.38 实验版含经典代码格式工具 Berlin 编译版
    初始化IoC容器(Spring源码阅读)
    JavaScript函数绑定
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12285760.html
Copyright © 2020-2023  润新知