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

这篇具有很好参考价值的文章主要介绍了PIG框架学习2——资源服务器的配置详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

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>内容如下所示:

pig框架,从零开始的PIG框架学习生活,学习,java

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

pig框架,从零开始的PIG框架学习生活,学习,java

其中的方法如下,即从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();
}

具体详解:文章来源地址https://www.toymoban.com/news/detail-828761.html

/**
 * 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);
		}
	}

}

到了这里,关于PIG框架学习2——资源服务器的配置详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity Addressables学习笔记(1)---创建远程服务器加载资源

    Unity Addressables学习笔记—汇总 Bulid Path选择RemoteBuildPath Load Path我选择了custom,地址是http://localhost:8080/WebGL/ 遇坑1 :最开始我选择的Build Path 是 LocalBuildPath,Load Path是custom的时候报错如下: 解决办法:把Build Path 改为RemoteBuildPath后才好,我也不知道为什么不能把本地的资源放到远程

    2024年02月14日
    浏览(41)
  • 针对“您的计算机配置似乎是正确的,但该设备或资源(DNS 服务器)没有响应“问题解决

    解决思路: 1.在“开始”中找到“运行”(或者直接【Win】+【R】),然后输入“cmd”进入管理员操作界面。 2.在管理员界面中输入命令:ipconfig/all然后按Enter键确认即可显示WindowsIP配置,在这里我们可以查看我们的DNS服务器地址。  IPv4 地址 . . . . . . . . . . . . :   默认网关. .

    2024年02月07日
    浏览(48)
  • 如何查看服务器各项指标的配置-具体指令-服务器配置参数详解-大模型训练推荐配置单服务器和服务器之间显卡直通叠加扩容

    要查看服务器的各项组件配置,您可以执行以下步骤: 操作系统信息 : 使用命令 uname -a (Linux/Unix)或 systeminfo (Windows)来查看操作系统的版本和内核信息。 CPU 信息 : 在Linux/Unix系统上,运行 lscpu 命令来查看CPU的详细信息。 在Windows系统上,您可以使用 wmic cpu get caption 命

    2024年02月09日
    浏览(51)
  • 使用MobaXterm连接服务器并利用Anaconda进行安装pytoch框架跑深度学习模型(使用学校服务器+显卡进行深度学习)

    在开始之前你需要找学校服务器负责人 申请服务器账号和密码 以及 校内外网IP和端口号 ;另外还需要知道学校 服务器显卡cuda版本 ,以及去pytorch官网查看显卡cuda版本对应的 pytorch版本 一、安装MobaXterm 1.下载MobaXterm 软件的下载可以去这里:我都已经给大家准备好了。 在我网

    2024年02月07日
    浏览(64)
  • 详解小程序配置服务器域名

    配置小程序的服务器域名是小程序开发过程中的一个关键步骤,主要涉及到设置小程序后端服务的网络通信。这通常是为了确保小程序能够安全、稳定地与服务器进行数据交换。以下是配置小程序服务器域名的详细步骤: 1. 准备服务器域名 首先,你需要有一个已经注册并备

    2024年03月13日
    浏览(80)
  • 【云服务器 ECS 实战】云服务器新手指南(配置+使用详解)

    谈起云计算,相信大家都不陌生,可以说它已经颠覆了我们生活中的很多应用场景,如虚拟化办公、物联网、虚拟 IDC 以及日常的生活娱乐、金融、教育等多个行业领域都享受到了来自“云”技术的红利,毫不夸张的说:“云”已经渗透到了我们日常生活中的各个领域。 那么

    2024年02月03日
    浏览(47)
  • C++服务器框架开发11——编译调试1/cmake学习

    该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见:[C++高级教程]从零开始开发服务器框架(sylar) 上一篇:C++服务器框架开发10——日志系统1~9代码 学习到第6个视频的00:59,由于不了解编译,这次先学习下cmake。下图是CMakeLists.txt中的内容。 参考自文

    2024年02月16日
    浏览(83)
  • Linux服务器配置指南:网络、用户管理、共享服务及DNS配置详解

    💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】 🤟 基于Web端打造的:👉轻量化工具创作平台 💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 设置虚拟机的网卡为仅主机模式,并要求服务器采用双网卡, ens33 IP地址设置为192.168. 5.x/24(其中x学号),

    2024年02月05日
    浏览(58)
  • [学习分享]----sylar服务器框架源码阅读--IO协程调度模块

    sylar作者在本站的地址为 这里,也可以查看 作者主页,也有视频教程可以 点击这里。此外,也可以看一下赵路强大佬的关于sylar协程模块的博客 点击这里,我本人在阅读sylar源码的时候也是参考了赵路强大佬的解析 可以点击这里。 各位看官也可以加我的qq和我讨论2511010742

    2024年01月17日
    浏览(47)
  • 深度学习模型部署——Flask框架轻量级部署+阿里云服务器

    ​因为参加一个比赛,需要把训练好的深度学习模型部署到web端,第一次做,在网上也搜索了很多教程,基本上没有适合自己的,只有一个b站up主讲的还不错 https://www.bilibili.com/video/BV1Qv41117SR/?spm_id_from=333.999.0.0vd_source=6ca6a313467efae52a28428a64104c10 https://www.bilibili.com/video/BV1Qv41117

    2024年02月07日
    浏览(83)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包