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

 

前面文章我们对比过网关受权与微服务受权的区别,文章也提到了,若是要实现微服务受权,通常会构建一个独立的资源服务器配置模块,不然每一个后端业务都须要进行资源服务器的配置,那本节内容咱们就来完成此功能。html

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

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

SpringCloud Alibaba微服务实战二十八 - 网关受权VS微服务受权程序员

话很少说,咱们直接开始代码改造。web

认证服务器改造

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

@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
 //获取本地用户
 SysUser sysUser = sysUserMapper.selectByUserName(userName);
 if(sysUser != null){
  //获取当前用户的全部角色
  List<SysRole> roleList = sysRoleService.listRolesByUserId(sysUser.getId());
  sysUser.setRoles(roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList()));
  List<Integer> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
  //获取全部角色的权限
  List<SysPermission> permissionList = sysPermissionService.listPermissionsByRoles(roleIds);

  //基于方法拦截.只需放入用户权限标识便可
  List<String> permissionMethodList = permissionList.stream()
    .map(SysPermission::getPermission)
    .collect(Collectors.toList());
  sysUser.setPermissions(permissionMethodList);
  //构建oauth2的用户
  return buildUserDetails(sysUser);

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

网关改造

网关服务器再也不须要进行用户权限校验,因此咱们须要将相关校验逻辑所有删除。api

@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.classTokenStoreConfigure.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

<dependency>
 <groupId>com.jianzh5.cloud</groupId>
 <artifactId>cloud-component-security-starter</artifactId>
</dependency>

 

  • 删除全部资源服务器相关代码(此过程略)

     

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

@EnableDiscoveryClient
@SpringCloudApplication
@EnableCloudResourceServer
public class AccountServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AccountServiceApplication.classargs);
    }
}

 

  • 在须要拦截的Controller方法中添加自定义权限拦截注解 @PreAuthorize("hasPrivilege('queryAccount')")
    固然也可使用SpringSecurity原生注解  @PreAuthorize("hasAuthority('queryAccount')")  ,二者做用同样。

@GetMapping("/account/getByCode/{accountCode}")
@PreAuthorize("hasPrivilege('queryAccount')")
//@PreAuthorize("hasAuthority('queryAccount')")
public ResultData<AccountDTO> 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
}

 

 

以上,但愿对你有所帮助!

 

这里为你们准备了一份小小的礼物,关注公众号,输入以下代码,便可得到百度网盘地址,无套路领取!

001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》

010: 微信交流群

 

 

 

本文分享自微信公众号 - JAVA日知录(javadaily)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索