您现在的位置是:首页 > 文章详情

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

日期:2021-04-22点击:624

 

前面文章咱们对比过网关授权与微服务授权的区别,文章也提到了,如果要实现微服务授权,一般会构建一个独立的资源服务器配置模块,否则每个后端业务都需要进行资源服务器的配置,那本节内容我们就来完成此功能。

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

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<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+"]不存在");  } } 

网关改造

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

 @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源创计划”,欢迎正在阅读的你也加入,一起分享。

原文链接:https://my.oschina.net/u/1388595/blog/5029820
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章