• 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,现在记得要去掉这个~

    步骤代码:

    1. //2. 源服务器对外直接暴露URL
    2. package com.pig4cloud.pig.common.security.component;
    3. @Slf4j
    4. @ConfigurationProperties(prefix = "security.oauth2.ignore")
    5. public class PermitAllUrlProperties {
    6. @Getter
    7. @Setter
    8. private List urls = new ArrayList<>();
    9. }
    1. //----------------------------
    2. //在nacos的application-dev.yml里面添加配置
    3. # 自定义的 spring security 配置,目前只对资源端认证有效(也就是只有资源端认证配置有用到);注意,如果有{##}的可能会匹配多个路径,需要对路径添加规则,并检查哦~
    4. security:
    5. oauth2:
    6. # 通用放行URL,服务个性化,请在对应配置文件覆盖
    7. ignore:
    8. urls:
    9. - /user/info/*
    10. - /client/getClientDetailsById/*
    1. //3.请求认证的决策器,白名单不进行认证
    2. public class PigBearerTokenExtractorResolver implements BearerTokenResolver {
    3. /**
    4. * 可以设置为灵活的配置项
    5. */
    6. private boolean allowFormEncodedBodyParameter = false;
    7. private boolean allowUriQueryParameter = false;
    8. /**
    9. * 路径白名单
    10. */
    11. private final PermitAllUrlProperties permitAllUrlProperties;
    12. /**
    13. * Pattern可以看作是一个正则表达式的匹配模式,Matcher可以看作是管理匹配结果
    14. */
    15. private static final Pattern authenticationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-:._~+/]+=*)$", Pattern.CASE_INSENSITIVE);
    16. private final PathMatcher pathMatcher = new AntPathMatcher();
    17. public PigBearerTokenExtractorResolver(PermitAllUrlProperties urlProperties) {
    18. this.permitAllUrlProperties = urlProperties;
    19. }
    20. @Override
    21. public String resolve(HttpServletRequest request) {
    22. //校验请求路径,校验是否是白名单,是直接返回null
    23. boolean match = permitAllUrlProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
    24. if(match){
    25. return null;
    26. }
    27. //校验hearder里的accesstoken格式是否匹配,并返回token
    28. final String headerToken =resolveFromHeader(request);
    29. //校验请求方式是否是GET/POST,是就从参数中获取accesstoken,并返回token
    30. final String parameterToken = isParameterSupportedForRequest(request) == true ? resolveFromParameters(request) : null ;
    31. //判断如果headertoekn不是空
    32. if(StringUtils.hasText(headerToken)){
    33. //如果parametertoekn不是空则抛出多个accesstoken异常
    34. if(StringUtils.hasText(parameterToken)){
    35. final BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
    36. throw new OAuth2AuthenticationException(error);
    37. }
    38. //如果parametertoekn是空则返回headertoekn
    39. return headerToken;
    40. }
    41. //如果parametertoekn不是空并且支持参数的token,则返回parameteken
    42. if(StringUtils.hasText(parameterToken) && isParameterEnableForRequest(request)){
    43. return parameterToken;
    44. }
    45. //都是空则返回null
    46. return null;
    47. }
    48. private boolean isParameterEnableForRequest(HttpServletRequest request) {
    49. return (this.allowFormEncodedBodyParameter && HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
    50. || this.allowUriQueryParameter && HttpMethod.GET.equals(request.getMethod()));
    51. }
    52. private boolean isParameterSupportedForRequest(HttpServletRequest request) {
    53. return (HttpMethod.POST.equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
    54. || HttpMethod.GET.equals(request.getMethod()));
    55. }
    56. private String resolveFromHeader(HttpServletRequest request) {
    57. String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
    58. //判断是否是 bearer 开头
    59. if(!StringUtils.startsWithIgnoreCase(authorization, "bearer")){
    60. return null;
    61. }
    62. Matcher matcher = authenticationPattern.matcher(authorization);
    63. //判断是否能匹配上
    64. if(!matcher.matches()){
    65. BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
    66. throw new OAuth2AuthenticationException(error);
    67. }
    68. return matcher.group("token");
    69. }
    70. private String resolveFromParameters(HttpServletRequest request) {
    71. String[] values = request.getParameterValues("access_token");
    72. if (values == null || values.length == 0) {
    73. return null;
    74. }
    75. if (values.length == 1) {
    76. return values[0];
    77. }
    78. BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
    79. throw new OAuth2AuthenticationException(error);
    80. }
    81. }
    1. //4.令牌自省
    2. //由于用户认证的信息都存到了 redis 里面,所以所有服务都可以通过 token 从 redis 里面拿到用户认证信息
    3. @Slf4j
    4. @RequiredArgsConstructor
    5. public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
    6. private final OAuth2AuthorizationService authorizationService;
    7. @Override
    8. public OAuth2AuthenticatedPrincipal introspect(String token) {
    9. //根据 token 从 redis 里面拿到用户认证信息
    10. OAuth2Authorization authAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
    11. if (Objects.isNull(authAuthorization)) {
    12. throw new InvalidBearerTokenException(token);
    13. }
    14. //从容器中获取到 UserDetailsService bean
    15. Map userDetailsServiceMap = SpringUtil
    16. .getBeansOfType(PigUserDetailsServiceImpl.class);
    17. Optional optional = userDetailsServiceMap.values().stream()
    18. .max(Comparator.comparingInt(Ordered::getOrder));
    19. UserDetails userDetails = null;
    20. try{
    21. //由于在授权端认证过程中会给 OAuth2Authorization 的 attributes 添加 ,所以直接拿
    22. Object principal = Objects.requireNonNull(authAuthorization.getAttributes().get(Principal.class.getName()));
    23. UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
    24. Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
    25. //拿到授权端认证的用户信息后,可以在这里再认证一遍
    26. userDetails = optional.get().loadUserByUser((PigUser) tokenPrincipal);
    27. }catch (UsernameNotFoundException notFoundException) {
    28. log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
    29. throw notFoundException;
    30. }catch (Exception ex) {
    31. log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
    32. }
    33. return (PigUser) userDetails;
    34. }
    35. }
    1. //5.客户端异常处理 AuthenticationException 不同细化异常处理,匿名用户访问无权限资源时的异常
    2. @RequiredArgsConstructor
    3. public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
    4. private final ObjectMapper objectMapper;
    5. @Override
    6. @SneakyThrows
    7. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
    8. response.setCharacterEncoding(CommonConstants.UTF8);
    9. response.setContentType(CommonConstants.CONTENT_TYPE);
    10. R result = new R<>();
    11. result.setCode(CommonConstants.FAIL);
    12. response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
    13. if (authException != null) {
    14. result.setMsg("error");
    15. result.setData(authException.getMessage());
    16. }
    17. // 针对令牌过期返回特殊的 424
    18. if (authException instanceof InvalidBearerTokenException) {
    19. response.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value());
    20. result.setMsg("token expire");
    21. }
    22. PrintWriter printWriter = response.getWriter();
    23. printWriter.append(objectMapper.writeValueAsString(result));
    24. }
    25. }
    1. //6.资源端的自动配置项
    2. @RequiredArgsConstructor
    3. @EnableConfigurationProperties(PermitAllUrlProperties.class)
    4. public class PigResourceServerAutoConfiguration {
    5. /**
    6. * @Description: 请求认证的决策器,白名单不进行认证
    7. * @param urlProperties 对外暴露的接口列表
    8. * @Return: com.pig4cloud.pig.common.security.component.PigBearerTokenExtractorResolver
    9. */
    10. @Bean
    11. public PigBearerTokenExtractorResolver pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
    12. return new PigBearerTokenExtractorResolver(urlProperties);
    13. }
    14. /**
    15. * 资源端认证异常
    16. * @param objectMapper jackson 输出对象
    17. * @return ResourceAuthExceptionEntryPoint
    18. */
    19. @Bean
    20. public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper) {
    21. return new ResourceAuthExceptionEntryPoint(objectMapper);
    22. }
    23. /**
    24. * 资源服务器toke内省处理器
    25. * @param authorizationService token 存储实现
    26. * @return TokenIntrospector
    27. */
    28. @Bean
    29. public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
    30. return new PigCustomOpaqueTokenIntrospector(authorizationService);
    31. }
    32. }
    1. //7.资源服务器认证授权配置
    2. @Slf4j
    3. @EnableWebSecurity
    4. @RequiredArgsConstructor
    5. public class PigResourceServerConfiguration {
    6. private final PigBearerTokenExtractorResolver bearerTokenExtractorResolver;
    7. private final PigCustomOpaqueTokenIntrospector opaqueTokenIntrospector;
    8. protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
    9. private final PermitAllUrlProperties permitAllUrlProperties;
    10. @Bean
    11. @Order(Ordered.HIGHEST_PRECEDENCE)
    12. SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {
    13. http.authorizeRequests(auththorized ->
    14. auththorized.antMatchers(ArrayUtil.toArray(permitAllUrlProperties.getUrls(), String.class)).permitAll()
    15. .anyRequest().authenticated()
    16. ).oauth2ResourceServer(oauth ->
    17. oauth.bearerTokenResolver(bearerTokenExtractorResolver)
    18. .opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer.introspector(opaqueTokenIntrospector))
    19. .authenticationEntryPoint(resourceAuthExceptionEntryPoint)
    20. );
    21. http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    22. return http.build();
    23. }
    24. }
    1. //8.资源服务注解
    2. @Documented
    3. @Inherited //@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到
    4. @Target({ ElementType.TYPE }) //@Target注解的作用目标,接口、类、枚举、注解
    5. @Retention(RetentionPolicy.RUNTIME) //注解的保留位置
    6. @EnableGlobalMethodSecurity(prePostEnabled = true)
    7. @Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class})
    8. public @interface EnablePigResourceServer {
    9. }
    10. //--------------------
    11. @EnablePigResourceServer
    12. @EnableFeignClients(basePackages = "com.pig4cloud.pig")
    13. @EnableDiscoveryClient
    14. //@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
    15. @SpringBootApplication
    16. public class PigAdminApplication {
    17. public static void main(String[] args) {
    18. SpringApplication.run(PigAdminApplication.class, args);
    19. }
    20. }

    启动程序,先从auth里面拿到 token ,然后去用户模块测试,不添加token访问,会提示错误,添加正确的token冯文就会成功~

    oauth2资源端自定义密码模式调用图:

    防止忘记

    遇到的问题:

    拿到token访问 upms 模块接口时,已经根据token拿到用户信息了,但是在OpaqueTokenAuthenticationProvider里面报错了!!!

    问题就是写 PigUser 类时,实现了 OAuth2AuthenticatedPrincipal 接口,会重写他的getAttributes()方法,而 OpaqueTokenAuthenticationProvider 里面拿的就是 OAuth2AuthenticatedPrincipal#getAttributes() !由于我写这个类的时候没有修改,直接就是 null ,于是报错了~ 需要返回个对象,否则会报错!

    1. /**
    2. * @author QingChen
    3. * @Description 扩展用户认证时的信息
    4. * @date 2022-10-25 11:59
    5. * @Version 1.0
    6. */
    7. public class PigUser extends User implements OAuth2AuthenticatedPrincipal {
    8. ...
    9. @Override
    10. public Map getAttributes() {
    11. return new HashMap<>();
    12. }
    13. @Override
    14. public String getName() {
    15. return this.getUsername();
    16. }
    17. }

  • 相关阅读:
    jupyterlab开发环境最佳构建方式
    GRU门控循环单元
    详细计算机专业毕业设计开题报告书写方法
    配置nginx域名转发
    Redis——听说你速度跟甲斗一样快?(上)
    Java中InputStream写入到文件中
    2.1_2进程的状态与转换
    牛客小白月赛61 B.柜台结账(模拟+字符串)
    计算机毕业设计springboot+vue基本微信小程序的校园二手物品交易平台系统
    【技术分享】Python 和 JavaScript 的区别是什么?
  • 原文地址:https://blog.csdn.net/vaevaevae233/article/details/127545240