This commit is contained in:
孙小云 2025-07-21 15:15:38 +08:00
parent a856062a90
commit f91d65803e
5 changed files with 127 additions and 9 deletions

View File

@ -5,6 +5,7 @@ import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.tuoheng.oauth.oidc.filter.ForcePromptLoginFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -14,6 +15,8 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@ -47,12 +50,24 @@ import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@Configuration
public class SecurityConfig {
@Bean
public FilterRegistrationBean<ForcePromptLoginFilter> forcePromptLoginFilterRegistration(ForcePromptLoginFilter filter) {
FilterRegistrationBean<ForcePromptLoginFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(filter);
registration.addUrlPatterns("/oauth2/authorize");
registration.setOrder(-101); // 顺序要比Spring Security的Filter更靠前
return registration;
}
@Autowired
@Lazy
AuthenticationProvider tenantAwareAuthenticationProvider;
@ -62,10 +77,11 @@ public class SecurityConfig {
super(loginFormUrl);
}
@Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException exception) {
String loginUrl = super.determineUrlToUseForThisRequest(request, response, exception);
// 获取client_id参数
String clientId = request.getParameter("client_id");
if (clientId != null && !clientId.isEmpty()) {

View File

@ -0,0 +1,46 @@
package com.tuoheng.oauth.oidc.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component
public class ForcePromptLoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
System.out.println("ForcePromptLoginFilter invoked! URI=" + request.getRequestURI());
// // 只拦截/oauth2/authorize请求
// String uri = request.getRequestURI();
// if ("/oauth2/authorize".equals(uri)) {
// // 你可以自定义条件比如只对b-client强制加prompt=login
// if (request.getParameter("prompt") == null) {
// // 构造新的URL加上prompt=login
// StringBuilder newUrl = new StringBuilder(request.getRequestURL());
// newUrl.append("?");
// request.getParameterMap().forEach((key, values) -> {
// for (String value : values) {
// if (!"prompt".equals(key)) {
// newUrl.append(URLEncoder.encode(key, StandardCharsets.UTF_8)).append("=")
// .append(URLEncoder.encode(value, StandardCharsets.UTF_8)).append("&");
// }
// }
// });
// newUrl.append("prompt=login");
// response.sendRedirect(newUrl.toString());
// return; // 必须return防止后续Filter继续处理
// }
// }
filterChain.doFilter(request, response);
}
}

View File

@ -15,6 +15,9 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.HashMap;
import java.util.Map;
@Component
public class TenantAwareAuthenticationProvider implements AuthenticationProvider {
@ -42,13 +45,20 @@ public class TenantAwareAuthenticationProvider implements AuthenticationProvider
System.out.println("客户端ID: " + clientId);
System.out.println("租户代码: " + tenantCode);
// 执行标准的用户认证
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//在这边判断用户是否有权限
UserDetails userDetails = userDetailsService.loadUserByUsername(username,clientId,tenantCode);
if (userDetails != null && passwordEncoder.matches(password, userDetails.getPassword())) {
System.out.println("用户认证成功");
return new UsernamePasswordAuthenticationToken(
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userDetails, password, userDetails.getAuthorities());
Map<String, Object> details = new HashMap<>();
details.put("client_id", clientId);
details.put("tenant_code", tenantCode);
token.setDetails(details);
return token;
}
System.out.println("用户认证失败");

View File

@ -16,13 +16,19 @@ public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 创建UserDetails对象
/**
* 这个地方需要扩展,判断用户是否有权限
* @param username
* @param clientId
* @param tenantCode
* @return
* @throws UsernameNotFoundException
*/
public UserDetails loadUserByUsername(String username,String clientId,String tenantCode) throws UsernameNotFoundException {
return org.springframework.security.core.userdetails.User.builder()
.username("user")
.password(passwordEncoder.encode("password"))
.username("1")
.password(passwordEncoder.encode("1"))
.authorities(new HashSet<>())
.accountExpired(false)
.accountLocked(false)
@ -32,4 +38,9 @@ public class CustomUserDetailsService implements UserDetailsService {
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}

View File

@ -0,0 +1,35 @@
package com.tuoheng.oauth.oidc.token;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class CustomTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> {
@Override
public void customize(JwtEncodingContext context) {
// 只对access_token生效
if ("access_token".equals(context.getTokenType().getValue())) {
String username = context.getPrincipal().getName();
context.getClaims().claim("username", username);
// 取details
Object detailsObj = context.getPrincipal().getDetails();
if (detailsObj instanceof Map details) {
Object clientId = details.get("client_id");
Object tenantCode = details.get("tenant_code");
if (clientId != null) {
context.getClaims().claim("client_id", clientId.toString());
}
if (tenantCode != null) {
context.getClaims().claim("tenant_code", tenantCode.toString());
}
}
}
}
}