目前我开始实施 BFF(frotnend 的后端 - spring oauth 2 客户端),目的是为我的前端(react)提供服务,以便通过授权服务器进行身份验证。
我想弄清楚如何准确地使用 spring oauth 2 客户端来实现前端 - 授权工作流。
到目前为止,我在 spring boot 项目上有一个简单的 oauth2-client:
@Configuration
public class Security {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.cors(cors -> cors.configurationSource(request -> {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://127.0.0.1:3000");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
return corsConfiguration;
}))
.csrf()
.disable()
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.oauth2Login( oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/securio"))
.oauth2Client(Customizer.withDefaults())
.build();
}
}
我想有一个 get /userinfo 端点,它会在每次需要加载页面时检索用户(前端)的角色,以检查它是否具有必要的权限。
@Controller
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthenticationController {
private final RestTemplate restTemplate;
private final OAuth2AuthorizedClientService authorizedClientService;
@GetMapping("/userinfo")
public ResponseEntity<UserInfo> getUserInfo() throws ParseException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
var client = authorizedClientService.loadAuthorizedClient(
((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(),
authentication.getName());
var accessToken = client.getAccessToken().getTokenValue();
JWT jwt = JWTParser.parse(accessToken);
List<String> authorities = jwt.getJWTClaimsSet().getStringListClaim("authorities");
String userRole = null;
for (String authority : authorities) {
if (authority.startsWith("ROLE_")) {
userRole = authority;
break;
}
}
if (userRole == null) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
String username = jwt.getJWTClaimsSet().getSubject();
return new ResponseEntity<>(UserInfo.builder()
.username(username)
.role(userRole)
.build(), HttpStatus.OK);
}
@PostMapping("/logout")
@ResponseStatus(HttpStatus.OK)
public void logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session != null) {
ResponseEntity<Void> responseEntity = restTemplate.exchange(
"http://127.0.0.1:8082/auth/logout", HttpMethod.POST, null, Void.class);
if (responseEntity.getStatusCode() != HttpStatus.NO_CONTENT) {
throw new RuntimeException("Logout failed");
}
session.invalidate();
Cookie cookie = new Cookie("JSESSIONID", "");
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
} else {
throw new RuntimeException("User already logged out");
}
}
}
这是 oauth2-client 的 application.yml:
server:
port: 8081
logging:
level:
org.springframework:
security: trace
spring:
security:
oauth2:
client:
registration:
securio:
client-id: securio
client-secret: securio-secret
authorization-grant-type: authorization_code
redirect-uri: http://127.0.0.1:8081/login/oauth2/code/securio
scope: openid
provider: securio
provider:
securio:
issuer-uri: http://localhost:8082
这就是我获取用户信息的方式
useEffect(() => {
axios
.get('http://127.0.0.1:8081/auth/userinfo', {
})
.then((response) => {
switch (response.data.role) {
case 'ROLE_STANDARD_USER':
setRole('ROLE_STANDARD_USER');
setMenuItems(standardMenuItems);
break;
case 'ROLE_ADMIN':
setRole('ROLE_ADMIN');
setMenuItems(adminMenuItems);
break;
default:
setRole(null);
setMenuItems([]);
break;
}
})
.catch((error) => {
console.log(error); // handle error
});
所以我希望工作流程是这样的:
但是这种方法有两个大问题:
我们有 3 个服务器(域):服务器 A(前端)、服务器 B(BFF)、服务器 C(身份验证服务器)。所以 Server B 正在将 Server A 重定向到 Server C 。在服务器 C 上,由于浏览器设置,请求到达时 origin 设置为 null,这与隐私问题有关。因此,cors 将始终失败,因为它无法使用 null 验证允许的来源。我没有找到任何解决方案
CORS 问题的解决方法是将 auth 服务器上的允许来源设置为所有 ( * ),因此在这种情况下 null 来源不再重要,但现在还有另一个问题。 BFF 应该将前端重定向到 auth 服务器,这意味着前端应该出现一个登录页面以输入凭据,但实际情况是,在 axios 请求的响应中,此重定向以 html 形式出现,并且我不知道如何进一步处理它以便让用户输入凭据。
我正在尝试找出前端和 BFF 之间的工作流程,以便以某种方式检索用户角色或正确的身份验证方式。