0%

SpringCloud微服务快速入坑

开始

关键词:Spring Cloud、Spring Boot、Eureka、Zuul、Feign、Oauth2

初入服务端,菜鸟一枚

Spring Cloud 是基于Spring Boot的一整套完善的微服务框架,包含服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等一系列组件,能够简单快速的的入坑微服务架构。

Spring Cloud的版本与Spring Boot有一定的对应关系,截至目前最新的稳定版本应该是Spring Cloud Greenwich + Spring Boot 2.1.x

sample

服务治理

为什么需要服务治理

随着业务不断增长,为了追求更高的性能支撑业务,集群的引入使得服务架构的复杂度大大提升。庞大的集群容易出现各种各样的问题:

  1. 过多的服务URL配置困难
  2. 负载均衡分配节点压力过大的情况下也需要部署集群
  3. 服务依赖混乱,启动顺序不清晰
  4. 过多服务导致性能指标分析难度较大,需要监控

简单来说主要是通过服务治理可以通过服务名来访问服务,不需要通过url来直接访问,这样子可以有利于负载均衡实现与服务间解耦。

Eureka 入门

  1. maven依赖
1
2
3
4
5
6
7
8
9
10
<!-- 服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 服务端application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
# 服务名
name: eureka-server

server:
# 端口
port: 8001

eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
  1. 客户端application.yml
1
2
3
4
5
6
7
8
9
10
spring:
application:
# 服务名
name: xxx

eureka:
client:
serviceUrl:
# 指定服务注册中心的位置
defaultZone: http://localhost:8001/eureka/
  1. 服务端注解@EnableEurekaServer
1
2
3
4
5
6
7
8
9
10
11
12
// import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

// 启动一个服务注册中心提供给其他应用进行对话
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {

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

}
  1. 客户端注解@EnableEurekaClient
1
2
3
4
5
6
7
8
9
@EnableEurekaClient
@SpringBootApplication
public class EurekaServiceApplication {

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

}
  1. 发现服务(查看已注册服务)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@RestController
public class DcController {

@Autowired
DiscoveryClient mDiscoveryClient;

@GetMapping("/dc")
public Result dc() throws Exception {
// 发现服务
String services = "Services: " + mDiscoveryClient.getServices();
log.info(services);
return ResultUtils.resultData(ResultEnum.SUCCESS, "eureka-client返回的数据:" + services);
}
}
  1. 其他

网页直接访问服务端可以查看当前已经注册了哪些服务

ex: http://localhost:8001/

服务网关

简介

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API,具备服务路由、过滤、负载均衡等功能,也可以实现用户认证功能。目前用的比较多的有Zuul、Spring Cloud Gateway,Spring Cloud Gateway依赖Spring Boot和Spring Webflux提供的Netty runtime,是目前官方推荐的网关,但是我在使用过程中OAuth出现了问题,所以还是用的Zuul。

Zuul

  1. maven依赖
1
2
3
4
5
6
7
8
9
<!-- eureka客户端 -->
<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-zuul</artifactId>
</dependency>
  1. application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server:
port: 8002

spring:
application:
# 指定微服务的名称
name: api-gateway

zuul:
host:
connect-timeout-millis: 20000
socket-timeout-millis: 20000
ignoredServices: '*'
prefix: /api # 设置一个公共的前缀
routes:
auth-service:
path: /auth/**
sensitiveHeaders:
serviceId: service-auth
consumer-service:
path: /consumer/**
sensitiveHeaders:
serviceId: eureka-consumer
client-service:
path: /client/**
sensitiveHeaders:
serviceId: eureka-client
add-proxy-headers: true
include-debug-header: true

eureka:
client:
serviceUrl:
# 指定服务注册中心的位置
defaultZone: http://localhost:8001/eureka/

logging:
level:
com.netflix: DEBUG
  1. 添加@EnableZuulProxy注解启动网关服务
1
2
3
4
5
6
7
8
9
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ApiZuulApplication {

public static void main(String[] args) {
SpringApplication.run(ApiZuulApplication.class, args);
}
}
  1. 其它

如果不使用路由,直接通过服务名访问服务,我在测试过程中遇到了Oauth一直显示认证失败的问题。

服务通信

简介

服务间通信实际上是通过Url(RestFul)来进行通信的,通过服务治理我们可以通过服务名等方式进行服务间通信

以下方式均调用的其他服务的同一个方法,服务名为eureka-client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@RestController
public class DcController {

@Autowired
DiscoveryClient mDiscoveryClient;

@GetMapping("/dc")
public Result dc() throws Exception {
// 发现服务
String services = "Services: " + mDiscoveryClient.getServices();
log.info(services);
return ResultUtils.resultData(ResultEnum.SUCCESS, "eureka-client返回的数据:" + services);
}
}

LoadBalancerClient

LoadBalancerClient是带有负载均衡的最基础的服务间通信组件

  1. 启动配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 加入到服务治理
@EnableEurekaClient
@SpringBootApplication
public class Application {

// 初始化RestTemplate,用来真正发起REST请求
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
  1. 使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
@RequestMapping("/lbc")
@RestController
public class LbcController {

@Autowired
LoadBalancerClient loadBalancerClient;
@Autowired
RestTemplate restTemplate;

/**
* 通过loadBalancerClient的choose函数来负载均衡的选出一个eureka-client的服务实例,
* 这个服务实例的基本信息存储在ServiceInstance中,然后通过这些对象中的信息拼接出访问/dc接口的详细地址,
* 最后再利用RestTemplate对象实现对服务提供者接口的调用。
*/
@GetMapping("/consumer")
public Result dc() {
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc";
log.info(url);
return restTemplate.getForObject(url, Result.class);
}
}

Ribbn

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。

  1. maven依赖
1
2
3
4
5
6
<!-- Ribbn -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
  1. 启动配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 相对于LoadBalancerClient只添加一个@LoadBalanced的注解
@EnableEurekaClient
@SpringBootApplication
public class Application {

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
  1. 使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 相对于LoadBalancerClient调用方式简化了一点点
@Slf4j
@RequestMapping("/ribbon")
@RestController
public class RibbonController {

@Autowired
RestTemplate restTemplate;

@GetMapping("/consumer")
public Result consumer() {
return restTemplate.getForObject("http://eureka-client/dc", Result.class);
}
}

Feign

这个是我比较喜欢的方式,调用简单,也可以通过RequestInterceptor统一设置Header用来做用户认证

  1. maven依赖
1
2
3
4
5
6
<!-- Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
  1. 启动配置,只需要添加一个注解即可
1
2
3
4
5
6
7
8
9
10
11
// 通过@EnableFeignClients注解开启扫描Spring Cloud Feign客户端的功能
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class EurekaConsumerApplication {

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

}
  1. 使用方法

  2. 第一步先定义一个接口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 创建一个Feign的客户端接口定义。
* 使用@FeignClient注解来指定这个接口所要调用的服务名称,
* 接口中定义的各个函数使用Spring MVC的注解就可以来绑定服务提供方的REST接口
* <p>
*
* @author 张钦
* @date 2019/10/31
*/
@FeignClient(name = "eureka-client")
public interface DcClient {

@GetMapping("/dc")
Result consumer();

}
  1. 第二步直接调用定义的接口就可以
1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/feign")
@RestController
public class FeignController {

@Autowired
DcClient mDcClient;

@GetMapping("/consumer")
public Result consumer() {
return mDcClient.consumer();
}
}
  1. 通过RequestInterceptor拦截器为服务请求添加Oauth2认证参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class SecuringRequestInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String authorization = request.getHeader("Authorization");
if (!StringUtils.isEmpty(authorization)) {
requestTemplate.header("Authorization", authorization);
}
}
}

Oauth2.0

Oauth2.0认证占用本文大半篇幅,但也是简单使用,后期准备在补一篇完整的Spring Cloud Oauth2使用水文。
Spring Cloud Oauth依赖了Spring Security,所以角色权限认证均使用的Spring Security的方式认证。
本文仅使用Redis存储Oauth2相关数据

授权服务

  1. 授权服务器maven文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 监控系统健康情况 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 对spring-cloud-starter-security、spring-security-oauth2、spring-security-jwt这3个依赖的整合 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
  1. Oauth2有三张基础表,可以根据业务增加字断,表结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
KEY `FK859n2jvi8ivhui0rl0esws6o` (`user_id`),
CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
  1. 先写一下获取用户信息吧

    Oauth2默认调用org.springframework.security.core.userdetails.UserDetailsService获取用户信息,所以我们继承UserDetailsService重写loadUserByUsername方法来实现获取用户的方法。

    1. 创建一个AuthUserDetailsService
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Slf4j
    @Service("userDetailService")
    public class AuthUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao mUserDao;
    @Autowired
    private UserRoleDao mUserRoleDao;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    List<UserDo> userDos = mUserDao.listUserByUserName(userName);
    if (userDos == null || userDos.size() == 0) {
    throw new UsernameNotFoundException("用不存在");
    }
    UserDo userDo = userDos.get(0);
    List<RoleDo> roleDos = mUserRoleDao.listRoleByUserId(userDo.getId());
    userDo.setAuthorities(roleDos);
    log.info(userDo.toString());
    return userDo;
    }
    }
    1. UserDo实现了UserDetails的相关接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    @Data
    public class UserDo implements UserDetails, Serializable {

    private Long id;
    private String username;
    private String password;
    private List<RoleDo> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return authorities;
    }

    /**
    * 过期性 :true:没过期 false:过期
    *
    * @return
    */
    @Override
    public boolean isAccountNonExpired() {
    return true;
    }

    /**
    * 锁定性 :true:未锁定 false:已锁定
    *
    * @return
    */
    @Override
    public boolean isAccountNonLocked() {
    return true;
    }

    /**
    * 有效性 :true:凭证有效 false:凭证无效
    *
    * @return
    */
    @Override
    public boolean isCredentialsNonExpired() {
    return true;
    }

    /**
    * 可用性 :true:可用 false:不可用
    *
    * @return
    */
    @Override
    public boolean isEnabled() {
    return true;
    }
    }
    1. RoleDo

    从上面代码可以看到getAuthorities方法返回的集合数据里面的对象继承了GrantedAuthority,所以我们的RoleDo要实现GrantedAuthority接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Data
    public class RoleDo implements GrantedAuthority, Serializable {

    private Long id;
    private String name;

    @Override
    public String getAuthority() {
    return name;
    }
    }
  2. WebSecurityConfigurerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AuthUserDetailsService userDetailService;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.anyRequest()
.and()
.authorizeRequests()
// 放行 /oauth/ 下面的Api
.antMatchers("/oauth/**")
.permitAll();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}

/**
* 不定义没有password grant_type
*
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

// TODO: 用户密码加密方式
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));
}
}
  1. OAuth2AuthorizationConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Autowired
private AuthUserDetailsService userDetailService;

private static final String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("sdwfqin");

@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 将客户端的信息存储在内存中
clients.inMemory()
// 创建了一个client名为android的客户端
.withClient("android")
.secret(finalSecret)
// 配置验证类型
.authorizedGrantTypes("password", "refresh_token")
// 配置客户端域
.scopes("mobile")
.and()
.withClient("service")
.secret(finalSecret)
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("service");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置Token的存储方式
endpoints
// 读取用户的验证信息
.userDetailsService(userDetailService)
// 注入WebSecurityConfig配置的bean
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.tokenServices(redisTokenServices());
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 允许表单认证
.allowFormAuthenticationForClients()
// 对获取Token的请求不再拦截
.tokenKeyAccess("permitAll()")
// 验证获取Token的验证信息
.checkTokenAccess("isAuthenticated()");
}

@Bean
public DefaultTokenServices redisTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
// token有效期自定义设置,默认12小时
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
// refresh_token默认30天
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
return tokenServices;
}
}
  1. 为其他服务暴露获取用户信息的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

@RequestMapping(value = "/current", method = RequestMethod.GET)
public Principal getUser(Principal principal) {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>");
log.info(principal.toString());
log.info(">>>>>>>>>>>>>>>>>>>>>>>>");
return principal;
}

@GetMapping("/register")
public Result register() {
return ResultUtils.resultData(ResultEnum.SUCCESS, "注册");
}
}
  1. 授权服务上面的资源服务配置ResourceServerConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

private ObjectMapper objectMapper = new ObjectMapper();

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//当权限不足时返回
resources.accessDeniedHandler((request, response, e) -> {
log.error("【accessDeniedHandler】{}", e.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
// 统一认证失败返回的异常
response.getWriter()
.write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.AUTHORITY_ERROR)));
});
//当token不正确时返回
resources.authenticationEntryPoint((request, response, e) -> {
log.error("【authenticationEntryPoint】{}", e.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter()
.write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.TOKEN_ERROR)));
});
}

@Override
public void configure(HttpSecurity http) throws Exception {
// 配置哪些请求需要验证
http.csrf().disable()
.httpBasic().disable()
.authorizeRequests()
// 放行start
.antMatchers("/user/register")
.permitAll()
// 放行end
// ==========
// 认证start
.anyRequest()
.authenticated();
}
}

资源服务器鉴权

  1. maven文件
1
2
3
4
5
<!-- 对spring-cloud-starter-security、spring-security-oauth2、spring-security-jwt这3个依赖的整合 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
  1. ResourceServerConfig配置跟上面的基本相似,设置服务内的放行规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

private ObjectMapper objectMapper = new ObjectMapper();

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// 设置资源服务器id,需要与认证服务器对应
// resources.resourceId("service-auth");
//当权限不足时返回
resources.accessDeniedHandler((request, response, e) -> {
log.error("【accessDeniedHandler】{}", e.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter()
.write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.AUTHORITY_ERROR)));
});
//当token不正确时返回
resources.authenticationEntryPoint((request, response, e) -> {
log.error("【authenticationEntryPoint】{}", e.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter()
.write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.TOKEN_ERROR)));
});
}

@Override
public void configure(HttpSecurity http) throws Exception {
// 配置哪些请求需要验证
http.csrf().disable()
.httpBasic().disable()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
  1. application.yml

通过走zuul的路由访问。

1
2
3
4
5
6
7
8
9
10
security:
oauth2:
resource:
user-info-uri: http://localhost:8002/api/auth/user/current
client:
client-id: service
client-secret: sdwfqin
access-token-uri: http://localhost:8002/api/auth/oauth/token
user-authorization-uri: http://localhost:8002/api/auth/oauth/authorize
scope: service

参考文章与对应Demo

  1. 文中没有接口调用示例,如需查看请下载Demo运行并且将接口json文件导入Postman即可

  2. 对应Demo查看:https://github.com/sdwfqin/SpringCloudSample

  3. 参考文章:

    1. Spring Cloud 从入门到精通(程序猿DD-翟永超)
    2. SpringCloud+SpringBoot+OAuth2+Spring Security+Redis实现的微服务统一认证授权(myCat、)