No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)
代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客
之前只零碎的学习过spring-cloud-alibaba,并没有全面了解过,这次学习pig框架时,想着可以根据这个项目学习一下,练练手,于是断断续续的用了几天时间搭建了一下基础框架。目前就先重点记录一下遇到的问题吧,毕竟流程也不是特别复杂,就是有的东西没遇到过了解的也不深~
由于微服务包括认证这里内容太多,所以分了好几篇~
第一篇文章:No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一)_清晨敲代码的博客-CSDN博客
文章包括:
1.将服务系统注册到nacos注册中心;
2.通过nacos实现配置动态更新;
3.添加fegin服务,实现服务之间调用;
4.添加网关(学会使用webflux,学会添加过滤器);
5.添加log服务,通过springevent实现,并使用注解使用(使用AOP);
第二篇文章:
No6.从零搭建spring-cloud-alibaba微服务框架,实现数据库调用、用户认证与授权等(二,no6-2)_清晨敲代码的博客-CSDN博客
文章包括:
6.添加 mysql 数据库调用,并使用mybatis-plus操作;
7.在认证模块添加用户认证,基于oauth2的自定义密码模式(已认证用户是基于自定义token加redis持久化,不是session);
本篇文章包括:
8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑(但是没有处理微服务间的不鉴权调用,微服务间的调用接口都是白名单呢!);
剩余包括(会有变动):
9.解决微服务间的不鉴权调用(优化外部访问鉴权逻辑~)
10.添加用户权限校验等逻辑;
目录
A8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑;
oauth2资源端自定义密码模式调用图:
遇到的问题:
A8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑;
再给 pig-upms模块添加 security 鉴权前,突然意识到 pig-auth 模块认证的时候是会远程调用它的接口的,如果这个接口需要鉴权后才能调用,这样就需要 pig-auth 提供认证信息了。可pig-auth 调用接口本身就是要进行认证的,这不就成死循环了吗?
而且抛开这个问题,后续开发中会有多个微服务之间进行远程调用,难道都需要携带认证信息吗?
于是在理解了 pig 项目里面的微服务之间的调用逻辑后,可以参考他的文档:feign调用及服务间鉴权 · 语雀和Inner注解使用说明 · 语雀,反正我有些没懂,然后看了源码后才理解,于是写了篇笔记防止自己忘掉:【pig-cloud项目】关于@Inner和@PreAuthorize的理解,以及微服务内外部间的调用认证鉴权理解
在开始前,首先明确目标,先实现资源端的用户认证,微服务间的调用在下一个A9里面实现,那么这里就需要注意要先将A7里面用户认证所需要的接口添加到白名单里面,不然是没有认证权限,就永无法调用的!
开始步骤:
1.不用导包,因为authorization-server包里面带有resource-server包;
2.在pig-common-security模块添加security白名单PermitAllUrlProperties,对外暴露URL,可以添加到nacos配置中心;
3.在pig-common-security模块添加请求认证的决策器BearerTokenResolver,对于白名单不进行认证,其余路径必须经过token认证,不认证则不能访问;
4.在pig-common-security模块添加自定义令牌自省类OpaqueTokenIntrospector,通过token拿到授权端用户认证信息,然后进行用户认证(也可以直接认证成功,添加是为了防止用户信息更改);
5.在pig-common-security模块添加认证异常处理类AuthenticationEntryPoint,处理token失效、无token、token异常等情况;
6.在pig-common-security模块将需要bean的类通过PigResourceServerAutoConfiguration注入容器;
7.在pig-common-security模块在PigResourceServerConfiguration添加资源端的安全过滤链,要将oauth2ResourceServer配置成我们自定义的;
8.最后在pig-common-security模块将资源端认证配置类封装成了注解,然后加到需要的资源端启动类上!
注意:由于之前upms模块不需要security,所以在启动类上exclude = SecurityAutoConfiguration.class,现在记得要去掉这个~
步骤代码:
//2. 源服务器对外直接暴露URL
package com.pig4cloud.pig.common.security.component;@Slf4j
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties {@Getter@Setterprivate List<String> urls = new ArrayList<>();}
//----------------------------
//在nacos的application-dev.yml里面添加配置# 自定义的 spring security 配置,目前只对资源端认证有效(也就是只有资源端认证配置有用到);注意,如果有{##}的可能会匹配多个路径,需要对路径添加规则,并检查哦~security:oauth2:# 通用放行URL,服务个性化,请在对应配置文件覆盖ignore:urls:- /user/info/*- /client/getClientDetailsById/*
//3.请求认证的决策器,白名单不进行认证public class PigBearerTokenExtractorResolver implements BearerTokenResolver {/*** 可以设置为灵活的配置项*/private boolean allowFormEncodedBodyParameter = false;private boolean allowUriQueryParameter = false;/*** 路径白名单*/private final PermitAllUrlProperties permitAllUrlProperties;/*** Pattern可以看作是一个正则表达式的匹配模式,Matcher可以看作是管理匹配结果*/private static final Pattern authenticationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$", Pattern.CASE_INSENSITIVE);private final PathMatcher pathMatcher = new AntPathMatcher();public PigBearerTokenExtractorResolver(PermitAllUrlProperties urlProperties) {this.permitAllUrlProperties = urlProperties;}@Overridepublic String resolve(HttpServletRequest request) {//校验请求路径,校验是否是白名单,是直接返回nullboolean match = permitAllUrlProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));if(match){return null;}//校验hearder里的accesstoken格式是否匹配,并返回tokenfinal String headerToken =resolveFromHeader(request);//校验请求方式是否是GET/POST,是就从参数中获取accesstoken,并返回tokenfinal String parameterToken = isParameterSupportedForRequest(request) == true ? resolveFromParameters(request) : null ;//判断如果headertoekn不是空if(StringUtils.hasText(headerToken)){//如果parametertoekn不是空则抛出多个accesstoken异常if(StringUtils.hasText(parameterToken)){final BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");throw new OAuth2AuthenticationException(error);}//如果parametertoekn是空则返回headertoeknreturn headerToken;}//如果parametertoekn不是空并且支持参数的token,则返回parametekenif(StringUtils.hasText(parameterToken) && isParameterEnableForRequest(request)){return parameterToken;}//都是空则返回nullreturn null;}private boolean isParameterEnableForRequest(HttpServletRequest request) {return (this.allowFormEncodedBodyParameter && HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())|| this.allowUriQueryParameter && HttpMethod.GET.equals(request.getMethod()));}private boolean isParameterSupportedForRequest(HttpServletRequest request) {return (HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())|| HttpMethod.GET.equals(request.getMethod()));}private String resolveFromHeader(HttpServletRequest request) {String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);//判断是否是 bearer 开头if(!StringUtils.startsWithIgnoreCase(authorization, "bearer")){return null;}Matcher matcher = authenticationPattern.matcher(authorization);//判断是否能匹配上if(!matcher.matches()){BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");throw new OAuth2AuthenticationException(error);}return matcher.group("token");}private String resolveFromParameters(HttpServletRequest request) {String[] values = request.getParameterValues("access_token");if (values == null || values.length == 0) {return null;}if (values.length == 1) {return values[0];}BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");throw new OAuth2AuthenticationException(error);}}
//4.令牌自省
//由于用户认证的信息都存到了 redis 里面,所以所有服务都可以通过 token 从 redis 里面拿到用户认证信息@Slf4j
@RequiredArgsConstructor
public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {private final OAuth2AuthorizationService authorizationService;@Overridepublic OAuth2AuthenticatedPrincipal introspect(String token) {//根据 token 从 redis 里面拿到用户认证信息OAuth2Authorization authAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);if (Objects.isNull(authAuthorization)) {throw new InvalidBearerTokenException(token);}//从容器中获取到 UserDetailsService beanMap<String, PigUserDetailsServiceImpl> userDetailsServiceMap = SpringUtil.getBeansOfType(PigUserDetailsServiceImpl.class);Optional<PigUserDetailsServiceImpl> optional = userDetailsServiceMap.values().stream().max(Comparator.comparingInt(Ordered::getOrder));UserDetails userDetails = null;try{//由于在授权端认证过程中会给 OAuth2Authorization 的 attributes 添加 <Principal,upAuthenticationToken>,所以直接拿Object principal = Objects.requireNonNull(authAuthorization.getAttributes().get(Principal.class.getName()));UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();//拿到授权端认证的用户信息后,可以在这里再认证一遍userDetails = optional.get().loadUserByUser((PigUser) tokenPrincipal);}catch (UsernameNotFoundException notFoundException) {log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());throw notFoundException;}catch (Exception ex) {log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());}return (PigUser) userDetails;}
}
//5.客户端异常处理 AuthenticationException 不同细化异常处理,匿名用户访问无权限资源时的异常@RequiredArgsConstructor
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {private final ObjectMapper objectMapper;@Override@SneakyThrowspublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {response.setCharacterEncoding(CommonConstants.UTF8);response.setContentType(CommonConstants.CONTENT_TYPE);R<String> result = new R<>();result.setCode(CommonConstants.FAIL);response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);if (authException != null) {result.setMsg("error");result.setData(authException.getMessage());}// 针对令牌过期返回特殊的 424if (authException instanceof InvalidBearerTokenException) {response.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value());result.setMsg("token expire");}PrintWriter printWriter = response.getWriter();printWriter.append(objectMapper.writeValueAsString(result));}}
//6.资源端的自动配置项@RequiredArgsConstructor
@EnableConfigurationProperties(PermitAllUrlProperties.class)
public class PigResourceServerAutoConfiguration {/*** @Description: 请求认证的决策器,白名单不进行认证* @param urlProperties 对外暴露的接口列表* @Return: com.pig4cloud.pig.common.security.component.PigBearerTokenExtractorResolver*/@Beanpublic PigBearerTokenExtractorResolver pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {return new PigBearerTokenExtractorResolver(urlProperties);}/*** 资源端认证异常* @param objectMapper jackson 输出对象* @return ResourceAuthExceptionEntryPoint*/@Beanpublic ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper) {return new ResourceAuthExceptionEntryPoint(objectMapper);}/*** 资源服务器toke内省处理器* @param authorizationService token 存储实现* @return TokenIntrospector*/@Beanpublic OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {return new PigCustomOpaqueTokenIntrospector(authorizationService);}}
//7.资源服务器认证授权配置@Slf4j
@EnableWebSecurity
@RequiredArgsConstructor
public class PigResourceServerConfiguration {private final PigBearerTokenExtractorResolver bearerTokenExtractorResolver;private final PigCustomOpaqueTokenIntrospector opaqueTokenIntrospector;protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;private final PermitAllUrlProperties permitAllUrlProperties;@Bean@Order(Ordered.HIGHEST_PRECEDENCE)SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {http.authorizeRequests(auththorized ->auththorized.antMatchers(ArrayUtil.toArray(permitAllUrlProperties.getUrls(), String.class)).permitAll().anyRequest().authenticated()).oauth2ResourceServer(oauth ->oauth.bearerTokenResolver(bearerTokenExtractorResolver).opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer.introspector(opaqueTokenIntrospector)).authenticationEntryPoint(resourceAuthExceptionEntryPoint));http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);return http.build();}
}
//8.资源服务注解@Documented
@Inherited //@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到
@Target({ ElementType.TYPE }) //@Target注解的作用目标,接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME) //注解的保留位置
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class})
public @interface EnablePigResourceServer {
}//--------------------@EnablePigResourceServer
@EnableFeignClients(basePackages = "com.pig4cloud.pig")
@EnableDiscoveryClient
//@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@SpringBootApplication
public class PigAdminApplication {public static void main(String[] args) {SpringApplication.run(PigAdminApplication.class, args);}}
启动程序,先从auth里面拿到 token ,然后去用户模块测试,不添加token访问,会提示错误,添加正确的token冯文就会成功~
oauth2资源端自定义密码模式调用图:
防止忘记
遇到的问题:
拿到token访问 upms 模块接口时,已经根据token拿到用户信息了,但是在OpaqueTokenAuthenticationProvider里面报错了!!!
问题就是写 PigUser 类时,实现了 OAuth2AuthenticatedPrincipal 接口,会重写他的getAttributes()方法,而 OpaqueTokenAuthenticationProvider 里面拿的就是 OAuth2AuthenticatedPrincipal#getAttributes() !由于我写这个类的时候没有修改,直接就是 null ,于是报错了~ 需要返回个对象,否则会报错!
/*** @author QingChen* @Description 扩展用户认证时的信息* @date 2022-10-25 11:59* @Version 1.0*/public class PigUser extends User implements OAuth2AuthenticatedPrincipal {...@Overridepublic Map<String, Object> getAttributes() {return new HashMap<>();}@Overridepublic String getName() {return this.getUsername();}
}
No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)相关推荐
- 从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(三) (mini-cloud) 搭建认证服务(认证/资源分离版) oauth2.0 (中)
本文承接上文<从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(三) (mini-cloud) 搭建认证服务(认证/资源分离版) oauth2.0 (上)> ...
- 从0到1手把手搭建spring cloud alibaba 微服务大型应用框架(十五) swagger篇 : gateway 集成swagger 与 knife4j实现在线api文档并嵌入到自己项目内
背景 我们日常开发中基本都是协同开发的,当然极个别的项目整体前后端都是一个人开发的,当多人协作时,尤其是前后端人员协同开发时 必然会面临着前端需要了解后端api接口的情况,两个选择,提前设计好文档,然 ...
- 从0到1带大家把手搭建spring cloud alibaba 微服务大型应用框架(八) saas平台篇-解决不同租户针定制化开发问题 -完整代码以及案例方案(1)
问题描述 平台越做越大到多租户时,经常会遇见一种情况,就是某些用户希望自己的功能是定制化的,有可能是完全的新功能,也有可能压根就是同样的功能但是A和B两个用户的实现前后台展示和逻辑就压根不同 不可能在 ...
- 防止内卷和被潜规则,Spring Cloud Alibaba微服务架构实战派(上下册)|35岁程序员那些事
目录 1 写书缘由 2 本书上册核心内容 2.1 Spring Cloud Alibaba基础实战 2.1.1 主要内容 2.1.2 MyBatis-Plus实现多租户架构的核心原理 2.2 分布式服 ...
- Spring Cloud Alibaba 微服务开发实践
作者:禅与计算机程序设计艺术 1.简介 Spring Cloud Alibaba 是阿里巴巴开源的基于 Spring Cloud 的微服务框架.该项目从最初孵化到现在已经历经十多年的发展,得到了广泛的 ...
- Spring Cloud Alibaba 微服务详细笔记
文章目录 SpringCloud 一.微服务概述 1.1.什么是微服务? 1.2.为什么是微服务? 1.3.架构演变 1.4.微服务的解决方案 二.什么是SpringCloud 2.1.官方定义 2. ...
- 开源,阿里内部Spring Cloud Alibaba微服务神仙文档(全彩版)
Spring Cloud Alibaba概述 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring C ...
- Spring Cloud Alibaba微服务项目中集成Redis实现分布式事务锁实践
引言 我们知道同一个进程里面为了解决资源共享而不出现高并发的问题可以通过高并发编程解决,通过给变量添加volatile关键字实现线程间变量可见:通过synchronized关键字修饰代码块.对象或者方 ...
- 最新版Spring Cloud Alibaba微服务架构-Config配置中心篇
文章目录 前言 一.Config引入背景 1.文件相对分散 2.无法区分环境 3.无法实时更新 4.安全无法保证 二.Config引入配置 1.配置文件格式 1.1 命名空间(Namespace) 1 ...
- 最新版Spring Cloud Alibaba微服务架构-Openfeign服务调用篇
文章目录 前言 一.OpenFeign配置使用 1.引入pom依赖 2.消费端开启Feign服务调用 3.消费端动态代理Service接口 3.消费端测试 二.OpenFeign日志配置 1.全局配置 ...
最新文章
- sql 怎样 得到 的客户端的ip地址_怎样用卷发棒?正确用法大揭密-装修攻略
- xcode 及 MAC 常用快捷键
- CentOS 7 修改时区例如上海时区
- 9款Android经常使用的高速开发框架
- 软件架构视图—4+1模式
- 巴塞尔新资本协议_《巴塞尔公约》修订!进口再生颗粒或对中国产生巨大冲击...
- mysql允许远程访问
- indices should be one-based and in ascending order
- HDU2067 小兔的棋盘
- 【Hibernate】Hibernate系列6之HQL查询
- node redis集群_如何使用集群扩展Node.js服务器
- python获取绝对路径_Python 获取文件路径及文件目录
- 剑三 服务器维护,11月15日服务器例行维护公告 补偿部分服务器
- 基于python的MUSIC算法
- 《软件评测师教程》读书心得 一
- C# 计算农历日期方法
- 杭电3233(杂题)
- (转) latch 入门
- 营业执照编码验证规则(15位和18位)
- 工作流与BPM的区别