This commit is contained in:
孙小云 2025-07-21 18:14:48 +08:00
parent f91d65803e
commit fe56edd54f
10 changed files with 510 additions and 7 deletions

View File

@ -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<Void> 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; // 优先级高
}
}

View File

@ -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<Client> clients = new ArrayList<>();
List<Tenant> tenants = new ArrayList<>();
List<TenantClient> tenantClients = new ArrayList<>();
List<com.tuoheng.oauth.oidc.db.entity.User> users = new ArrayList<>();
List<UserClientAuthorities> 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<String> 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<Long> clientIds = tenantClients.stream()
.filter(tc -> tc.getTenantId().equals(tenant.getId()))
.map(tc -> tc.getClientId())
.toList();
List<String> 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<Long> userClientIds = userClientAuthorities.stream()
.filter(uca -> uca.getUserId().equals(user.getId()))
.map(uca -> uca.getClientId())
.toList();
List<String> clientUrls = clients.stream()
.filter(c -> userClientIds.contains(c.getId()))
.map(c -> c.getClientUrl()) // 如果你的字段名是clientUrl请改成c.clientUrl
.toList();
userInfo.validClient = clientUrls;
}
return userInfo;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -45,7 +45,9 @@ public class TenantAwareAuthenticationProvider implements AuthenticationProvider
System.out.println("客户端ID: " + clientId); System.out.println("客户端ID: " + clientId);
System.out.println("租户代码: " + tenantCode); System.out.println("租户代码: " + tenantCode);
//在这边判断用户是否有权限 /**
* 这边判断用户是否有权限
*/
UserDetails userDetails = userDetailsService.loadUserByUsername(username,clientId,tenantCode); UserDetails userDetails = userDetailsService.loadUserByUsername(username,clientId,tenantCode);
@ -57,6 +59,8 @@ public class TenantAwareAuthenticationProvider implements AuthenticationProvider
Map<String, Object> details = new HashMap<>(); Map<String, Object> details = new HashMap<>();
details.put("client_id", clientId); details.put("client_id", clientId);
details.put("tenant_code", tenantCode); details.put("tenant_code", tenantCode);
details.put("clientIds",userDetails.getAuthorities().toString());
token.setDetails(details); token.setDetails(details);
return token; return token;
} }

View File

@ -1,14 +1,14 @@
package com.tuoheng.oauth.oidc.service; package com.tuoheng.oauth.oidc.service;
import com.tuoheng.oauth.oidc.db.DbService;
import org.springframework.beans.factory.annotation.Autowired; 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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashSet; import java.util.Objects;
@Service @Service
public class CustomUserDetailsService implements UserDetailsService { public class CustomUserDetailsService implements UserDetailsService {
@ -16,6 +16,9 @@ public class CustomUserDetailsService implements UserDetailsService {
@Autowired @Autowired
private PasswordEncoder passwordEncoder; private PasswordEncoder passwordEncoder;
@Autowired
private DbService dbService;
/** /**
* 这个地方需要扩展,判断用户是否有权限 * 这个地方需要扩展,判断用户是否有权限
@ -26,15 +29,36 @@ public class CustomUserDetailsService implements UserDetailsService {
* @throws UsernameNotFoundException * @throws UsernameNotFoundException
*/ */
public UserDetails loadUserByUsername(String username,String clientId,String tenantCode) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username,String clientId,String tenantCode) throws UsernameNotFoundException {
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() return org.springframework.security.core.userdetails.User.builder()
.username("1") .username(userInfo.userName)
.password(passwordEncoder.encode("1")) .password(passwordEncoder.encode(userInfo.password))
.authorities(new HashSet<>()) .authorities(authorities)
.accountExpired(false) .accountExpired(false)
.accountLocked(false) .accountLocked(false)
.credentialsExpired(false) .credentialsExpired(false)
.disabled(false) .disabled(false)
.build(); .build();
}else {
return null;
}
} }

View File

@ -23,12 +23,16 @@ public class CustomTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingC
if (detailsObj instanceof Map details) { if (detailsObj instanceof Map details) {
Object clientId = details.get("client_id"); Object clientId = details.get("client_id");
Object tenantCode = details.get("tenant_code"); Object tenantCode = details.get("tenant_code");
Object clientIds = details.get("clientIds");
if (clientId != null) { if (clientId != null) {
context.getClaims().claim("client_id", clientId.toString()); context.getClaims().claim("client_id", clientId.toString());
} }
if (tenantCode != null) { if (tenantCode != null) {
context.getClaims().claim("tenant_code", tenantCode.toString()); context.getClaims().claim("tenant_code", tenantCode.toString());
} }
if(clientIds != null) {
context.getClaims().claim("clientIds", clientIds);
}
} }
} }
} }