PIG框架学习2——资源服务器的配置详解

news2024/9/23 21:28:54

一、前言

1、pig资源服务器的配置

Spring Security oauth2相关的依赖是在pigx-common-security模块中引入的,其他模块需要进行token鉴权的,需要在微服务中引入pigx-common-security模块的依赖,从而间接引入相关的Spring security oauth2依赖。

其最简单的一个目的,是对资源进行保护,对访问资源时携带的token进行鉴权。

微服务,开启资源服务器配置步骤:

①引入相关的依赖

<!--安全模块-->
<dependency>
   <groupId>com.pig4cloud</groupId>
   <artifactId>pig-common-security</artifactId>
   <version>laster.version</version>
</dependency>

main方法开启@EnablePigResourceServer

pig4cloudSpring Security OAuth2的资源服务器配置进行了封装,只需要一个注解即可完成相关的操作。

二、EnablePigxResourceServer解析

1、EnablePigxResourceServer的源码
/*
用于指示编译器将被注解的元素的注释信息包含在生成的文档中
使用该自定义注解的地方会在生成的文档中显示该注解的信息和说明
*/
@Documented
/*
用于指示一个自定义注解是否具有继承性
当使用@Inherited注解某个自定义注解时,如果一个类或接口使用了该被注解的自定义注解,那么其子类或实现类也会自动被应用该注解
*/
@Inherited
/*
用于限定自定义注解可以应用的目标元素类型
TYPE 类或接口; FIELD 字段(成员变量);
METHOD 方法;PARAMETER 方法参数;
CONSTRUCTOR 构造函数;LOCAL_VARIABLE 局部变量;
ANNOTATION_TYPE 注解类型;PACKAGE 包;
TYPE_PARAMETER 类型参数;TYPE_USE 类型使用;
*/
@Target({ ElementType.TYPE })
/*
指定自定义注解的保留策略
SOURCE: 自定义注解仅在源代码中保留,编译后不包含
CLASS: 自定义注解在编译后的字节码文件中保留,但不会被加载到虚拟机中
RUNTIME: 自定义注解在运行时保留
*/
@Retention(RetentionPolicy.RUNTIME)
/*
@Import注解主要用于将其他配置类导入到当前的配置类中,以实现配置的组合和复用,而不是用于创建Bean对象
*/
@Import({ PigxResourceServerAutoConfiguration.class, PigxResourceServerConfiguration.class,
		PigxFeignClientConfiguration.class })
public @interface EnablePigxResourceServer {

}
2、PigxResourceServerAutoConfiguration.class源码:
/*
用于自动生成一个包含所有非final和非null字段的构造函数
*/
@RequiredArgsConstructor
/*
只要在加载PigxResourceServerAutoConfiguration时
才会去加载对应的属性配置类:PermitAllUrlProperties
注意: 通过该注解引入的配置@Import({EnableConfigurationPropertiesRegistrar.class}),
会将被@ConfigurationProperties 注解标记的目标类PermitAllUrlProperties注册为一个bean对象
目的:减少spring管控在资源数量 详情见2.1
*/
@EnableConfigurationProperties(PermitAllUrlProperties.class)
public class PigxResourceServerAutoConfiguration {

	/**
	 * 鉴权具体的实现逻辑 详情见2.2
	 * @return (#pms.xxx)
	 */
	@Bean("pms")
	public PermissionService permissionService() {
		return new PermissionService();
	}

	/**
	 * 请求令牌的抽取逻辑 详情见2.3
	 * @param urlProperties 对外暴露的接口列表
	 * @return BearerTokenExtractor
	 */
	@Bean
	public PigxBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
		return new PigxBearerTokenExtractor(urlProperties);
	}

	/**
	 * 资源服务器异常处理 详情见2.4
	 * @param objectMapper jackson 输出对象
	 * @param securityMessageSource 自定义国际化处理器
	 * @return ResourceAuthExceptionEntryPoint
	 */
	@Bean
	public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,
			MessageSource securityMessageSource) {
		return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);
	}

	/**
	 * 资源服务器toke内省处理器 详情见2.5
	 * @param authorizationService token 存储实现
	 * @return TokenIntrospector
	 */
	@Bean
	public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
		return new PigxCustomOpaqueTokenIntrospector(authorizationService);
	}

}

2.1、属性配置类:PermitAllUrlProperties

①将默认的忽略地址加入ignoreUrls列表

②将配置文件中配置的地址加入到ignoreUrls列表

③通过请求映射器获得所有的请求控制器,将添加@inner注解的请求地址加入到ignoreUrls列表

//自动添加日志记录器(Logger)的字段,实现了简化日志记录的功能
@Slf4j
/*
@ConfigurationProperties将配置文件中以指定前缀开头的属性值映射到一个Java类中,
以方便统一管理和使用
*/
@ConfigurationProperties(prefix = "security.oauth2.client")
/*
InitializingBean:
在Bean声明周期中的初始化操作,InitializingBean接口中有一个afterPropertiesSet()方法,
其执行时机早于init-method配置的方法,其是在所有的bean实例化完成并完成依赖注入后执行的,
自动调用实现了InitializingBean接口的bean的afterPropertiesSet()方法,即在bean实例化后和依赖注入后执行的回调方法
注意:implements InitializingBean接口并不是在所有类中都能生效的,它只适用于Spring容器中的bean对象
*/
public class PermitAllUrlProperties implements InitializingBean {
	
	private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

	private static final String[] DEFAULT_IGNORE_URLS = new String[] { "/actuator/**", "/error", "/v3/api-docs" };

    //在配置文件中指定的需要忽略的url
	@Getter
	@Setter
	private List<String> ignoreUrls = new ArrayList<>();

    //在Bean属性设置后执行该方法
	@Override
	public void afterPropertiesSet() {
        //忽略url的列表中先加入默认忽略的url
		ignoreUrls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));
        
        /*
        RequestMappingHandlerMapping 是 Spring MVC 中的一个重要组件,它负责将请求映射到具体的处理方法(handler method)
        在 Spring MVC 的处理流程中,RequestMappingHandlerMapping 会根据请求的 URL 和请求方式(GET、POST 等)来确定需要调用哪个处理方法,
        从而完成请求的处理过程
        */
		RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping");
		//RequestMappingInfo:请求映射信息,包括请求路径、请求方式等
        //HandlerMethod:获得所有处理方法的具体信息,包括所属的类、方法名、参数列表等存放到
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

        //处理@Inner注解的方法和类,将其添加到ignoreUrls列表中
		map.keySet().forEach(info -> {
            //获取对应的映射处理方法
			HandlerMethod handlerMethod = map.get(info);

			// 获取方法上边的注解 替代path variable 为 *
            //通过AnnotationUtils获取当前映射处理方法上的Inner注解,赋值给method,如果没有inner注解,method的值为null
			Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
			//如果method不为空(当前方法添加Inner注解)将映射的url通过正则表达式解析后加入到ignoreurls列表中
            //正则表达式主要是对路径上的参数进行处理,匹配{}中的内容,然后替换为*
            Optional.ofNullable(method).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
					.getPatternValues().forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));

			// 获取类上边的注解, 替代path variable 为 *
            //同理方法
			Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
			Optional.ofNullable(controller).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
					.getPatternValues().forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
		});
	}

}

Map<RequestMappingInfo, HandlerMethod>内容如下所示:

在这里插入图片描述

2.2、接口权限判断工具:PermissionService

/**
	 * 鉴权具体的实现逻辑
	 * @return (#pms.xxx)
	 */
@Bean("pms")
public PermissionService permissionService() {
    return new PermissionService();
}

具体解析

public class PermissionService {

	/**
	 * 判断接口是否有任意xxx,xxx权限
	 * @param permissions 权限
	 * @return {boolean}
	 */
    //String... 可变参数,允许将任意数量的String参数打包成一个数组
    //可以将一个 ArrayList 作为参数传递给可变参数 String... permissions
    //eg:hasPermission("param1", "param2")、hasPermission(Arrays.asList("param1", "param2"))
	public boolean hasPermission(String... permissions) {
        //入参为空,返回false
		if (ArrayUtil.isEmpty(permissions)) {
			return false;
		}
        //从用户的安全上下文信息获取权限信息
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		//用户权限信息为null,返回false
        if (authentication == null) {
			return false;
		}
        
        //获得权限信息赋值给authorities
		Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        //权限是否匹配
		return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
				.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
	}

}

具体使用方式:

/**
* 更新角色菜单
*
* @param roleVo 角色对象
* @return success、false
*/
@SysLog("更新角色菜单")
@PutMapping("/menu")
@PreAuthorize("@pms.hasPermission('sys_role_perm')")
public R saveRoleMenus(@RequestBody RoleVO roleVo) {
    return R.ok(sysRoleService.updateRoleMenus(roleVo));
}

使用的Spring Security@PreAuthorize注解,用于指定方法执行前需要满足的权限要求,它通常用于控制访问某些受保护资源时的权限控制。

@PreAuthorize 中,可以指定一个 SpEL 表达式作为权限要求,如@PreAuthorize("@pms.hasPermission('sys_role_perm')")

@pms”是 SpEL 中使用的 Spring EL Bean 引用语法,表示引用名为 pms 的 Bean。hasPermissionpms Bean 中定义的一个方法,用于检查当前用户是否拥有指定的权限。

因此,上述注解的作用是,当执行该方法时,应该检查当前用户是否具有 sys_role_perm 权限。如果当前用户不具备该权限,方法将被拒绝执行,抛出 AccessDeniedException 异常。

注意:使用的Spring Security@PreAuthorize注解,需要配置全局的方法级安全性设置,启用 Spring Security 的方法级安全性(Method Security)意味着你可以在方法级别上对访问权限进行控制。通过使用 @PreAuthorize@PostAuthorize@Secured 等注解,你可以在方法执行前或执行后对用户的权限进行验证。

在yml中配置:

spring:
  security:
    enabled: true
    method:
      security:
        enabled: true

在xml配置文件中配置:

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd">

    <global-method-security pre-post-annotations="enabled" secured-annotations="enabled" />

    <!-- 其他配置 -->
</beans:beans>

在pig中是直接通过注解@EnableMethodSecurity开启的

@Slf4j
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class PigxResourceServerConfiguration {
	……
}

2.3、请求令牌的抽取逻辑:PigxBearerTokenExtractor

/**
	 * 请求令牌的抽取逻辑
	 * @param urlProperties 对外暴露的接口列表
	 * @return BearerTokenExtractor
	 */
@Bean
public PigxBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
    return new PigxBearerTokenExtractor(urlProperties);
}

即获取请求中的token的相关逻辑

//BearerTokenResolver:是Spring Security中的一个接口,用于解析Bearer Token,并将其返回
//该接口定义了一个方法 resolve(HttpServletRequest request),用于从请求中提取出 Bearer Token,需要在实现类中重写
//这里pigx自定义了一个类PigxBearerTokenExtractor作为BearerTokenResolver的实现类,用于解析Bearer Token
public class PigxBearerTokenExtractor implements BearerTokenResolver
{

    //定义处理Bearer Token 的正则表达式模式
	private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
			Pattern.CASE_INSENSITIVE);

    //是否允许从表单编码的请求体参数中获取 Token。
	private boolean allowFormEncodedBodyParameter = false;

    //是否允许从 URI 查询参数中获取 Token
	private boolean allowUriQueryParameter = true;

    //存储 Bearer Token 的请求头名称,默认为 Authorization
    //常量值public static final String AUTHORIZATION = "Authorization";
	private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;

    //用于检查当前请求路径是否应被忽略的路径匹配器
	private final PathMatcher pathMatcher = new AntPathMatcher();

    //存储可忽略 URL 列表
	private final PermitAllUrlProperties urlProperties;

    //构造器传入属性配置类:PermitAllUrlProperties(存储对外暴露的接口列表)
	public PigxBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
		this.urlProperties = urlProperties;
	}

    //对token的抽取方法
	@Override
	public String resolve(HttpServletRequest request) {
        //获取当前请求的url
		String requestUri = request.getRequestURI();
		
        //去除上下文,获得相对路径
        String relativePath = requestUri.substring(request.getContextPath().length());

        //当前请求路径是否忽略
		boolean match = urlProperties.getIgnoreUrls().stream().anyMatch(url -> pathMatcher.match(url, relativePath));

        //当前请求路径忽略,返回null
		if (match) {
			return null;
		}
		
        //通过resolveFromAuthorizationHeader方法获取token 详情见2.3.1
		final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
        
        //通过isParameterTokenSupportedForRequest方法从请求参数中解析出 Bearer Token,并返回 Token 字符串详情见2.3.2
        //通过isParameterTokenSupportedForRequest 判断当前请求是否支持从请求参数中获取 Token 详情见2.3.3
		final String parameterToken = isParameterTokenSupportedForRequest(request)
				? resolveFromRequestParameters(request) : null;
        //请求头中获取到token
		if (authorizationHeaderToken != null) {
			//请求参数中也有token,则抛出重复token
            if (parameterToken != null) {
				final BearerTokenError error = BearerTokenErrors
						.invalidRequest("Found multiple bearer tokens in the request");
				throw new OAuth2AuthenticationException(error);
			}
            //返回请求头中的token
			return authorizationHeaderToken;
		}
        
        //检测是否支持参数中获取token(详情见2.3.4),并且判断参数中是否有token
        //如果支持参数中获取token,并且参数中有token则返回参数中的token
		if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
			return parameterToken;
		}
		return null;
	}

    //详情2.3.1  从请求头中解析出 Bearer Token,并返回 Token 字符串
	private String resolveFromAuthorizationHeader(HttpServletRequest request) {
		//从请求头中获取请求头名称,默认为 Authorization的值
        String authorization = request.getHeader(this.bearerTokenHeaderName);
		//不以不区分大小写的方式以 "bearer" 开头,则返回 null,表示未找到有效的 Bearer Token
        if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
			return null;
		}
        //通过正则表达式对 authorization 进行匹配
		Matcher matcher = authorizationPattern.matcher(authorization);
		//果匹配失败,即 Bearer Token 格式不正确,则抛出 OAuth2AuthenticationException 异常,异常信息为 "Bearer token is malformed"
        if (!matcher.matches()) {
			BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
			throw new OAuth2AuthenticationException(error);
		}
        //匹配成功,通过 matcher.group("token") 方法提取出 Token 字符串,并返回
		return matcher.group("token");
	}
    

    //详情2.3.2 从请求参数中解析出 Bearer Token,并返回 Token 字符串
	private static String resolveFromRequestParameters(HttpServletRequest request) {
        //通过 request.getParameterValues("access_token") 方法获取名为 "access_token" 的请求参数的值,存储在 values 数组中
		String[] values = request.getParameterValues("access_token");
		//如果 values 为 null 或长度为 0,则返回 null,表示未找到有效的 Bearer Token
        if (values == null || values.length == 0) {
			return null;
		}
        //如果 values 的长度为 1,则直接返回第一个值(默认取第一个),即 Token 字符串
		if (values.length == 1) {
			return values[0];
		}
        //如果 values 的长度大于 1,表示请求中包含多个 Bearer Token,此时抛出 OAuth2AuthenticationException 异常,异常信息为 "Found multiple bearer tokens in the request"
		BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
		throw new OAuth2AuthenticationException(error);
	}
	
    //详情2.3.3 判断当前请求是否支持从请求参数中获取 Token
	private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
		return (("POST".equals(request.getMethod())
				&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
				|| "GET".equals(request.getMethod()));
	}

    //详情2.3.4该方法的作用是判断是否允许在当前请求中通过请求参数获取 Token
	private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {
        /*
        满足情况:
        1、allowFormEncodedBodyParameter 的值是否为 true,并且当前请求方法为 "POST",且请求的 Content-Type 为 "application/x-www-form-urlencoded
        2、allowUriQueryParameter 的值是否为 true,并且当前请求方法为 "GET"
        */
		return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod())
				&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
				|| (this.allowUriQueryParameter && "GET".equals(request.getMethod())));
	}	

}

2.4、资源服务器异常处理resourceAuthExceptionEntryPoint

/**
	 * 资源服务器异常处理
	 * @param objectMapper jackson 输出对象
	 * @param securityMessageSource 自定义国际化处理器
	 * @return ResourceAuthExceptionEntryPoint
	 */
@Bean
public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,
                                                                       MessageSource securityMessageSource) {
    return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);
}

具体内容解析

	/**
 * @author lengleng
 * @date 2019/2/1
 *
 * 客户端异常处理 AuthenticationException 不同细化异常处理
 */

//全参构造器,会生成一个带有所有 final 字段的构造函数
@RequiredArgsConstructor
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {

    //进行 JSON 序列化
	private final ObjectMapper objectMapper;

    //国际化消息处理
	private final MessageSource messageSource;

	@Override
	@SneakyThrows	//@SneakyThrows 是 Lombok 提供的注解,用于在方法上抛出异常时,自动将该异常包装为 RuntimeException 抛出
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) {
        
        /// 设置响应的字符编码为UTF8,内容类型为 JSON 
		response.setCharacterEncoding(CommonConstants.UTF8);
		response.setContentType(ContentType.JSON.getValue());
		//创建一个封装错误信息的对象
        R<String> result = new R<>();
		//设置code为失败
        //常量:Integer FAIL = 1;
        result.setCode(CommonConstants.FAIL);
        //设置响应状态码为未授权401
        //UNAUTHORIZED(401, HttpStatus.Series.CLIENT_ERROR, "Unauthorized"),
		response.setStatus(HttpStatus.UNAUTHORIZED.value());
        
         如果存在认证异常,设置错误消息为 "error",数据为认证异常的消
		if (authException != null) {
			result.setMsg("error");
			result.setData(authException.getMessage());
		}

		// 针对令牌过期返回特殊的 424
		if (authException instanceof InvalidBearerTokenException
				|| authException instanceof InsufficientAuthenticationException) {
			//设置响应状态码为 424(FAILED_DEPENDENCY)
            //FAILED_DEPENDENCY(424, HttpStatus.Series.CLIENT_ERROR, "Failed Dependency")
            response.setStatus(HttpStatus.FAILED_DEPENDENCY.value());
			
  设置特定的错误消息           result.setMsg(this.messageSource.getMessage("OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired",
					null, LocaleContextHolder.getLocale()));

			//如果用户令牌过期 修改code
			result.setCode(TOKEN_EXPIRED_FAIL);
		}
        
        //获取响应的输出流,通过该输出流可以向客户端发送数据
		PrintWriter printWriter = response.getWriter();
        //使用 Jackson 的 ObjectMapper 将 result 对象序列化为 JSON 格式的字符串
        //将序列化后的 JSON 字符串添加到输出流中,以便将其发送给客户端
		printWriter.append(objectMapper.writeValueAsString(result));
	}

}

2.5、资源服务器toke内省处理器opaqueTokenIntrospector

自定义认证器,用于通过传递的令牌进行身份验证

/**
	 * 资源服务器toke内省处理器
	 * @param authorizationService token 存储实现
	 * @return TokenIntrospector
	 */
@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
    return new PigxCustomOpaqueTokenIntrospector(authorizationService);
}

具体解析

/**
 * @author lengleng
 * @date 2022/5/28
 */
@Slf4j
@RequiredArgsConstructor
public class PigxCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

	private final OAuth2AuthorizationService authorizationService;

    //用于根据传递的令牌进行身份验证
	@Override
	public OAuth2AuthenticatedPrincipal introspect(String token) {
		//通过OAuth2AuthorizationService的实现类去获取对应的token 详情见2.5.1
        OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
		
        //如果找不到与令牌关联的授权信息,则抛出 InvalidBearerTokenException 异常,表示令牌无效
        if (Objects.isNull(oldAuthorization)) {
			throw new InvalidBearerTokenException(token);
		}

		// 客户端模式默认返回
        //判断授权类型是否为客户端模式
        //public static final AuthorizationGrantType CLIENT_CREDENTIALS = new AuthorizationGrantType("client_credentials");
		if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {
            //默认返回一个 PigxClientCredentialsOAuth2AuthenticatedPrincipal 对象。该对象包含了传递的授权信息的属性、空权限列表以及授权主体名称。
			return new PigxClientCredentialsOAuth2AuthenticatedPrincipal(oldAuthorization.getAttributes(),
					AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName());
		}

        //如果授权类型不是客户端模式,则获取所有实现了 PigxUserDetailsService 接口的 Bean 对象,并过滤出支持当前授权信息的 PigxUserDetailsService 对象
        //这里会获取到对应的PigxUserDetailsService的实现类
		Map<String, PigxUserDetailsService> userDetailsServiceMap = SpringContextHolder
				.getBeansOfType(PigxUserDetailsService.class);

        //选择支持度最高的 PigxUserDetailsService 对象(根据 Ordered 接口的顺序进行比较)
		Optional<PigxUserDetailsService> optional = userDetailsServiceMap.values().stream()
				.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
						oldAuthorization.getAuthorizationGrantType().getValue()))
				.max(Comparator.comparingInt(Ordered::getOrder));

        //获取用户信息
		UserDetails userDetails = null;
		try {
			Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());
			UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
			Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
			userDetails = optional.get().loadUserByUser((PigxUser) tokenPrincipal);
		}
		catch (UsernameNotFoundException notFoundException) {
			log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
			throw notFoundException;
		}
		catch (Exception ex) {
			log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
		}

		// 注入客户端信息,方便上下文中获取
		PigxUser pigxUser = (PigxUser) userDetails;
		Objects.requireNonNull(pigxUser).getAttributes().put(SecurityConstants.CLIENT_ID,
				oldAuthorization.getRegisteredClientId());
		return pigxUser;
	}

}

2.5.1 authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);

其实现类有三个,我们用的是Pix提供的实现类PigxRedisOAuth2AuthorizationService

在这里插入图片描述

其中的方法如下,即从redis中去获取对应的token信息

@Override
@Nullable
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
    Assert.hasText(token, "token cannot be empty");
    Assert.notNull(tokenType, "tokenType cannot be empty");
    redisTemplate.setValueSerializer(RedisSerializer.java());
    return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
}
3、PigxResourceServerConfiguration 资源服务器认证授权配置
@Slf4j
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class PigxResourceServerConfiguration {

	protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;

	private final PermitAllUrlProperties permitAllUrl;

	private final PigxBearerTokenExtractor pigxBearerTokenExtractor;

	private final OpaqueTokenIntrospector customOpaqueTokenIntrospector;

	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls().stream().map(AntPathRequestMatcher::new)
				.collect(Collectors.toList()).toArray(new AntPathRequestMatcher[] {});

		http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers).permitAll()
				.anyRequest().authenticated())
				.oauth2ResourceServer(
						oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector))
								.authenticationEntryPoint(resourceAuthExceptionEntryPoint)
								.bearerTokenResolver(pigxBearerTokenExtractor))
				.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
				.csrf(AbstractHttpConfigurer::disable);

		return http.build();
	}

}

这段代码是一个 Java 类 PigxResourceServerConfiguration,它配置了 Spring Security 的资源服务器。

首先,类中定义了一些依赖注入的属性:

  • resourceAuthExceptionEntryPoint:用于处理资源服务器的异常入口点。
  • permitAllUrl:用于配置允许所有请求的 URL 列表。
  • pigxBearerTokenExtractor:用于从请求中提取 Bearer Token。
  • customOpaqueTokenIntrospector:自定义的不透明令牌内省器。

接下来,使用 @Bean 注解标记了一个方法 securityFilterChain,该方法返回一个 SecurityFilterChain 对象。该方法的作用是配置 Spring Security 的安全过滤器链。

securityFilterChain 方法中,首先根据 permitAllUrl 中的忽略 URL 列表创建了一个 AntPathRequestMatcher 数组 requestMatchers。这里使用了 Stream API 将忽略 URL 列表转换为 AntPathRequestMatcher 数组。

然后,通过调用 authorizeHttpRequests() 方法配置了请求的授权规则。其中,使用 requestMatchers(requestMatchers).permitAll().anyRequest().authenticated() 来配置了忽略 URL 列表的请求允许访问,而其他请求需要进行身份验证。

接着,使用 oauth2ResourceServer() 方法配置了 OAuth2 资源服务器。通过调用 opaqueToken() 方法设置了自定义的不透明令牌内省器,并使用 bearerTokenResolver() 方法设置了用于解析 Bearer Token 的 pigxBearerTokenExtractor

继续,使用 headers() 方法配置了 HTTP 头部,通过调用 frameOptions() 方法禁用了 X-Frame-Options。

最后,使用 csrf() 方法禁用了 CSRF(跨站请求伪造)保护,并调用 http.build() 方法构建并返回了安全过滤器链。

这段代码的作用是配置 Spring Security 的资源服务器,定义了请求的授权规则、OAuth2 资源服务器和一些其他配置。

4、PigxFeignClientConfiguration.class
public class PigxFeignClientConfiguration {

	/**
	 * 注入 oauth2 feign token 增强
	 * @param tokenResolver token获取处理器
	 * @return 拦截器
	 */
	@Bean
	public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) {
		return new PigxOAuthRequestInterceptor(tokenResolver);
	}

	@Bean
	public RequestInterceptor clientToCRequestInterceptor() {
		return new PigxClientToCRequestInterceptor();
	}

}

4.1 、oauthRequestInterceptor方法

该类的作用是在发送请求之前拦截并修改请求模板(RequestTemplate

/**
	 * 注入 oauth2 feign token 增强
	 * @param tokenResolver token获取处理器
	 * @return 拦截器
	 */
@Bean
public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) {
    return new PigxOAuthRequestInterceptor(tokenResolver);
}

具体详解:

/**
 * oauth2 feign token传递
 *
 * 重新 OAuth2FeignRequestInterceptor ,官方实现部分常见不适用
 *
 * @author lengleng
 * @date 2022/5/29
 */
@Slf4j
@RequiredArgsConstructor
public class PigxOAuthRequestInterceptor implements RequestInterceptor {

	private final BearerTokenResolver tokenResolver;

	/**
	 * Create a template with the header of provided name and extracted extract </br>
	 *
	 * 1. 如果使用 非web 请求,header 区别 </br>
	 *
	 * 2. 根据authentication 还原请求token
	 * @param template
	 */
	@Override
	public void apply(RequestTemplate template) {
		Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
		// 带from 请求直接跳过
		if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
			return;
		}

		// 非web 请求直接跳过
		if (WebUtils.getRequest() == null) {
			return;
		}
		HttpServletRequest request = WebUtils.getRequest();
		// 避免请求参数的 query token 无法传递
		String token = tokenResolver.resolve(request);
		if (StrUtil.isBlank(token)) {
			return;
		}
        //添加token信息
		template.header(HttpHeaders.AUTHORIZATION,
				String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), token));
	}
}

4.2、 clientToCRequestInterceptor方法

@Bean
public RequestInterceptor clientToCRequestInterceptor() {
    return new PigxClientToCRequestInterceptor();
}

具体详解:

/**
 * TOC 客户标识传递
 *
 * @author lengleng
 * @date 2023/3/17
 */
@Slf4j
public class PigxClientToCRequestInterceptor implements RequestInterceptor {

	/**
	 * Called for every request. Add data using methods on the supplied
	 * {@link RequestTemplate}.
	 * @param template
	 */
	public void apply(RequestTemplate template) {
		String reqVersion = WebUtils.getRequest() != null
				? WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC) : null;

		if (StrUtil.isNotBlank(reqVersion)) {
			log.debug("feign  add header toc :{}", reqVersion);
			template.header(SecurityConstants.HEADER_TOC, reqVersion);
		}
	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv5改进 | 损失函数篇 | MPDIoU、InnerMPDIoU助力细节涨点

一、本文介绍 本文为读者详细介绍了YOLOv5模型的最新改进,带来的改进机制是最新的损失函数MPDIoU和融合了最新的Inner思想的InnerMPDIoU。提升检测精度和处理细节方面的作用。通过深入探讨MPDIoU和InnerMPDIoU(全网首发)的工作原理和实际代码实现,本文旨在指导读者如何将这些…

[文件I/O操作] 文件的介绍

目录 1.认识文件 2.树型组织结构和目录 3.文件路径 4.文件类型 5.用Java代码操作文件 5.1 File概述 5.2代码实例 5.2.1观察get系列代码的差异 ​编辑 5.3.2普通文件的创建、删除 ​编辑 5.3.3普通文件的删除 ​编辑 5.3.4观察目录的创建 ​编辑 5.3.5 观察文件重…

【SpringBoot】事务管理

1. 事务管理的概念 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位。事务会把所有的操作作为一个整体&#xff0c;一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功&#xff0c;要么同时失败。 事务的操作主要有三步&#xff1a; 开启事务&…

逆置算法和数组循环移动算法

元素逆置 概述&#xff1a;其实就是将 第一个元素和最后一个元素交换&#xff0c;第二个元素和倒数第二个元素交换&#xff0c;依次到中间位置。用途&#xff1a;可用于数组的移动&#xff0c;字符串反转&#xff0c;链表反转操作&#xff0c;栈和队列反转等操作。 逆置图解 …

Javaweb之Mybatis的XML配置文件的详细解析

2. Mybatis的XML配置文件 Mybatis的开发有两种方式&#xff1a; 注解 XML 2.1 XML配置文件规范 使用Mybatis的注解方式&#xff0c;主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能&#xff0c;建议使用XML来配置映射语句&#xff0c;也就是将SQL语句写在…

陆地探测一号01组(L波段差分干涉SAR)卫星

1. 卫星简介 陆地探测一号01组A/B卫星&#xff08;LT-1A/1B&#xff09;先后于2022年1月26日、2月27日在酒泉卫星发射中心成功发射&#xff0c;又被称为L波段差分干涉SAR卫星&#xff0c;是《国家民用空间基础设施中长期发展规划&#xff08;2015-2025年&#xff09;》中首个科…

DeepPurpose 生物化学深度学习库;蛋白靶点小分子药物对接亲和力预测虚拟筛选

参考: https://blog.csdn.net/c9Yv2cf9I06K2A9E/article/details/107649770 https://github.com/kexinhuang12345/DeepPurpose ##安装 pip install DeepPurpose rdkitDeepPurpose包括: 数据: 关联TDC库下载,是同一作者开发的 https://blog.csdn.net/weixin_42357472/artic…

Windows:笔记本电脑设置休眠教程

前言 不知道大家在使用【Windows】笔记本有没有这个习惯&#xff0c;我会把他的电池选项的【休眠】设置进行打开。因为作为我们开发人员电脑一般是一周关一次机&#xff0c;有时候一个月关一次机。这时候【休眠】功能就给我们提供了一个好处&#xff0c;我们选择了【休眠】后电…

3D点云上的深度学习综述

1 Title Deep Learning for 3D Point Clouds: A Survey&#xff08;Yulan Guo; Hanyun Wang; Qingyong Hu; Hao Liu; Li Liu; Mohammed Bennamoun&#xff09;【IEEE Transactions on Pattern Analysis and Machine Intelligence 2020】 2 Conclusion Deep learning on point…

[C#]使用sdcb.paddleocr部署v4版本ocr识别模型

【官方框架地址】 https://github.com/sdcb/PaddleSharp 【算法介绍】 PaddleOCR&#xff0c;全称为PaddlePaddle OCR&#xff0c;是PaddlePaddle深度学习平台下的一款强大的光学字符识别工具。它利用深度学习技术&#xff0c;实现了高精度的文字识别&#xff0c;可以帮助用户…

Python | 基于Mediapipe框架的手势识别系统

一、项目要求 1、题目 本题着力于解决会商演示系统中的非接触式人机交互问题&#xff0c;具体而言&#xff0c;其核心问题就是通过计算机视觉技术实现对基于视频流的手势动作进行实时检测和识别。通过摄像头采集并识别控制者连续的手势动作&#xff0c;完成包括点击、平移、缩放…

Opencv实验合集——实验七:二维码和条形码匹配

1.概念 二维码&#xff08;QR码&#xff09; 概念&#xff1a; 二维码是一种矩阵式的二维条码&#xff0c;由黑白方块组成&#xff0c;可以存储大量的信息&#xff0c;包括文本、链接、数字等。QR码的编码方式是在矩阵中通过不同的黑白方块组合表示不同的信息。 特点&#xf…

【读书笔记】《白帽子讲web安全》跨站脚本攻击

目录 前言&#xff1a; 第二篇 客户端脚本安全 第3章 跨站脚本攻击&#xff08;XSS&#xff09; 3.1XSS简介 3.2XSS攻击进阶 3.2.1初探XSS Payload 3.2.2强大的XSS Payload 3.2.2.1 构造GET与POST请求 3.2.2.2XSS钓鱼 3.2.2.3识别用户浏览器 3.2.2.4识别用户安装的软…

【linux学习】重定向

目录 重定向标准输出、标准输入和标准错误标准输出重定向标准错误重定向将标准输出和标准错误重定向到同一个文件处理不想要的输出标准输入重定向 管道过滤器uniq-报告或者忽略文件中重复的行wc-打印行数、字数和字节数grep-打印匹配行head/tail 打印文件的开头部分/结尾部分te…

嵌入式系统习题(考试相关)

文章目录 上一篇嵌入式系统概述ARM技术概述ARM指令Thumb指令集ARM程序设计 上一篇 嵌入式系统复习–基于ARM的嵌入式程序设计 嵌入式系统概述 嵌入式系统中常用的通信接口包括哪些&#xff1f; RS-232C串行通信接口&#xff0c;RS-422串行通信接口&#xff0c;RS-485串行通信…

【APACHE】的认识和基础配置参数

#主页传送:江南的江 #每日鸡汤&#xff1a;人生没有如果和假设&#xff0c;只有后果和结果。生活有进有退&#xff0c;输什么也不能输心情。生活简单就是迷人的&#xff0c;学会简单其实就是不简单。要学会平静地接受现实&#xff0c;学会对自己说声顺其自然&#xff0c;学会坦…

基于实时Linux+FPGA实现NI CompactRIO系统详解

利用集成的软件工具链&#xff0c;结合信号调理I/O模块&#xff0c;轻松构建和部署实时应用程序。 什么是CompactRIO&#xff1f; CompactRIO系统提供了高处理性能、传感器专用I/O和紧密集成的软件工具&#xff0c;使其成为工业物联网、监测和控制应用的理想之选。实时处理器提…

leetcode——杨辉三角

https://leetcode.cn/problems/pascals-triangle/ 杨辉三角&#xff1a; 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 核心思想&#xff1a;找出杨辉三角的规律&#xff0c;发…

如何用UE5 的小白人替换成自己的 metahumen 数字人

1、用QuixelBridge 插件导入制作好的metahumen数字人 2、创建项目时如有选择第三人称游戏&#xff0c;在内容目录中找到第三人称游戏小白人的蓝图类&#xff0c;对其进行复制一个&#xff0c;重命名&#xff0c;我这里命名为BP_METAHUMEN&#xff0c; 并移到Metahumen目录下方便…

python统计分析——箱线图(plt.boxplot)

参考资料&#xff1a;用python动手学统计学 使用matplotlib.pyplot.boxplot()函数绘制箱线图 import numpy as np import pandas as pd from matplotlib import pyplot as pltdata_set1np.array([2,3,3,4,4,4,4,5,5,6]) data_set2np.array([[2,3,3,4,4,4,4,5,5,6],[5,6,6,7,7…