前面文章咱们对比过网关授权与微服务授权的区别,文章也提到了,如果要实现微服务授权,一般会构建一个独立的资源服务器配置模块,否则每个后端业务都需要进行资源服务器的配置,那本节内容我们就来完成此功能。
由于间隔时间较久,建议先阅读下面两篇相关文章回顾一下。
话不多说,我们直接开始代码改造。
认证服务器改造
首先我们需要改造认证服务器,需要认证服务器在构建用户权限的时候使用的是权限标识字段。对于代码而言只需要 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.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
<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.class, args);
}
}
- 在需要拦截的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
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。