2021-04-26

  SpringCloud Alibaba微服务实战三十 | 统一资源服务器配置模块

  如果要实现微服务授权,一般会构建一个独立的资源服务器配置模块,否则每个后端业务都需要进行资源服务器的配置,那本节内容我们就来完成此功能。

  由于间隔时间较久,建议先阅读下面两篇相关文章回顾一下。

  SpringCloud Alibaba微服务实战十九 - 集成RBAC授权

  SpringCloud Alibaba微服务实战二十八 - 网关授权VS微服务授权

  话不多说我们直接开始代码改造。

  认证服务器改造

  首先我们需要改造认证服务器,需要认证服务器在构建用户权限的时候使用的是权限标识字段。对于代码而言只需要 UserDetailServiceImpl#loadUserByUsername()中修改即可。

  @Override

  public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

  //获取本地用户

  SysUser sysUser = sysUserMapper.selectByUserName(userName);

  if(sysUser != null){

  //获取当前用户的所有角色

  List roleList = sysRoleService.listRolesByUserId(sysUser.getId());

  sysUser.setRoles(roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList()));

  List roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());

  //获取所有角色的权限

  List permissionList = sysPermissionService.listPermissionsByRoles(roleIds);

  //基于方法拦截.只需放入用户权限标识即可

  List permissionMethodList = permissionList.stream()

  .map(SysPermission::getPermission)

  .collect(Collectors.toList());

  sysUser.setPermissions(permissionMethodList);

  //构建oauth2的用户

  return buildUserDetails(sysUser);

  }else{

  throw new UsernameNotFoundException("用户["+userName+"]不存在");

  }

  }

  网关改造

  网关服务器不再需要进行用户权限校验,所以我们需要将相关校验逻辑全部删除。

  @Configuration

  public class SecurityConfig {

  @Bean

  SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{

  http

  .httpBasic().disable()

  .csrf().disable();

  return http.build();

  }

  }

  独立资源服务器配置模块

  完成了上面两步后就到了最重要的步骤了,需要建立一个独立的资源服务器配置模块,用于其他模块引用。

  首先我们得建立一个单独的资源服务模块 cloud-component-security-starter ,如下为改造后的代码结构图。

  

 

  然后,要让一个普通后端服务成为资源服务器,需要有一个配置类继承 ResourceServerConfigurerAdapter并进行相关配置,那在我们独立的资源服务器模块我们首先得创建一个这样的配置类,这个比较简单,只需从之前的模块中拷贝一份出来。

  public class CloudResourceServerConfigure extends ResourceServerConfigurerAdapter {

  private CustomAccessDeniedHandler accessDeniedHandler;

  private CustomAuthenticationEntryPoint exceptionEntryPoint;

  private TokenStore tokenStore;

  @Value("${security.oauth2.resource.id}")

  private String resourceId ;

  @Autowired(required = false)

  public void setAccessDeniedHandler(CustomAccessDeniedHandler accessDeniedHandler) {

  this.accessDeniedHandler = accessDeniedHandler;

  }

  @Autowired(required = false)

  public void setExceptionEntryPoint(CustomAuthenticationEntryPoint exceptionEntryPoint) {

  this.exceptionEntryPoint = exceptionEntryPoint;

  }

  @Autowired(required = false)

  public void setTokenStore(TokenStore tokenStore) {

  this.tokenStore = tokenStore;

  }

  @Override

  public void configure(HttpSecurity http) throws Exception {

  http

  .authorizeRequests()

  .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()

  .antMatchers(

  "/v2/api-docs/**",

  "/swagger-resources/**",

  "/swagger-ui.html",

  "/webjars/**"

  ).permitAll()

  .anyRequest().authenticated()

  .and()

  .csrf().disable();

  }

  @Override

  public void configure(ResourceServerSecurityConfigurer resources) {

  DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();

  UserAuthenticationConverter userTokenConverter = new CustomUserAuthenticationConverter();

  accessTokenConverter.setUserTokenConverter(userTokenConverter);

  if (exceptionEntryPoint != null) {

  resources.authenticationEntryPoint(exceptionEntryPoint);

  }

  if (accessDeniedHandler != null) {

  resources.accessDeniedHandler(accessDeniedHandler);

  }

  resources.resourceId(resourceId).tokenStore(tokenStore);

  }

  }

  现在有了资源服务器配置,那其他模块如何引入这个配置类呢?

  这里我们可以借助SpringBoot的Enable模块驱动能力,通过@EnableXXX注解导入配置类。

  我们创建一个自定义注解类 EnableCloudResourceServer,其他模块通过 @EnableCloudResourceServer注解即可导入资源服务器配置

  @Target({ElementType.TYPE})

  @Retention(RetentionPolicy.RUNTIME)

  @Documented

  @EnableResourceServer //开启资源服务器

  @Import({CloudResourceServerConfigure.class, TokenStoreConfigure.class})

  public @interface EnableCloudResourceServer {

  }

  最后我们知道微服务授权是基于方法拦截,基于方法拦截我们就需要开启 @EnableGlobalMethodSecurity,并且需要将我们自定义的权限注解功能迁移过来。所以我们再创建一个配置类用于配置上述功能。

  @EnableGlobalMethodSecurity(prePostEnabled = true)

  public class CloudSecurityAutoConfigure extends GlobalMethodSecurityConfiguration {

  @Bean

  @ConditionalOnMissingBean(name = "accessDeniedHandler")

  public CustomAccessDeniedHandler accessDeniedHandler() {

  return new CustomAccessDeniedHandler();

  }

  @Bean

  @ConditionalOnMissingBean(name = "authenticationEntryPoint")

  public CustomAuthenticationEntryPoint authenticationEntryPoint() {

  return new CustomAuthenticationEntryPoint();

  }

  @Override

  protected MethodSecurityExpressionHandler createExpressionHandler() {

  return new CustomMethodSecurityExpressionHandler();

  }

  }

  经过上面的改造,一个独立的资源服务器创建成功了,现在剩下的就是对微服务的改造。

  微服务改造

  在maven中删除原oauth2.0的相关配置,引入自定义 cloud-component-security-starter

  com.jianzh5.cloud

  cloud-component-security-starter

  删除所有资源服务器相关代码(此过程略)

  修改主启动类,通过 @EnableCloudResourceServer引入资源服务器配置

  @EnableDiscoveryClient

  @SpringCloudApplication

  @EnableCloudResourceServer

  public class AccountServiceApplication {

  public static void main(String[] args) {

  SpringApplication.run(AccountServiceApplication.class, args);

  }

  }

  在需要拦截的Controller方法中添加自定义权限拦截注解 @PreAuthorize("hasPrivilege('queryAccount')")

  当然也可以使用SpringSecurity原生注解 @PreAuthorize("hasAuthority('queryAccount')") ,两者作用一样。

  @GetMapping("/account/getByCode/{accountCode}")

  @PreAuthorize("hasPrivilege('queryAccount')")

  //@PreAuthorize("hasAuthority('queryAccount')")

  public ResultData getByCode(@PathVariable(value = "accountCode") String accountCode){

  AccountDTO accountDTO = accountService.selectByCode(accountCode);

  return ResultData.success(accountDTO);

  }

  测试

  我们访问一个没有权限的方法会出现如下错误提示,表明独立资源服务器成功配置

  

 

  {

  "status": 500,

  "message": "不允许访问",

  "data": null,

  "success": false,

  "timestamp": 1619052359563

  }

  提示:@PreAuthorize 注解的异常,抛出AccessDeniedException异常,不会被accessDeniedHandler捕获,而是会被全局异常捕获

  如果需要自定义 @PreAuthorize错误异常,可以通过全局的 @RestControllerAdvice进行异常拦截

  拦截后的自定义异常如下:

  

 

  {

  "status": 2003,

  "message": "没有权限访问该资源",

  "data": null,

  "success": false,

  "timestamp": 1619052359563

  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值