From fe56edd54ffee26f760582a6b051a8ce6fc08e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E5=B0=8F=E4=BA=91?= Date: Mon, 21 Jul 2025 18:14:48 +0800 Subject: [PATCH] xx --- .../gateway/filter/JwtPermissionFilter.java | 70 +++++++ .../com/tuoheng/oauth/oidc/db/DbService.java | 174 ++++++++++++++++++ .../tuoheng/oauth/oidc/db/entity/Client.java | 49 +++++ .../tuoheng/oauth/oidc/db/entity/Tenant.java | 31 ++++ .../oauth/oidc/db/entity/TenantClient.java | 38 ++++ .../tuoheng/oauth/oidc/db/entity/User.java | 72 ++++++++ .../oidc/db/entity/UserClientAuthorities.java | 37 ++++ .../TenantAwareAuthenticationProvider.java | 6 +- .../service/CustomUserDetailsService.java | 36 +++- .../oidc/token/CustomTokenCustomizer.java | 4 + 10 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/DbService.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Client.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Tenant.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/TenantClient.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/User.java create mode 100644 oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/UserClientAuthorities.java diff --git a/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java new file mode 100644 index 0000000..6f1e154 --- /dev/null +++ b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java @@ -0,0 +1,70 @@ +package com.tuoheng.gateway.filter; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; + +@Component +public class JwtPermissionFilter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { + + // 获取完整的请求URL + String fullUrl = exchange.getRequest().getURI().toString(); + System.out.println("用户访问的完整URL: " + fullUrl); + + // 获取请求的path + String path = exchange.getRequest().getPath().toString(); + System.out.println("用户访问的path: " + path); + + // 获取请求的host + String host = exchange.getRequest().getHeaders().getHost().toString(); + System.out.println("用户访问的host: " + host); + + String hostName = exchange.getRequest().getHeaders().getHost().getHostName(); + System.out.println("用户访问的域名: " + hostName); + + // 获取Referer(如果有) + String referer = exchange.getRequest().getHeaders().getFirst("Referer"); + System.out.println("Referer: " + referer); +// 从Spring Security上下文获取JWT + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + if (securityContext.getAuthentication() instanceof JwtAuthenticationToken) { + JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) securityContext.getAuthentication(); + Jwt jwt = jwtAuth.getToken(); + String username = jwt.getClaimAsString("username"); + String clientId = jwt.getClaimAsString("client_id"); + String tenantCode = jwt.getClaimAsString("tenant_code"); + String authorities = jwt.getClaimAsString("clientIds"); + + // 你可以在这里做权限判断 + System.out.println("网关解析到token字段:"); + System.out.println("用户名: " + username); + System.out.println("客户端ID: " + clientId); + System.out.println("租户代码: " + tenantCode); + System.out.println("用户权限: " + authorities); + + //该域名没有权限 + if(!authorities.contains(hostName)){ + exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.FORBIDDEN); + return exchange.getResponse().setComplete(); + } + } + return chain.filter(exchange); + }); + + } + + @Override + public int getOrder() { + return -1; // 优先级高 + } +} \ No newline at end of file diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/DbService.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/DbService.java new file mode 100644 index 0000000..bdb146d --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/DbService.java @@ -0,0 +1,174 @@ +package com.tuoheng.oauth.oidc.db; + +import com.tuoheng.oauth.oidc.db.entity.*; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class DbService { + + List clients = new ArrayList<>(); + List tenants = new ArrayList<>(); + List tenantClients = new ArrayList<>(); + List users = new ArrayList<>(); + List userClientAuthorities = new ArrayList<>(); + + // 构造函数初始化一些数据 + public DbService() { + + // 添加租户 + tenants.add(new Tenant(1L, "t1")); + tenants.add(new Tenant(2L, "t2")); + + // 添加客户端 + clients.add(new Client(1L, "a-client","a-secret","https://a.local.com/callback")); + clients.add(new Client(2L, "b-client","b-secret","https://b.local.com/callback")); + + // 租户-客户端关系 + tenantClients.add(new TenantClient(1L, 1L,1L)); + tenantClients.add(new TenantClient(2L, 1L,2L)); + tenantClients.add(new TenantClient(3L, 2L,1L)); + + // 为租户添加用户 + users.add(new User(1L,1L,"u1","u1",true,false)); + users.add(new User(2L,1L,"u2","u2",false,false)); + users.add(new User(3L,2L,"u3","u3",false,false)); + + // 添加系统的普通用户 + userClientAuthorities.add(new UserClientAuthorities(1L,2L,1L)); + + } + + + public static class UserInfo { + public String userName; + public String password; + public List validClient; + } + + /** + * 判断租户是否有权限 + * @param clientId + * @param tenantName + * @return + */ + public Boolean isValidTenant(String clientId,String tenantName) { + // 先找到tenantId和clientId + Tenant tenant = tenants.stream() + .filter(t -> t.getName().equals(tenantName)) + .findFirst() + .orElse(null); + Client client = clients.stream() + .filter(c -> c.getClientId().equals(clientId)) + .findFirst() + .orElse(null); + if (tenant == null || client == null) return false; + // 判断租户-客户端关系 + return tenantClients.stream() + .anyMatch(tc -> tc.getTenantId().equals(tenant.getId()) && tc.getClientId().equals(client.getId())); + } + + /** + * 判断是否是有权限的客户端 + * @param clientId + * @return + */ + public Boolean isValidClientId(String clientId) { + return clients.stream() + .anyMatch(c -> c.getClientId().equals(clientId)); + } + + /** + * 判断用户是否有权限 + * @param clientId + * @param tenantName + * @param userName + * @return + */ + public Boolean isValidUserId(String clientId,String tenantName,String userName) { + // 先找到tenantId和clientId + Tenant tenant = tenants.stream() + .filter(t -> t.getName().equals(tenantName)) + .findFirst() + .orElse(null); + Client client = clients.stream() + .filter(c -> c.getClientId().equals(clientId)) + .findFirst() + .orElse(null); + if (tenant == null || client == null) return false; + // 找到用户 + User user = users.stream() + .filter(u -> u.getUserName().equals(userName) && u.getTenentId().equals(tenant.getId())) + .findFirst() + .orElse(null); + if (user == null) return false; + + //如果是租户用户,就有权限,租户用户的权限和租户的权限一致 + if(user.getTenantUser()){ + return true; + } + + //判断用户是否有该client的权限 + return userClientAuthorities.stream() + .anyMatch(uca -> uca.getUserId().equals(user.getId()) && uca.getClientId().equals(client.getId())); + } + + + public UserInfo getUser(String clientId, String tenantName, String userName) { + Tenant tenant = tenants.stream() + .filter(t -> t.getName().equals(tenantName)) + .findFirst() + .orElse(null); + Client client = clients.stream() + .filter(c -> c.getClientId().equals(clientId)) + .findFirst() + .orElse(null); + if (tenant == null || client == null) return null; + User user = users.stream() + .filter(u -> u.getUserName().equals(userName) && u.getTenentId().equals(tenant.getId())) + .findFirst() + .orElse(null); + + if (user == null) return null; + + UserInfo userInfo = new UserInfo(); + userInfo.userName = user.getUserName(); + userInfo.password = user.getPassword(); + + //如果是租户用户 + if(user.getTenantUser()){ + //todo 获取租户有权限的client的所有 clientUrl 字段赋予 userInfo.validClient + List clientIds = tenantClients.stream() + .filter(tc -> tc.getTenantId().equals(tenant.getId())) + .map(tc -> tc.getClientId()) + .toList(); + + List clientUrls = clients.stream() + .filter(c -> clientIds.contains(c.getId())) + .map(c -> c.getClientUrl()) // 如果你的字段名是clientUrl请改成c.clientUrl + .toList(); + + userInfo.validClient = clientUrls; + + }else { + //todo 获取有权限的client的所有 clientUrl 字段赋予 userInfo.validClient + List userClientIds = userClientAuthorities.stream() + .filter(uca -> uca.getUserId().equals(user.getId())) + .map(uca -> uca.getClientId()) + .toList(); + + List clientUrls = clients.stream() + .filter(c -> userClientIds.contains(c.getId())) + .map(c -> c.getClientUrl()) // 如果你的字段名是clientUrl请改成c.clientUrl + .toList(); + + userInfo.validClient = clientUrls; + } + + return userInfo; + + } + +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Client.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Client.java new file mode 100644 index 0000000..1b669f9 --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Client.java @@ -0,0 +1,49 @@ +package com.tuoheng.oauth.oidc.db.entity; + + +public class Client { + + public Client(Long id, String clientId, String clientSecret, String clientUrl) { + this.id = id; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.clientUrl = clientUrl; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getClientUrl() { + return clientUrl; + } + + public void setClientUrl(String clientUrl) { + this.clientUrl = clientUrl; + } + + private Long id; + private String clientId; + private String clientSecret; + private String clientUrl; +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Tenant.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Tenant.java new file mode 100644 index 0000000..1ddae68 --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/Tenant.java @@ -0,0 +1,31 @@ +package com.tuoheng.oauth.oidc.db.entity; + +public class Tenant { + + public Tenant(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * 数据库主键 + */ + private Long id; + private String name; +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/TenantClient.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/TenantClient.java new file mode 100644 index 0000000..f8dbe6a --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/TenantClient.java @@ -0,0 +1,38 @@ +package com.tuoheng.oauth.oidc.db.entity; + +public class TenantClient { + public Long getId() { + return id; + } + + public TenantClient(Long id, Long tenantId, Long clientId) { + this.id = id; + this.tenantId = tenantId; + this.clientId = clientId; + + } + + public void setId(Long id) { + this.id = id; + } + + public Long getClientId() { + return clientId; + } + + public void setClientId(Long clientId) { + this.clientId = clientId; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + private Long id; + private Long clientId; + private Long tenantId; +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/User.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/User.java new file mode 100644 index 0000000..3146068 --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/User.java @@ -0,0 +1,72 @@ +package com.tuoheng.oauth.oidc.db.entity; + + +import java.io.Serializable; + +public class User implements Serializable { + + public User(Long id, Long tenantId, String userName, String password, Boolean isTenantUser ,Boolean isLongToken ) { + this.id = id; + this.tenentId = tenantId; + this.userName = userName; + this.password = password; + this.isTenantUser = isTenantUser; + this.isLongToken = isLongToken; + } + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTenentId() { + return tenentId; + } + + public void setTenentId(Long tenentId) { + this.tenentId = tenentId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean getTenantUser() { + return isTenantUser; + } + + public void setTenantUser(Boolean tenantUser) { + isTenantUser = tenantUser; + } + + public Boolean getLongToken() { + return isLongToken; + } + + public void setLongToken(Boolean longToken) { + isLongToken = longToken; + } + + private Long id; + private Long tenentId; + private String userName; + private String password; + private Boolean isTenantUser; + private Boolean isLongToken; +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/UserClientAuthorities.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/UserClientAuthorities.java new file mode 100644 index 0000000..4bb9986 --- /dev/null +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/db/entity/UserClientAuthorities.java @@ -0,0 +1,37 @@ +package com.tuoheng.oauth.oidc.db.entity; + +public class UserClientAuthorities { + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public UserClientAuthorities(Long id, Long userId, Long clientId) { + this.id = id; + this.userId = userId; + this.clientId = clientId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getClientId() { + return clientId; + } + + public void setClientId(Long clientId) { + this.clientId = clientId; + } + + private Long id; + private Long userId; + private Long clientId; +} diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/provider/TenantAwareAuthenticationProvider.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/provider/TenantAwareAuthenticationProvider.java index 4ebb173..13f28e4 100644 --- a/oidc/src/main/java/com/tuoheng/oauth/oidc/provider/TenantAwareAuthenticationProvider.java +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/provider/TenantAwareAuthenticationProvider.java @@ -45,7 +45,9 @@ public class TenantAwareAuthenticationProvider implements AuthenticationProvider System.out.println("客户端ID: " + clientId); System.out.println("租户代码: " + tenantCode); - //在这边判断用户是否有权限 + /** + * 这边判断用户是否有权限 + */ UserDetails userDetails = userDetailsService.loadUserByUsername(username,clientId,tenantCode); @@ -57,6 +59,8 @@ public class TenantAwareAuthenticationProvider implements AuthenticationProvider Map details = new HashMap<>(); details.put("client_id", clientId); details.put("tenant_code", tenantCode); + details.put("clientIds",userDetails.getAuthorities().toString()); + token.setDetails(details); return token; } diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/service/CustomUserDetailsService.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/service/CustomUserDetailsService.java index 74d8b30..7f56cfc 100644 --- a/oidc/src/main/java/com/tuoheng/oauth/oidc/service/CustomUserDetailsService.java +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/service/CustomUserDetailsService.java @@ -1,14 +1,14 @@ package com.tuoheng.oauth.oidc.service; +import com.tuoheng.oauth.oidc.db.DbService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.util.HashSet; +import java.util.Objects; @Service public class CustomUserDetailsService implements UserDetailsService { @@ -16,6 +16,9 @@ public class CustomUserDetailsService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; + @Autowired + private DbService dbService; + /** * 这个地方需要扩展,判断用户是否有权限 @@ -26,15 +29,36 @@ public class CustomUserDetailsService implements UserDetailsService { * @throws UsernameNotFoundException */ public UserDetails loadUserByUsername(String username,String clientId,String tenantCode) throws UsernameNotFoundException { - return org.springframework.security.core.userdetails.User.builder() - .username("1") - .password(passwordEncoder.encode("1")) - .authorities(new HashSet<>()) + + if(!dbService.isValidClientId(clientId)) { + return null; + } + + if(!dbService.isValidTenant(clientId,tenantCode)){ + return null; + } + + if(!dbService.isValidUserId(clientId,tenantCode,username)) { + return null; + } + + DbService.UserInfo userInfo = dbService.getUser(clientId,tenantCode,username); + + if(Objects.nonNull(userInfo)) { + String[] authorities = userInfo.validClient.toArray(new String[0]); + return org.springframework.security.core.userdetails.User.builder() + .username(userInfo.userName) + .password(passwordEncoder.encode(userInfo.password)) + .authorities(authorities) .accountExpired(false) .accountLocked(false) .credentialsExpired(false) .disabled(false) .build(); + + }else { + return null; + } } diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/token/CustomTokenCustomizer.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/token/CustomTokenCustomizer.java index 4b78594..f4329dc 100644 --- a/oidc/src/main/java/com/tuoheng/oauth/oidc/token/CustomTokenCustomizer.java +++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/token/CustomTokenCustomizer.java @@ -23,12 +23,16 @@ public class CustomTokenCustomizer implements OAuth2TokenCustomizer