SpringCloud基础权限框架搭建(2)

该篇用以记录我在整合springcloud框架中遇到林林总总的问题,因为目前对于微服务框架的理解还很有局限性,下文有误的地方欢迎批评指正

SpringCloud权限设计_SpringCloud权限设计


先说明一下思路,auth服务用于处理授权请求,user服务作为资源服务器同时也提供注册登录的开放接口(采用密码模式来获取令牌),zuul服务除了作为服务网关也是受auth服务保护的资源服务器,开放对auth与user的请求,HystrixDashboard/Turbine用于监控熔断

1)Spring boot - 微服务的入门级微框架,用来简化 Spring 应用的初始搭建以及开发过程。
2)Eureka - 云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
3)Hystrix - 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
4)Hystrix-dashboard - 一款针对Hystrix进行实时监控的工具,可以在直观地看到各Hystrix Command的请求响应时间, 请求成功率等数据
5)Turbine - Turbine 是聚合服务器发送事件流数据的一个工具,用来监控集群下 hystrix 的 metrics 情况。
6)Zuul - Zuul是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web网站后端所有请求的前门。
7)Ribbon - 提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。
8)Feign - Feign是一种声明式、模板化的 HTTP 客户端。
9)RestTemplate - RestTemplate是一种支持Rest风格架构的互联网的请求方式模板类。
10)JWT - JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
11)Spring Cloud OAuth2 - 基于 Spring Security 和 OAuth2的安全工具包,为你的应用程序添加安全控制。

项目结构:

  1. sc-eurekaserver:注册中心
  2. common:公共包,方便其他服务引用
  3. sc-auth-server:授权服务器
  4. sc-zuul-server:服务网关
  5. sc-user-server:用户服务
  6. sc-hystrix-dashboard:熔断监控

以下仅贴出部分核心代码,详情见源码
源码路径:
传送门


文章目录

  • parent
  • 注册中心sc-eurekaserver
  • 公共包common
  • 授权服务器sc-auth-server
  • 服务网关sc-zuul-server
  • 用户服务sc-user-server
  • 熔断监控sc-hystrix-dashboard
  • 补充:使用Feign/RestTemplate调用服务时的权鉴处理与服务调用案例
  • 权鉴处理
  • feign
  • RestTemplate


parent

pom.xml

<modules>
    	<module>cloud1.0-common</module>
        <module>cloud1.0-eureka-server</module>
        <module>cloud1.0-auth-server</module>
		<module>cloud1.0-zuul-server</module>
		<module>cloud1.0-user-server</module>
		<module>cloud1.0-hystrix-dashboard</module>
  </modules>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
        <common.version>0.0.1-SNAPSHOT</common.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
    </dependencies>

注册中心sc-eurekaserver

与普通的Eureka工程无太大差异

pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	</dependency>
</dependencies>

bootstrap.yml

server:
  port: 8761
spring:
  application:
    name: sc-eurekaserver
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

EurekaServerApplication.java

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

公共包common

作为公共包被其他服务所引用

pom.xml

<dependencies>
    	<!-- RestTemplate和FeignClient -->
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>fastjson</artifactId>
		    <version>1.2.31</version>
		</dependency>
    </dependencies>

src/main/resource/META-INF/spring.factories(该文件是为了将公共的bean注册到spring容器)

这里解释一下,由于SpringBoot默认包扫描机制是从启动类所在包开始,扫描当前包及其子包下的所有文件,所以配置在公共包里面定义的bean在引用工程将不会注册,所以在这里将公共的bean配置在spring.factories。当然,也可以在引用工程使用@ComponentScan注解来指定扫描包

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cn.springcloud.book.common.config.CommonConfiguration

CommonConfiguration.java
用于定义全局的Feign与RestTemplate拦截器

在springcloud项目中,服务消费者与服务生产者一般通过Feign与RestTemplate来实现服务调用。Feign默认实现负载均衡,RestTemplate在这里添加负载均衡配置

@SuppressWarnings("deprecation")
@Configuration
@EnableWebMvc
public class CommonConfiguration extends WebMvcConfigurerAdapter {

	/**
	 * 创建Feign请求拦截器
	 */
	@Bean
	@ConditionalOnClass(Feign.class)
	public FeignUserContextInterceptor feignTokenInterceptor() {
		return new FeignUserContextInterceptor();
	}

	/**
	 * RestTemplate拦截器
	 * @LoadBalanced:启用了负载均衡后url不再写端口而是直接写serviceId,ps:http://sc-auth-server/oauth/token
	 * @return
	 */
	@LoadBalanced
	// 开启客户端负载均衡
	@Bean
	public RestTemplate restTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		restTemplate.getInterceptors().add(
				new RestTemplateUserContextInterceptor());
		return restTemplate;
	}

}

授权服务器sc-auth-server

pom.xml

这里对于SecurityOAuth2数据与令牌存储使用mysql数据库
注意需添加免编译过滤jks与cert文件(jwt令牌与公钥),关于Security与OAuth2的使用可以参见另外两篇博客:


关于jwt令牌的生成见:

<dependencies>
       <dependency>
			<groupId>cn.springcloud.book</groupId>
			 <artifactId>cloud1.0-common</artifactId>
			 <version>${parent.version}</version>
        </dependency>
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>

application.yml

spring:
  application:
    name: sc-auth-server
  datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: update
    show-sql: true
    
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
server:
  port: 8078

resources目录

test-jwt.jks和public.cert分别为私钥和公钥,公钥用于授权服务器配置,公钥用于资源服务器配置

SpringCloud权限设计_SpringSecurityOAuth2_02


启动类AuthServerApplication

@SpringBootApplication
@EnableDiscoveryClient//配置本应用将使用服务注册和服务发现
@EnableCircuitBreaker//启动断路器
public class AuthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServerApplication.class, args);
    }
    
}

sql

用户/角色/权限的表结构可以根据自己的业务需求做调整,这里默认添加了一个admin/admin用户
关于entity、repository的代码就不贴出来了

DROP TABLE IF EXISTS `sys_permission`;
 
CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT '',
  `descritpion` varchar(10) DEFAULT '',
  `url` varchar(10) DEFAULT '',
  `pid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
 
/*Data for the table `sys_permission` */
 
insert  into `sys_permission`(`id`,`name`,`descritpion`,`url`,`pid`) values (3,'ADMIN','','',NULL),(4,'USER','','',NULL);
 
/*Table structure for table `sys_permission_role` */
 
DROP TABLE IF EXISTS `sys_permission_role`;
 
CREATE TABLE `sys_permission_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `permission_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
 
/*Data for the table `sys_permission_role` */
 
insert  into `sys_permission_role`(`id`,`role_id`,`permission_id`) values (1,2,4),(2,1,3);
 
/*Table structure for table `sys_role` */
 
DROP TABLE IF EXISTS `sys_role`;
 
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
 
/*Data for the table `sys_role` */
 
insert  into `sys_role`(`id`,`name`) values (1,'ADMIN'),(2,'USER');
 
/*Table structure for table `sys_role_user` */
 
DROP TABLE IF EXISTS `sys_role_user`;
 
CREATE TABLE `sys_role_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `Sys_user_id` int(11) DEFAULT NULL,
  `sys_role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
 
/*Data for the table `sys_role_user` */
 
insert  into `sys_role_user`(`id`,`Sys_user_id`,`sys_role_id`) values (1,2,1),(2,3,2);
 
/*Table structure for table `sys_user` */
 
DROP TABLE IF EXISTS `sys_user`;
 
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
 
/*Data for the table `sys_user` admin/admin xuweichao/xuweichao*/
 
insert  into `sys_user`(`id`,`name`,`password`) values (2,'admin','$2a$10$Yks2LoqzBUHEWjyLCnsdtepI4oCNip9yNdf67y19ewF8geORNAO5m'),(3,'xuweichao','$2a$10$kmFQOKZw8l776qXp00Lq9e2drL5MUSpG9YHnQtQwbVzyUjJQwHNha');

DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL,
  `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

定义权限对象service层接口PermissionService

public interface PermissionService {
    public List<Permission> findAll();
    public List<Permission> findByAdminUserId(int userId);
}

PermissionService实现类PermissionServiceImpl

@Service
public class PermissionServiceImpl implements PermissionService {
 
    @Autowired
    PermissionRepository permissionRepository ;
 
    @PersistenceContext
    private EntityManager entityManager;
 
    @Override
    public List<Permission> findAll() {
        return null;
    }
 
    @Override
    public List<Permission> findByAdminUserId(int userId) {
 
        List<Permission> list = new ArrayList<Permission>();
        List<Object[]> abcs = entityManager.createNativeQuery("select p.* \n" +
                "        from Sys_User u\n" +
                "        LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id\n" +
                "        LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id\n" +
                "        LEFT JOIN Sys_permission_role spr on spr.role_id=r.id\n" +
                "        LEFT JOIN Sys_permission p on p.id =spr.permission_id\n" +
                "        where u.id="+userId).getResultList();
        for (Object[] abc : abcs) {
        	if(abc[0] != null){
                Permission permission = new Permission();
                permission.setId(Integer.valueOf(abc[0]+""));
                permission.setName((abc[1] != null?abc[1]:"")+"");
                permission.setDescritpion((abc[2] != null?abc[2]:"")+"");
                permission.setUrl((abc[3] != null?abc[3]:"")+"");
                list.add(permission);
        	}
        }
        return list;
    }
}

自定义的UserDetailsService实现类

@Service
public class UserDetailServiceImpl implements UserDetailsService {

  @Autowired
  SysUserRepository sysUserRepository;
  @Autowired
  PermissionService permissionService;
  @Autowired
  PasswordEncoder passwordEncoder;//cn.springcloud.book.auth.config.SecurityConfiguration>BCryptPasswordEncoder

  @Transactional
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      SysUser sysUser = sysUserRepository.getUserByName(username);
      List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
      if (sysUser != null) {
          System.err.println("sysUser===============" + sysUser);
          //获取用户的授权
          List<Permission> permissions = permissionService.findByAdminUserId(sysUser.getId());
          //声明授权文件
          for (Permission permission : permissions) {
              if (permission != null && permission.getName() != null) {
                  GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+permission.getName());//spring Security中权限名称必须满足ROLE_XXX
                  grantedAuthorities.add(grantedAuthority);
              }
          }
      }
      System.err.println("grantedAuthorities===============" + grantedAuthorities);
      return new User(sysUser.getName(), sysUser.getPassword(), grantedAuthorities);
  }
}

配置WebSecurityConfigurer

注意:WebSecurityConfigurer的过滤顺位要在ResourceServerConfigurer之前,所以在WebSecurityConfigurer和ResourceServerConfigurer添加@Order配置

@Order(2)
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
    private UserDetailsService userDetailsService;//注入自定义userdetailservice(com.service.auth.serviceauth.service.impl.UserDetailServiceImpl)
	
    @Bean
    PasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder();
        //return PasswordEncoderFactories.createDelegatingPasswordEncoder();//兼容多种密码的加密方式
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	//不拦截/oauth/**,/login/**,/logout/**(requestMatchers用于需要过滤多个HttpSecurity的情况)
        http.requestMatchers().antMatchers("/oauth/**", "/login/**", "/logout/**")//使HttpSecurity接收以"/login/","/oauth/","/logout/"开头请求。
                .and().authorizeRequests().antMatchers("/oauth/**").authenticated()
                .and().formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());//注入自定义的UserDetailsService,采用BCrypt加密
    }
    
    @Override//通过重载该方法,可配置Spring Security的Filter链(HTTP请求安全处理)
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
}

实现TokenEnhancer接口用于给JWT令牌添加自定义信息

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,OAuth2Authentication authentication) {
        Map<String, Object> additionalInformation = new HashMap<>();
        String userName = authentication.getUserAuthentication().getName();
        User user = (User) authentication.getUserAuthentication().getPrincipal();
        additionalInformation.put("userName", userName);
        additionalInformation.put("roles", user.getAuthorities());
        additionalInformation.put("organization",authentication.getName());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
        return accessToken;
    }
}

配置AuthorizationServerConfigurer

注意:授权服务器(AuthorizationServerConfiguration)的TokenStore与资源服务器(ResourceServerConfig)的TokenStore是不同的,前者直接使用秘钥+密码,后者使用公钥解析

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
 
    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;
 
    @Autowired
    private UserDetailsService userDetailsService;
 

    @Autowired
    @Qualifier("passwordEncoder")
    PasswordEncoder passwordEncoder;//BCryptPasswordEncoder

    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("test-jwt.jks"), "test123".toCharArray());//证书路径和密钥库密码
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("test-jwt"));//密钥别名
        return converter;
    }
    
	@Bean
	public TokenEnhancer tokenEnhancer() {
		return new CustomTokenEnhancer();
	}

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
 
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(
                Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));//jwt令牌中添加自定义信息

        endpoints.tokenStore(tokenStore())
        		.tokenEnhancer(tokenEnhancerChain)//令牌配置
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)//必须设置 UserDetailsService 否则刷新token 时会报错
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        endpoints.accessTokenConverter(jwtAccessTokenConverter());
        
        //自定义授权页(none)
        endpoints.pathMapping("/oauth/confirm_access", "/confirm");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
		/* 配置token获取合验证时的策略 */
        .tokenKeyAccess("permitAll()")// 开启/oauth/token_key验证端口无权限访问
        .checkTokenAccess("isAuthenticated()")// 开启/oauth/check_token验证端口认证权限访问
        .allowFormAuthenticationForClients();//允许表单登录
 
    }
}

1)由于授权服务器本身也提供对外接口所以也配置资源服务ResourceServerConfigurer
2)这里开放/actuator/hystrix.stream是为了后面整合HystrixDashboard/Turbine使用
3)配置资源服务器要注意两点:1需要把accessTokenConverter作为bean注册到容器中;2直接使用converter.setVerifierKey(publicKey)配置公钥时通过网关使用令牌获取资源会提示convert access token to JSON,需配置成converter.setVerifier(new RsaVerifier(publicKey))

@Order(6)//该过滤器优先级需高于WebSecurityConfigurerAdapter才能实现授权码模式
@Configuration
@EnableResourceServer //这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
	
	@Bean//需要以公钥作为bean外部接口才能访问受限资源
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource =  new ClassPathResource("public.cert");
        String publicKey;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
        //使用converter.setVerifierKey(publicKey);会出现 Cannot convert access token to JSON
        converter.setVerifier(new RsaVerifier(publicKey));
        return converter;
    }
    
	@Bean
    public TokenStore tokenStore() {//配置token模式
        return new JwtTokenStore(accessTokenConverter());
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.tokenStore(tokenStore());
    }

	/*
	 * 配置受资源服务器保护的资源链接,仅接受签名校验
	 * */
    @Override
    public void configure(HttpSecurity http) throws Exception {
    	http.csrf().disable();
        http.authorizeRequests()
        .antMatchers("/actuator/hystrix.stream").permitAll()
        .anyRequest().authenticated();//校验所有请求
        }
 
}

提供获取用户信息的接口

@RestController
@RequestMapping("/users")
public class UserController {

    Logger logger = LoggerFactory.getLogger(UserController.class);

    //用于获取当前token的用户信息
    @RequestMapping(value = "/current", method = RequestMethod.GET)
    public Principal getUser(Principal principal) {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>");
        logger.info(principal.toString());
        logger.info("<<<<<<<<<<<<<<<<<<<<<<<<");
        return principal;
    }
}

测试:

依次启动sc-eurekaserver、sc-auth-server

访问http://localhost:8761/

SpringCloud权限设计_Zuul_03


配置一个密码登陆模式客户端

注意:
授权码模式已经在代码中实现,需另外配置客户端,该项目未使用该模式这里不细谈。
客户端模式在该项目中无法使用,这是因为我们在CustomTokenEnhancer中自定义令牌信息中使用了用户的相关属性,所以当我们使用客户端模式登陆时由于缺少用户信息授权服务器将会提示空指针异常。

INSERT INTO `oauth_client_details` VALUES ('client', '', '$2a$10$lnIAuLiG3yezER5BfOk//uqPFHLnkwsFV9FxdzoIhATmZEAkK4yXO', 'read,write', 'password,refresh_token', '', 'ROLE_ADMIN,ROLE_USER', '7200', '7200', '{}', '');

等同于如下代码

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.jdbc(dataSource)
    		//密码登陆模式
            .withClient("client")
            .secret(new BCryptPasswordEncoder().encode("123456"))
            .authorizedGrantTypes("password", "refresh_token")//允许授权范围
            .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
            .scopes( "read", "write")
            .accessTokenValiditySeconds(7200)//token超时时间
            .refreshTokenValiditySeconds(7200);//token刷新的token超时时间
}

1)访问http://localhost:8078/users/current 资源受保护

SpringCloud权限设计_SpringCloud权限设计_04


2)获取token:http://localhost:8078/oauth/token?username=admin&password=admin&grant_type=password&client_id=client&client_secret=123456

SpringCloud权限设计_Zuul_05


3)使用令牌访问受限资源:

http://localhost:8078/users/current?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJleHAiOjE1NzQwNzQxODMsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImNmNWJjY2JkLWYyMTYtNDQ0MC04NWFlLTdhODgwMDAzZjI5OSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.nz6ygVySdEOpil95H-mbBiZhjQfaz-EKustePdtopzrmuFePHkh8WMNTyeWtfPGAjMIAMYeRDqxvcPg6-nWidhiAXdUogIYMluwi4k-YsFZ8HgzjK7mQNDFaL7kasvRKKcQ0127yQqgG40fmghhInZsj3uGyh4MNMdbIrRBYVgvNsctbrG3StjoHVOAHKTlHxrC9cGQ2D9WV95byarr_UoADFpwad4wux6fnWEOcEgzR2aAmI6YraaZBpPNYrz1LsuHEn2cUJiK-CgbyAt-9PiZzhT-ZIsAK0pCSVi3ALlIepoEMtQSZHlYhT20S_dtK8P3Fp3_-QMecaLfu9Ik2Ow

返回:

{
	"authorities": [{
		"authority": "ROLE_ADMIN"
	}],
	"details": {
		"remoteAddress": "0:0:0:0:0:0:0:1",
		"sessionId": null,
		"tokenValue": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJleHAiOjE1NzQwNzQxODMsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImNmNWJjY2JkLWYyMTYtNDQ0MC04NWFlLTdhODgwMDAzZjI5OSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.nz6ygVySdEOpil95H-mbBiZhjQfaz-EKustePdtopzrmuFePHkh8WMNTyeWtfPGAjMIAMYeRDqxvcPg6-nWidhiAXdUogIYMluwi4k-YsFZ8HgzjK7mQNDFaL7kasvRKKcQ0127yQqgG40fmghhInZsj3uGyh4MNMdbIrRBYVgvNsctbrG3StjoHVOAHKTlHxrC9cGQ2D9WV95byarr_UoADFpwad4wux6fnWEOcEgzR2aAmI6YraaZBpPNYrz1LsuHEn2cUJiK-CgbyAt-9PiZzhT-ZIsAK0pCSVi3ALlIepoEMtQSZHlYhT20S_dtK8P3Fp3_-QMecaLfu9Ik2Ow",
		"tokenType": "Bearer",
		"decodedDetails": null
	},
	"authenticated": true,
	"userAuthentication": {
		"authorities": [{
			"authority": "ROLE_ADMIN"
		}],
		"details": null,
		"authenticated": true,
		"principal": "admin",
		"credentials": "N/A",
		"name": "admin"
	},
	"oauth2Request": {
		"clientId": "client",
		"scope": ["read", "write"],
		"requestParameters": {
			"client_id": "client"
		},
		"resourceIds": [],
		"authorities": [],
		"approved": true,
		"refresh": false,
		"redirectUri": null,
		"responseTypes": [],
		"extensions": {},
		"refreshTokenRequest": null,
		"grantType": null
	},
	"credentials": "",
	"clientOnly": false,
	"principal": "admin",
	"name": "admin"
}

4)刷新令牌:

http://localhost:8078/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123456&refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJhdGkiOiJjZjViY2NiZC1mMjE2LTQ0NDAtODVhZS03YTg4MDAwM2YyOTkiLCJleHAiOjE1NzQwNzQxODMsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjI1YzU3NWYwLWU1MGEtNDU3MS1hY2ExLWM3ODQwNjc3OWQ2NCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.n_ravNt0iK2JnpoMcS0nf-FS_U1H9lJAYjoR6Mca6r5fGKynoHIxAIqcQsRXw7Qst-w_O6ZCSF_ieAC8MmVxkj6ZKloC7cQglVua0U33PVrheKL6OPcIYa8K5fX_w-VBgXOjoMFN-iRf3aWK19JRvbTXhi9z9_89-u1ciII4lDBoOPVomvKGdXMpXbj_SptAgeh9AYiKBh40vhCBw3g-AntmMZSUfkpkDNiVD2fkyueUyGPCyhMDcHF5xnFuyO6qrBOkwlnrw7kZ96m7346sAb_KQn2u2a1IPU4gysGs7-0zD9rKpAl5d774RWAFGOBXk_CFH2Wol9I6Vmm7R_T5yg`

服务网关sc-zuul-server

pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	</dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
	<dependency>
		<groupId>cn.springcloud.book</groupId>
		 <artifactId>cloud1.0-common</artifactId>
		 <version>${parent.version}</version>
    </dependency>
</dependencies>

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-resources-plugin</artifactId>
			<configuration>
				<nonFilteredFileExtensions>
					<nonFilteredFileExtension>cert</nonFilteredFileExtension>
				</nonFilteredFileExtensions>
			</configuration>
		</plugin>
	</plugins>
</build>

application.yml

eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
feign:
  hystrix:
    enabled: true
ribbon:
  ConnectTimeout: 6000
  ReadTimeout: 6000
  MaxAutoRetries: 0 #对第一次请求的服务的重试次数
  MaxAutoRetriesNextServer: 0 #要重试的下一个服务的最大数量(不包括第一个服务)
  OkToRetryOnAllOperations: false
zuul:
  sensitive-headers:
  ribbonIsolationStrategy: THREAD
  threadPool:
    useSeparateThreadPools: true
    threadPoolKeyPrefix: zuulgateway
  max:
    host:
      max-per-route-connections: 200
      max-total-connections: 500
  host:
    socket-timeout-millis: 5000
    connect-timeout-millis: 10000
hystrix:
  threadpool:
    default:
      coreSize: 20
      maximumSize: 50
      maxQueueSize: -1
      allowMaximumSizeToDivergeFromCoreSize: true
  command:
    default:
      execution:
        timeout:
          enabled: false
        isolation:
          thread:
            interruptOnTimeout: false
            timeoutInMilliseconds: 15000

AuthFilter

这里注意,不能把所有头部信息都在过滤器重新添加一遍,因为zuul本身在转发时会会我们添加头部,重复添加会抛出异常而导致访问失败(post),但是zuul本身会过滤掉一些敏感信息,所以我们需要在配置文件添加:zuul.sensitive-headers=(为空)
题外说一句,要是发现调用错误但是后台没有提示异常记得尝试把熔断注释掉,因为熔断会覆盖异常信息

package cn.springcloud.book.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义的鉴权filter
 */
public class AuthFilter extends ZuulFilter {
	private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
	
	@Override
	public boolean shouldFilter() {
		// 判断是否需要进行处理
		return true;
	}

	@Override
	public Object run() {
		RequestContext rc = RequestContext.getCurrentContext();
		authUser(rc);
		return null;
	}

	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 0;
	}
	
	//将request中Http请求头的所有信息存到一个Map<String, String>中
	private static Map<String, String> httpRequestToMap(HttpServletRequest request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;
    }
	
	//自定义的鉴权处理
	public static void authUser(RequestContext ctx) {
/*		HttpServletRequest request = ctx.getRequest();
		Map<String, String> header = httpRequestToMap(request);
		for (Map.Entry<String, String> entry : header.entrySet()) {
			ctx.addZuulRequestHeader(entry.getKey(), entry.getValue());
		}*/
	}
	
}

ZuulFallback

@Component
public class ZuulFallback implements FallbackProvider{

	@Override
	public String getRoute() {
		return "*"; //可以配置指定的路由,值为serviceId,如sc-user-service
	}

	@Override
	public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
		 return new ClientHttpResponse() {
	            @Override
	            public HttpStatus getStatusCode() throws IOException {
	                return HttpStatus.INTERNAL_SERVER_ERROR;
	            }

	            @Override
	            public String getStatusText() throws IOException {
	                return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
	            }

	            @Override
	            public void close() {
	            }
	           
	            @Override
	            public InputStream getBody() throws IOException {
	            	  //定义自己的错误信息
	                return new ByteArrayInputStream(("microservice error").getBytes());
	            }

	            @Override
	            public HttpHeaders getHeaders() {
	                HttpHeaders headers = new HttpHeaders();
	                headers.setContentType(MediaType.APPLICATION_JSON);
	                return headers;
	            }

				@Override
				public int getRawStatusCode() throws IOException {
					// TODO Auto-generated method stub
					return HttpStatus.INTERNAL_SERVER_ERROR.value();
				}
	        };

	}

}

ResourceServerConfiguration

这里注意converter.setVerifier(new RsaVerifier(publicKey))的写法,使用converter.setVerifierKey(publicKey)时,通过令牌访问受限资源会出现 Cannot convert access token to JSON的提示

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

	public static final String public_cert = "public.cert";
	
	public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource =  new ClassPathResource(public_cert);
        String publicKey;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
        //使用converter.setVerifierKey(publicKey);会出现 Cannot convert access token to JSON
        converter.setVerifier(new RsaVerifier(publicKey));
        return converter;
    }
	
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/sc-user-server/**","/sc-auth-server/**","/actuator/hystrix.stream").permitAll()
                .antMatchers("/**").authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore());
    }
}

ZuulServerApplication

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableCircuitBreaker
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
    
	@Bean
	public AuthFilter preRequestFilter() {
		return new AuthFilter();
	}
}

用户服务sc-user-server

pom.xml

<dependencies>
        <dependency>
			<groupId>cn.springcloud.book</groupId>
			 <artifactId>cloud1.0-common</artifactId>
			 <version>${parent.version}</version>
        </dependency>
        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		</dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>

application.yml

spring:
  application:
    name: sc-user-server
  datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: update
    show-sql: true
    
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
feign:
  hystrix:
    enabled: true
ribbon:
  ConnectTimeout: 6000
  ReadTimeout: 6000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0
hystrix:
  command:
    default:
      execution:
        timeout:
        isolation:
          thread:
            timeoutInMilliseconds: 15000
security:
  oauth2:
    resource:
      jwt:
        key-uri: http://127.0.0.1:7777/sc-auth-server/oauth/token_key	#如果使用JWT,可以获取公钥用于 token 的验签
    client:
      access-token-uri: http://127.0.0.1:7777/sc-auth-server/oauth/token	#令牌端点
      user-authorization-uri: http://127.0.0.1:7777/sc-auth-server/oauth/authorize	#授权端点
      client-id: client_2
      client-secret: 123456
      grant-type: password
      scope: server
server:
  port: 8087

启动类UserServerApplication

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class UserServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServerApplication.class, args);
    }
    
}

基础代码这里不贴了
资源服务器配置ResourceServerConfiguration,开放登陆与注册接口

@EnableGlobalMethodSecurity(prePostEnabled = true)开启方法级别访问权限

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)//控制方法的访问权限
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

	public static final String public_cert = "public.cert";
	
	@Bean
	public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource =  new ClassPathResource(public_cert);
        String publicKey;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
        //使用converter.setVerifierKey(publicKey);会出现 Cannot convert access token to JSON
        converter.setVerifierKey(publicKey);
        return converter;
    }
	
	@Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/product/**","/registry/**", "/user/login/**","/actuator/hystrix.stream").permitAll()
                .antMatchers("/**").authenticated();
                //.antMatchers("/**").permitAll();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore());
    }
}

接口
(1)注册用户

@RequestMapping(value = "/registry", method = RequestMethod.GET)
public SysUser createUser(@RequestParam("username") String username, @RequestParam("password") String password) {
    if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
        return userService.create(username, password);
    }
    return null;
}

(2)用户登陆获取令牌

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private SysUserRepository sysUserRepository;

    @Autowired
    private OAuth2ClientProperties oAuth2ClientProperties;

    @Autowired
    private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/login")
    public ResponseEntity<OAuth2AccessToken> login(@Valid UserLoginParamDto loginDto, BindingResult bindingResult) throws Exception {

        if (bindingResult.hasErrors())
            throw new Exception("登录信息错误,请确认后再试");

        SysUser user = sysUserRepository.getUserByName(loginDto.getUsername());

        if (null == user)
            throw new Exception("用户为空,出错了");

        if (!BPwdEncoderUtil.matches(loginDto.getPassword(), user.getPassword()))
            throw new Exception("密码不正确");

        String client_secret = oAuth2ClientProperties.getClientId()+":"+oAuth2ClientProperties.getClientSecret();

        client_secret = "Basic "+Base64.getEncoder().encodeToString(client_secret.getBytes());
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("Authorization",client_secret);

        //授权请求信息
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.put("username", Collections.singletonList(loginDto.getUsername()));
        map.put("password", Collections.singletonList(loginDto.getPassword()));
        map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType()));

        map.put("scope", oAuth2ProtectedResourceDetails.getScope());
        //HttpEntity
        HttpEntity httpEntity = new HttpEntity(map,httpHeaders);
        //获取 Token(注意先配置security)
        return restTemplate.exchange("http://sc-auth-server/oauth/token", HttpMethod.POST,httpEntity,OAuth2AccessToken.class);//访问/oauth/token获取令牌

    }
}

(3)通过令牌获取用户信息

@GetMapping("/getPrinciple")
public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication) {
    logger.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
    logger.info(oAuth2Authentication.toString());
    logger.info("principal.toString() " + principal.toString());
    logger.info("principal.getName() " + principal.getName());
    logger.info("authentication: " + authentication.getAuthorities().toString());

    return oAuth2Authentication;
}

(4)测试权限控制接口

@PreAuthorize("hasAnyRole('ADMIN')")
    @RequestMapping("/helloRole")
    public String helloRole() {
        return "hello you.by ADMIN";
    }

(5)开放测试接口

@RequestMapping("/product/{id}")
public String getProduct(@PathVariable String id) {

    String dbpasswor = "$2a$10$HBX6q6TndkgMxhSEdoFqWOUtctaJEMoXe49NWh8Owc.4MTunv.wXa";

    logger.info("判断两个密码是否相等 " + (BPwdEncoderUtil.matches("123456", dbpasswor)));

    return "product id : " + id;
}

测试

依次启动sc-eurekaserver、sc-auth-server、sc-zuul-server、sc-user-server

访问http://localhost:8761/

SpringCloud权限设计_Zuul_06


(1)测试通过网关访问授权服务器

获取令牌

http://localhost:7777/sc-auth-server/oauth/token?username=admin&password=admin&grant_type=password&client_id=client&client_secret=123456

通过令牌访问受限接口

http://localhost:7777/sc-auth-server/users/current?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJleHAiOjE1NzUzNzI4NTcsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjhiZmJmMTI2LTkxZmItNGQxNy05MDk0LTM5NWQ4MjZmYWJjMSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.SGkzpqjMqftcsWdZshxGcA7VwbbGX3zE-fHBAvLF1bCtMctW3Fsfjfh_HGiU_gU1RyV7eiETFCaQRPl6zdO4AnBoXyav20DdBOq7D_cjgqp-aPtGu7swZFiRRg1AKbKyYmRRHTOeYEp2XF7uScD1RacqeECVt6Pj-Q9B7YNDlY_YiY94n1zCJLrhnscu1K2L-rvGx2G2AMZrVbKtZD1K1VTGNZXPz99Us9Dnow8-Cu5-w0U8x4dvQl1dlPgG7NL0dqX4_mNWUPcX4bMFqxsFY7-8cLQjT2LtzgIhJid9zY4RmqpXfujbOS_EP_o-KCdmA64aEzCkcNQgCUweVZKRlA

刷新令牌

http://localhost:7777/sc-auth-server/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123456&refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJhdGkiOiJiMTY0MWM1Mi1iNjdlLTQ5ZmYtOWY4OS1lNmI3YWUzYjQyMTUiLCJleHAiOjE1NzUwMjYyNTgsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImMzY2IxYTNkLTJmY2EtNDk1MS1iZDkxLWY3NzcxNjlkMTE5MSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.GhEHazkUZ8-KLpSBVugFvHoCYdUs-l8OSyVIEYBSXZPLY1ku6j4Fu8laRJy3JnovmmL72erPwdPXIcshcqoflZmE0FJn29ibIxyNvNqd5BbJroidwQJNQ_0u0RaMw0r1DQDtKMDBQv2Ajc6rgAcqR874duMb5Ehx9WUlmw5zEmF7SEKSP1TT_4MnVfwDumQHeBafrnVS7nqddgEckvMaTXbuhyY-0cr8C_56W1bE6tW_sbTF9HMly3nH65jlWiHRR43iAyGf2juEmFoKg2VwaKDTwBheiLwX2RTMZ5hhMhP_XkT2Ea6-2sjdVegKeNLcl7AYS47yq6PyKf4FCTSnyw

如果代码正确返回结果应和授权服务器一章的测试结果一致
(2)访问sc-user-server的开放接口

http://localhost:7777/sc-user-server/product/Hello

返回:

SpringCloud权限设计_JWT_07


(3)注册用户

http://localhost:7777/sc-user-server/registry?username=OneTest&password=OneTest

返回:

SpringCloud权限设计_SpringCloud_08


(4)通过该用户获取令牌

http://localhost:7777/sc-user-server/user/login?username=OneTest&password=OneTest

返回:

{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJPbmVUZXN0Iiwic2NvcGUiOlsic2VydmVyIl0sInJvbGVzIjpbXSwib3JnYW5pemF0aW9uIjoiT25lVGVzdCIsImV4cCI6MTU3NTA2MzUzMCwidXNlck5hbWUiOiJPbmVUZXN0IiwianRpIjoiOGVhM2ZkMjYtNTI0NS00MDZhLWI3YjctOWUwMDU5MTg4NjcwIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.esPN-hxIMYCyNFROeQvfDv1nwwxasiYilT51XY3VlalXJdyr4soKFw1Ivy-VV1WQUgcbl1aUD0oejHrd89XobHEym8vF2EY04DwDgiLpl7-8ORxqxMnlqeqoT95jURaMTRhHRyls6e6mv0JOecUE0LU_kOlqQjw9z7XiQTzVjdAku0zMstQjyV078vneoiYE-9xEyHG-_-eVLfXvYztVx86-EJbCPK4jEJndU6dCL0VKa_tUF6q_gMWzAzNYktWuBnjL_nQkwiFQ1Vq4b8l5S--3ZL60Ql-Mm_tuAMIdxH5H7TXdDPv4ewVxHDbUOO-3yfBWknjNzhqY5syHlQ_uWA","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJPbmVUZXN0Iiwic2NvcGUiOlsic2VydmVyIl0sInJvbGVzIjpbXSwib3JnYW5pemF0aW9uIjoiT25lVGVzdCIsImF0aSI6IjhlYTNmZDI2LTUyNDUtNDA2YS1iN2I3LTllMDA1OTE4ODY3MCIsImV4cCI6MTU3NzYxMjMzMCwidXNlck5hbWUiOiJPbmVUZXN0IiwianRpIjoiYjA4YzU5MGMtMGI5NC00MDY5LTg3ZWUtYjVhNjc4ODE4ZWJiIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.bb9tpoMVQfQj6DKg9MgKBm6fbcI_iIzbhI0vHLb-5RVwYuIFTLC5m7wjMaPs_MZHpQ6YI9by6CR5V68zw1P-sDJmS8n1JBnnhAh4RtzTbqJln7DB4k3ZunoubKERXZCnlQgF_x1YfqyZj8Fly-XvrrUevIyY68vVZJCVXWf2-yojX5Y-WowA_Sj2bnquBFDNuhisc-Lz4eR9mNLuQptu33ewRUTUf4NfwOCQ9wtZbvqknavy0jjZATXWGv2aUOJeYwy3RMfBkYjPSdvQbyLUXtCwc4wJ2aglfu8j8qmMU8gHnAkVHQEBIkOgUD18K7d3By-rZsEt_2T9TKnsi6GnUQ","expires_in":43198,"scope":"server","roles":[],"organization":"OneTest","userName":"OneTest","jti":"8ea3fd26-5245-406a-b7b7-9e0059188670"}

(5)通过令牌访问受限资源

http://localhost:7777/sc-user-server/getPrinciple?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJPbmVUZXN0Iiwic2NvcGUiOlsic2VydmVyIl0sInJvbGVzIjpbXSwib3JnYW5pemF0aW9uIjoiT25lVGVzdCIsImV4cCI6MTU3NTQ2OTgyMiwidXNlck5hbWUiOiJPbmVUZXN0IiwianRpIjoiMmU4YWI5YTgtYzZjYi00MmY5LWE4OTktYjZjM2M0MmVmZDQwIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.Jb9EQKtWeCE_50N2hQrGlJnY2FBQAjhOBLEhXOpVWekvGGpTUZ7YiapA8M9mqm49Nwg9cFYYSP_sUcm-BGM5sPZTJ5HkclVBd7NHJPpRyALB6rOIr0jR9i9b8AgwV0F-hPsh3cwaf2dZR3aARmLwM-zMZFnsD7-J0fbgCLIdQTcWb8XfRqULukYRp_D8ql4HV5IYg7zmQsNmgYghvo3Nf50PKr7q1ivCxoFGjOXyrm0uoKZZ5DZB3aXcQHDR_Zg8AWsJfctQ-YGexxY6jRPUKuOSI0_LPNSZ8VnfYH8DKfV0BZnnnH40A7OdN3GaXg-1i1bkgv-6yF4l5yvbVKknww

返回:

{"authorities":[],"details":{"remoteAddress":"192.168.90.111","sessionId":null,"tokenValue":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJPbmVUZXN0Iiwic2NvcGUiOlsic2VydmVyIl0sInJvbGVzIjpbXSwib3JnYW5pemF0aW9uIjoiT25lVGVzdCIsImV4cCI6MTU3NTQ2OTgyMiwidXNlck5hbWUiOiJPbmVUZXN0IiwianRpIjoiMmU4YWI5YTgtYzZjYi00MmY5LWE4OTktYjZjM2M0MmVmZDQwIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.Jb9EQKtWeCE_50N2hQrGlJnY2FBQAjhOBLEhXOpVWekvGGpTUZ7YiapA8M9mqm49Nwg9cFYYSP_sUcm-BGM5sPZTJ5HkclVBd7NHJPpRyALB6rOIr0jR9i9b8AgwV0F-hPsh3cwaf2dZR3aARmLwM-zMZFnsD7-J0fbgCLIdQTcWb8XfRqULukYRp_D8ql4HV5IYg7zmQsNmgYghvo3Nf50PKr7q1ivCxoFGjOXyrm0uoKZZ5DZB3aXcQHDR_Zg8AWsJfctQ-YGexxY6jRPUKuOSI0_LPNSZ8VnfYH8DKfV0BZnnnH40A7OdN3GaXg-1i1bkgv-6yF4l5yvbVKknww","tokenType":"Bearer","decodedDetails":null},"authenticated":true,"userAuthentication":{"authorities":[],"details":null,"authenticated":true,"principal":"OneTest","credentials":"N/A","name":"OneTest"},"credentials":"","oauth2Request":{"clientId":"client_2","scope":["server"],"requestParameters":{"client_id":"client_2"},"resourceIds":[],"authorities":[],"approved":true,"refresh":false,"redirectUri":null,"responseTypes":[],"extensions":{},"refreshTokenRequest":null,"grantType":null},"clientOnly":false,"principal":"OneTest","name":"OneTest"}

(6)测试权限控制接口
访问该接口需要ADMIN角色(目前我们注册的用户没有任何角色和权限)

http://localhost:7777/sc-user-server/helloRole?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJPbmVUZXN0Iiwic2NvcGUiOlsic2VydmVyIl0sInJvbGVzIjpbXSwib3JnYW5pemF0aW9uIjoiT25lVGVzdCIsImV4cCI6MTU3NTQ2OTgyMiwidXNlck5hbWUiOiJPbmVUZXN0IiwianRpIjoiMmU4YWI5YTgtYzZjYi00MmY5LWE4OTktYjZjM2M0MmVmZDQwIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.Jb9EQKtWeCE_50N2hQrGlJnY2FBQAjhOBLEhXOpVWekvGGpTUZ7YiapA8M9mqm49Nwg9cFYYSP_sUcm-BGM5sPZTJ5HkclVBd7NHJPpRyALB6rOIr0jR9i9b8AgwV0F-hPsh3cwaf2dZR3aARmLwM-zMZFnsD7-J0fbgCLIdQTcWb8XfRqULukYRp_D8ql4HV5IYg7zmQsNmgYghvo3Nf50PKr7q1ivCxoFGjOXyrm0uoKZZ5DZB3aXcQHDR_Zg8AWsJfctQ-YGexxY6jRPUKuOSI0_LPNSZ8VnfYH8DKfV0BZnnnH40A7OdN3GaXg-1i1bkgv-6yF4l5yvbVKknww

返回:

SpringCloud权限设计_Zuul_09


使用admin用户登陆拿到令牌,再访问该接口

http://localhost:7777/sc-user-server/user/login?username=admin&password=admin
http://localhost:7777/sc-user-server/helloRole?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInNlcnZlciJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwib3JnYW5pemF0aW9uIjoiYWRtaW4iLCJleHAiOjE1NzU0NzAzNzQsInVzZXJOYW1lIjoiYWRtaW4iLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6Ijc5OTc1NGM5LWU5Y2UtNDc5Mi05YjVlLWUzODYwNGQ2ODA4MyIsImNsaWVudF9pZCI6ImNsaWVudF8yIn0.d_MmIr05z_DsEhW9eb1LHLr90E4EWw3lnJzIo2iI9ZAfAB80HkkwvKMT4e2_oVVTeB5zvQtdJdjHf2OgaXm8oTc0ktzdpfi83jwVLBhH19Obpi1LEtgupI6dYh7KkSzpBhCwn_ObFKBDz2H_xJE58c5_dwA_Id1jpqvS7vjK0egoZeyPShIp-dq0GHwzUMntfl28XN7qLi9q5NBPvFl7bgKqmiycnaNBBRfQBpOntZWiO_BRZD8aEvc8kNwO2j5ywzA3mfFaXUW26jKEb4hPfj5_ibeN-S5YInIoe33wTqEdeZysq_qJN2EkAFzf0D_-QuLT9w8AH_PG05MteBfx-g

返回:

SpringCloud权限设计_SpringCloud_10


至此Zuul整合SpringSecurityOAuth2(JWT)完成

熔断监控sc-hystrix-dashboard

pom.xml

<dependencies>
     <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
	</dependency> 
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	</dependency>
    <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
	</dependency>
	 <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
</dependencies>

application.yml

server:
  port: 9099
spring:
  cloud:
    config:
      label: master
      uri: http://localhost:9090
      name: config-info
      profile: dev
  application:
    name: sc-hystrix-dashboard
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
turbine:
  appConfig: sc-auth-server,sc-user-server,sc-zuul-service	#指定聚合哪些集群,多个使用”,”分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问
  clusterNameExpression: "'default'"

HystrixDashboardTurbineApplication.java

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard
@EnableTurbine
public class HystrixDashboardTurbineApplication {

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

由于HystrixDashboard是对于熔断进行监控,为了方便我们进行测试,所以我们对sc-user-server的登陆接口添加熔断器(application.yml中我们已经添加过熔断器的配置了)

@RequestMapping("/login")
@HystrixCommand(fallbackMethod = "loginError")
public ResponseEntity<OAuth2AccessToken> login(@Valid UserLoginParamDto loginDto, BindingResult bindingResult) throws Exception {

    if (bindingResult.hasErrors())
        throw new Exception("登录信息错误,请确认后再试");

    SysUser user = sysUserRepository.getUserByName(loginDto.getUsername());

    if (null == user)
        throw new Exception("用户为空,出错了");

    if (!BPwdEncoderUtil.matches(loginDto.getPassword(), user.getPassword()))
        throw new Exception("密码不正确");

    String client_secret = oAuth2ClientProperties.getClientId()+":"+oAuth2ClientProperties.getClientSecret();

    client_secret = "Basic "+Base64.getEncoder().encodeToString(client_secret.getBytes());
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.set("Authorization",client_secret);

    //授权请求信息
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.put("username", Collections.singletonList(loginDto.getUsername()));
    map.put("password", Collections.singletonList(loginDto.getPassword()));
    map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType()));

    map.put("scope", oAuth2ProtectedResourceDetails.getScope());
    //HttpEntity
    HttpEntity httpEntity = new HttpEntity(map,httpHeaders);
    //获取 Token(注意先配置security)
    return restTemplate.exchange("http://sc-auth-server/oauth/token", HttpMethod.POST,httpEntity,OAuth2AccessToken.class);//访问/oauth/token获取令牌

}

//登陆熔断
public ResponseEntity<OAuth2AccessToken> loginError(@Valid UserLoginParamDto loginDto, BindingResult bindingResult) throws Exception {
	throw new Exception("登陆服务访问失败");
}

测试

http://localhost:9099/hystrix

SpringCloud权限设计_SpringSecurityOAuth2_11


在第一个输入框输入http://localhost:9099/turbine.stream,点击MonitorStream获取集群监控列表

输入http://localhost:port/actuator/hystrix.stream,点击MonitorStream获取获取单个熔断监控

访问http://localhost:7777/sc-user-server/user/login?username=OneTest&password=OneTest 查看集群监控列表

SpringCloud权限设计_SpringCloud_12

补充:使用Feign/RestTemplate调用服务时的权鉴处理与服务调用案例

权鉴处理

由于项目整合了CloudOAuth2,所以当我们使用Feign/RestTemplate进行服务间的相互调用时我们需要考虑到令牌传递的问题
SpringSecurityOAuth2通过token访问受限资源一般有两种方式,一种是通过头部添加Authorization凭证,一种是直接添加access_token作为参数,但是在SpringSecurityOAuth2框架中后者最终也是转换成Authorization凭证用于权鉴,所以我们只要在Feign/RestTemplate的过滤器中传递头部信息就能解决服务内部资源获取的权鉴问题
FeignUserContextInterceptor

public class FeignUserContextInterceptor implements RequestInterceptor {
	@Override
	public void apply(RequestTemplate template) {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		//Feign传递头部信息
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
            }
        }
	}

}

RestTemplateUserContextInterceptor

public class RestTemplateUserContextInterceptor implements ClientHttpRequestInterceptor {

	@Override
	public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
			throws IOException {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest req = attributes.getRequest();
		//RestTemplate传递头部信息
		HttpHeaders headers = request.getHeaders();
        Enumeration<String> headerNames = req.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = req.getHeader(name);
                headers.add(name, values);
            }
        }
        //RestTemplate实现session共享
        String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
        if (null != sessionId) {
        	request.getHeaders().add("Cookie", "SESSION=" + sessionId);
        }
		return execution.execute(request, body);
	}
}

直接修改完发现一个问题,RequestContextHolder.getRequestAttributes()返回为null,查看源码后发现RequestContextHolder.getRequestAttributes()方法是从ThreadLocal变量里面取得相应信息的

SpringCloud权限设计_SpringCloud_13


SpringCloud权限设计_SpringCloud_14


ThreadLocal是当前线程中的局部变量,当hystrix断路器的隔离策略为THREAD时服务于服务间的线程是不同的,因此我们在Feign/RestTemplate过滤器中无法取得ThreadLocal中的值。一种解决方案是将hystrix隔离策略换为SEMAPHORE(不推荐),一种是自定义隔离策略,这里我们采用第二种方案

在cloud1.0-common工程中添加自定义隔离策略

SpringCloudHystrixConcurrencyStrategy

public class SpringCloudHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    private HystrixConcurrencyStrategy delegateHystrixConcurrencyStrategy;
    
    //核心代码,自定义处理
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new HystrixThreadCallable<>(callable, RequestContextHolder.getRequestAttributes(),SwapContextHolder.context.get());
    }
    
    public SpringCloudHystrixConcurrencyStrategy() {
    	init();
    }
    
    private void init() {
   	 try {
            this.delegateHystrixConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegateHystrixConcurrencyStrategy instanceof SpringCloudHystrixConcurrencyStrategy) {
                return;
            }

            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();

            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        }
        catch (Exception e) {
            throw e;
        }
    }
       
    @Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
			HystrixProperty<Integer> corePoolSize,
			HystrixProperty<Integer> maximumPoolSize,
			HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
			BlockingQueue<Runnable> workQueue) {
		return this.delegateHystrixConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
				keepAliveTime, unit, workQueue);
	}

	@Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
			HystrixThreadPoolProperties threadPoolProperties) {
		return this.delegateHystrixConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties);
	}

	@Override
	public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
		return this.delegateHystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);
	}

	@Override
	public <T> HystrixRequestVariable<T> getRequestVariable(
			HystrixRequestVariableLifecycle<T> rv) {
		return this.delegateHystrixConcurrencyStrategy.getRequestVariable(rv);
	}

}

HystrixThreadCallable

public class HystrixThreadCallable<S> implements Callable<S>{
	 private final RequestAttributes requestAttributes;  
	 private final Callable<S> delegate;
	 private Map<String, Object> params;
	 
     public HystrixThreadCallable(Callable<S> callable, RequestAttributes requestAttributes,Map<String, Object> params) {  
         this.delegate = callable; 
         this.requestAttributes = requestAttributes;  
         this.params = params;
     }

     @Override  
     public S call() throws Exception {
         try {
             RequestContextHolder.setRequestAttributes(requestAttributes);
             SwapContextHolder.context.set(params);
             //System.out.println(">>>>>>>>>>>>>>>>>>."+params+".<<<<<<<<<<<<<<<<<<<<");
             return delegate.call();  
         } finally {
             RequestContextHolder.resetRequestAttributes();
             SwapContextHolder.context.remove();
         }  
     }  
       
}

在HystrixThreadCallable中我们除了将RequestAttributes进行线程间的传递我们还传入了SwapContextHolder内的资源,该对象主要是为了方便后续我们在服务于服务间传递变量而做的一个扩展
SwapContextHolder

public class SwapContextHolder {
	
	public static ThreadLocal<Map<String, Object>> context = new ThreadLocal<Map<String, Object>>();
	
	public static Map<String, Object> currentSwap() {
		return context.get();
	}

	public static void set(Map<String, Object> swap) {
		context.set(swap);
	}

	public static void shutdown() {
		context.remove();
	}
	
}

这里注意一点,我们原先在zuul上添加了zuul.sensitive-headers.ribbonIsolationStrategy=THREAD的配置,需要把这里注释掉,不然自定义策略在zuul网关将无法生效

feign

cloud1.0-user-server工程

DataService.java

@FeignClient(name = "sc-data-service", fallback=UserClientFallback.class)
public interface DataService {
	
	@RequestMapping("/getService")
	public String getService(@RequestParam(value = "request")String  request);
	
    //返回配置文件内配置的默认用户
    @GetMapping("/getDefaultUser")
    public String getDefaultUser();
    
    @RequestMapping(value = "/getProviderData")
    public List<String> getProviderData();

    @RequestMapping(value = "/getUser")
    public String getUser();
}

熔断UserClientFallback.java

@Component
public class UserClientFallback implements DataService{
	
	@Override
	public String getService(@RequestParam(value = "request")String  request) {
		return "hello," +request+", this messge send failed ";
	}
	@Override
	public String getDefaultUser() {
		return new String("get getDefaultUser failed");
	}
	@Override
	public List<String> getProviderData() {
		List<String> list = new ArrayList<>();
		list.add("get getProviderData failed");
		return list;
	}
	@Override
	public String getUser() {
		return new String("get getUser failed");
	}
	
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private DataService dataService;
    ......
	@Override
	public String getService(String request) {
		return dataService.getService(request);
	}
    
	@Override
	public String getDefaultUser() {
		return dataService.getDefaultUser();
	}
	
	@Override
	public String getUser() {
		return dataService.getUser();
	}
}

RestTemplate

cloud1.0-user-server工程
UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private RestTemplate restTemplate;
    ......
	@Override
	@HystrixCommand(fallbackMethod = "getProviderDataError")
	public List<String> getProviderData() {
		List<String> result = restTemplate.getForObject("http://sc-data-service/getProviderData", List.class);
		return result;
	}
	public List<String> getProviderDataError() {
		List<String> list = new ArrayList<String>();
		list.add("getFail");
		return list;
	}

	@Override
	@HystrixCommand(fallbackMethod = "getProviderDataByPostError")
	public List<String> getProviderDataByPost() {
		MultiValueMap<String, Object> param = new LinkedMultiValueMap<String, Object>();
		param.add("serviceName", "serviceName");
		param.add("serviceValue", "serviceValue");
//		HttpHeaders headers = new HttpHeaders();
//		headers.add("Content-Type", "application/x-www-form-urlencoded");
//		HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<MultiValueMap<String, Object>>(param, headers);
		//含有请求头
		//List<String> result = restTemplate.postForObject("http://sc-data-service/getProviderDataByPost", formEntity, List.class);
		//不含请求头
		List<String> result = restTemplate.postForObject("http://sc-data-service/getProviderDataByPost", param, List.class);
		return result;
	}
	public List<String> getProviderDataByPostError() {
		List<String> list = new ArrayList<String>();
		list.add("getFail");
		return list;
	}

	@Override
	@HystrixCommand(fallbackMethod = "getUserByRestTemplateError")
	public String getUserByRestTemplate() {
		return restTemplate.postForObject("http://sc-data-service/getUser", null, String.class);
	}
	public String getUserByRestTemplateError() {
		return new String("getUserByRestTemplateError");
	}
}

至此一个微服务的基本架构整合完成