package com.njcn.gateway.config; import cn.hutool.core.codec.Base64; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import com.njcn.common.pojo.constant.SecurityConstants; import com.njcn.gateway.enums.GateWayEnum; import com.njcn.gateway.security.AuthorizationManager; import com.njcn.gateway.utils.ResponseUtils; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import reactor.core.publisher.Mono; import java.io.InputStream; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; /** * @author hongawen * 资源服务器配置 */ @AllArgsConstructor @Configuration @EnableWebFluxSecurity public class ResourceServerConfig { private final AuthorizationManager authorizationManager; private final WhiteListConfig whiteListConfig; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()) // 本地获取公钥 .publicKey(rsaPublicKey()); http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint()); http.authorizeExchange() .pathMatchers(Convert.toStrArray(whiteListConfig.getUrls())).permitAll() .anyExchange().access(authorizationManager) .and() .exceptionHandling() // 处理未授权 .accessDeniedHandler(accessDeniedHandler()) // 处理未认证 .authenticationEntryPoint(authenticationEntryPoint()) .and().csrf().disable(); return http.build(); } /** * 访问拒绝处理器 */ @Bean ServerAccessDeniedHandler accessDeniedHandler() { return (exchange, denied) -> Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> ResponseUtils.writeErrorInfo(response, GateWayEnum.NO_AUTHORIZATION)); } /** * token无效或者已过期自定义响应 */ @Bean ServerAuthenticationEntryPoint authenticationEntryPoint() { return (exchange, e) -> Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> ResponseUtils.writeErrorInfo(response, GateWayEnum.ACCESS_TOKEN_EXPIRE_JWT)); } /** * @link https://blog.csdn.net/qq_24230139/article/details/105091273 * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication * 需要把jwt的Claim中的authorities加入 * 方案:重新定义权限管理器,默认转换器JwtGrantedAuthoritiesConverter */ @Bean public Converter> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } /** * 本地获取JWT验签公钥 */ @SneakyThrows @Bean public RSAPublicKey rsaPublicKey() { Resource resource = new ClassPathResource("public.key"); InputStream is = resource.getInputStream(); String publicKeyData = IoUtil.read(is).toString(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec((Base64.decode(publicKeyData))); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPublicKey) keyFactory.generatePublic(keySpec); } }