Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/develop'

linwenhua 3 gadi atpakaļ
vecāks
revīzija
accbfa31d5

+ 26 - 5
pom.xml

@@ -17,6 +17,10 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-gateway</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-gateway-server</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-loadbalancer</artifactId>
@@ -77,20 +81,37 @@
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
         </dependency>
+        <!--启用 Spring Security 对 OAuth 2.0 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-client</artifactId>
+        </dependency>
+        <!--激活TokenRelay-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-resource-server</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-oauth2-jose</artifactId>
         </dependency>
-        <!--HttpClient -->
         <dependency>
             <groupId>commons-httpclient</groupId>
             <artifactId>commons-httpclient</artifactId>
             <version>3.1</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-        </dependency>
     </dependencies>
 
     <build>

+ 9 - 4
src/main/java/com/inspur/smsb/gateway/SmsbGatewayApplication.java

@@ -2,16 +2,21 @@ package com.inspur.smsb.gateway;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
-import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.WebSession;
+import reactor.core.publisher.Mono;
 
 /**
  * 网关服务
  *
  * @author liangke
  */
-@EnableDiscoveryClient
-@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
+@SpringBootApplication(exclude = ReactiveUserDetailsServiceAutoConfiguration.class)
+@RestController
 public class SmsbGatewayApplication {
     public static void main(String[] args) {
         SpringApplication.run(SmsbGatewayApplication.class, args);

+ 52 - 0
src/main/java/com/inspur/smsb/gateway/config/AuthorizationManager.java

@@ -0,0 +1,52 @@
+package com.inspur.smsb.gateway.config;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.web.server.authorization.AuthorizationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+
+/**
+ * 鉴权管理器
+ */
+@Component
+@AllArgsConstructor
+@Slf4j
+public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
+    @Override
+    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
+        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
+        String path = request.getURI().getPath();
+        PathMatcher pathMatcher = new AntPathMatcher();
+        // todo 资源权限角色关系列表,需要初始化到容器中
+        Map<String, List<String>> resourceRolesMap = new HashMap<>();
+        List<String> authorities = new ArrayList<>();
+        resourceRolesMap.put("/token/**",Collections.singletonList("force"));
+        resourceRolesMap.forEach((resource, roles) -> {
+            if (pathMatcher.match(resource, path)) {
+                authorities.addAll(roles);
+            }
+        });
+        return mono
+            .filter(Authentication::isAuthenticated)
+            .flatMapIterable(Authentication::getAuthorities)
+            .map(GrantedAuthority::getAuthority)
+            .any(roleId -> {
+                log.info("访问路径:{}", path);
+                log.info("用户角色roleId:{}", roleId);
+                log.info("资源需要权限authorities:{}", authorities);
+                return authorities.contains(roleId);
+            })
+            .map(AuthorizationDecision::new)
+            .defaultIfEmpty(new AuthorizationDecision(false));
+    }
+}

+ 32 - 0
src/main/java/com/inspur/smsb/gateway/config/CustomServerAccessDeniedHandler.java

@@ -0,0 +1,32 @@
+package com.inspur.smsb.gateway.config;
+
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.Charset;
+
+/**
+ * 无权访问自定义响应
+ */
+@Component
+public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler {
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
+        ServerHttpResponse response=exchange.getResponse();
+        response.setStatusCode(HttpStatus.FORBIDDEN);
+        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+        response.getHeaders().set("Access-Control-Allow-Origin","*");
+        response.getHeaders().set("Cache-Control","no-cache");
+        String body= "没权限访问!";
+        DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
+        return response.writeWith(Mono.just(buffer));
+    }
+}

+ 1 - 0
src/main/java/com/inspur/smsb/gateway/config/RateLimiterConfiguration.java

@@ -1,5 +1,6 @@
 package com.inspur.smsb.gateway.config;
 
+
 import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 37 - 0
src/main/java/com/inspur/smsb/gateway/config/RealmRoleConverter.java

@@ -0,0 +1,37 @@
+package com.inspur.smsb.gateway.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class RealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>>{
+
+    @Override
+    public Collection<GrantedAuthority> convert(Jwt jwt) {
+        final Map<String, List<String>> realmAccess = (Map<String, List<String>>) jwt.getClaims().get("realm_access");
+        List<GrantedAuthority> realmList = realmAccess.get("roles")
+            .stream()
+            .map(roleName -> roleName)
+            .map(SimpleGrantedAuthority::new)
+            .collect(Collectors.toList());
+        final Map<String, List<String>> resourceAccess = (Map<String, List<String>>) jwt.getClaims().get("resource_access");
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode token = mapper.convertValue(resourceAccess, JsonNode.class);
+        Set<String> rolesWithPrefix = new HashSet<>();
+        token.elements()
+            .forEachRemaining(e -> e.path("roles")
+                .elements()
+                .forEachRemaining(r -> rolesWithPrefix.add(r.asText())));
+        final List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(rolesWithPrefix.toArray(new String[0]));
+        realmList.addAll(authorityList);
+        return realmList;
+    }
+
+}

+ 53 - 0
src/main/java/com/inspur/smsb/gateway/config/ResourceServerConfig.java

@@ -0,0 +1,53 @@
+package com.inspur.smsb.gateway.config;
+
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+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.ReactiveJwtAuthenticationConverterAdapter;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import reactor.core.publisher.Mono;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+
+/**
+ * 资源服务器配置
+ */
+@AllArgsConstructor
+@Configuration
+@EnableWebFluxSecurity
+public class ResourceServerConfig {
+
+    private AuthorizationManager authorizationManager;
+
+    private CustomServerAccessDeniedHandler customServerAccessDeniedHandler;
+
+    @Bean
+    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+        http.oauth2Login(withDefaults());
+        http.oauth2ResourceServer().jwt()
+            .jwtAuthenticationConverter(jwtAuthenticationConverter());
+        http.authorizeExchange()
+            // todo 增加白名单
+            .pathMatchers("/**").permitAll()
+            .anyExchange().access(authorizationManager)
+            .and()
+            .exceptionHandling()
+            // 处理未授权
+            .accessDeniedHandler(customServerAccessDeniedHandler)
+            .and().csrf().disable();
+
+        return http.build();
+    }
+    @Bean
+    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
+        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
+        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new RealmRoleConverter());
+        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
+    }
+}

+ 41 - 0
src/main/java/com/inspur/smsb/gateway/filter/AuthorizationClient.java

@@ -0,0 +1,41 @@
+package com.inspur.smsb.gateway.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.rotation.AdapterTokenVerifier;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * 校验token
+ * @author wangbo13
+ */
+@Slf4j
+@Service
+public class AuthorizationClient {
+    @Value("${keycloak.realm}")
+    private String realm;
+    @Value("${keycloak.resource}")
+    private String resource;
+    @Value("${keycloak.auth-server-url}")
+    private String authServerUrl;
+
+    public boolean verifyToken(String token) {
+        AccessToken accessToken = null;
+        try {
+            AdapterConfig adapterConfig = new AdapterConfig();
+            adapterConfig.setRealm(realm);
+            adapterConfig.setResource(resource);
+            adapterConfig.setAuthServerUrl(authServerUrl);
+            adapterConfig.setDisableTrustManager(true);
+            KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(adapterConfig);
+            accessToken = AdapterTokenVerifier.verifyToken(token, deployment);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return accessToken != null ? false : true;
+    }
+}

+ 3 - 5
src/main/java/com/inspur/smsb/gateway/filter/WebFluxUserRequestInfoFilter.java

@@ -1,6 +1,5 @@
 package com.inspur.smsb.gateway.filter;
 
-import com.alibaba.cola.dto.SingleResponse;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.google.common.base.Strings;
@@ -8,9 +7,7 @@ import com.inspur.smsb.gateway.dto.KeycloakUserDto;
 import com.inspur.smsb.gateway.utils.HttpClientUtil;
 import com.nimbusds.jose.JWSObject;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.RequestEntity;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.GlobalFilter;
@@ -24,7 +21,6 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -35,6 +31,8 @@ import java.util.stream.Collectors;
 @Slf4j
 @Component
 public class WebFluxUserRequestInfoFilter implements GlobalFilter {
+    @Autowired
+    private AuthorizationClient authorizationClient;
     @Value("${keycloak.auth-server-url}")
     private String keyCloakServiceUrl;
     @Value("${keycloak.realm}")

+ 2 - 2
src/main/java/com/inspur/smsb/gateway/utils/HttpClientUtil.java

@@ -56,7 +56,7 @@ public class HttpClientUtil {
 
         PostMethod httpPost = new PostMethod(url);
         try {
-            RequestEntity requestEntity = new ByteArrayRequestEntity(params.getBytes("utf-8"));
+            ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(params.getBytes("utf-8"));
             httpPost.setRequestEntity(requestEntity);
             if (StringUtils.hasText(type)) {
                 httpPost.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
@@ -126,7 +126,7 @@ public class HttpClientUtil {
         httpput.setRequestHeader("Authorization", "Bearer " + token);
         try {
             if (param != null) {
-                RequestEntity entity = new StringRequestEntity(param, "application/json", "UTF-8");
+                StringRequestEntity entity = new StringRequestEntity(param, "application/json", "UTF-8");
                 httpput.setRequestEntity(entity);
             }
             client.executeMethod(httpput);