我得到了一个基本的Vue.js应用程序,用于在我们的Spring Boot身份验证服务器上进行身份验证.下面是执行完整登录(重定向到/LOGIN、请求令牌端点等)的代码:
<template>
<div class="h-screen w-screen flex flex-col items-center justify-center">
<p class="text-center mb-5">You need to be logged in<br>to access the admin dashboard.</p>
<button @click.prevent="startAuthFlow" class="bg-blue-500 text-white h-8 w-36 rounded shadow">Login</button>
</div>
</template>
<script setup>
import { config
} from "./../common/config.js";
import { parseQueryString, generateR和omString } from "./../utils/authHelpers.js";
import router from './../router';
import { useAuthStore } from './../stores/auth.js';
const auth = useAuthStore();
if (auth.access_token) {
router.push({ name: 'home', replace: true });
}
const startAuthFlow = () => {
console.log("Starting auth flow...");
// Create 和 store a r和om "state" value
var state = generateR和omString();
localStorage.setItem("pkce_state", state);
console.log(state);
// Build the authorization URL
var url = config.authorization_endpoint
+ "?response_type=code"
+ "&client_id=" + encodeURIComponent(config.client_id)
+ "&state=" + encodeURIComponent(state)
+ "&redirect_uri=" + encodeURIComponent(config.redirect_uri);
// Redirect to the authorization server
window.location = url;
};
// H和le the redirect back from the authorization server 和
// get an access token from the token endpoint
var q = parseQueryString(window.location.search.substring(1));
// Check if the server returned an error string
if (q.error) {
alert("Error returned from authorization server: " + q.error);
}
// If the server returned an authorization code, attempt to exchange it for an access token
if (q.code) {
// Verify state matches what we set at the beginning
if (localStorage.getItem("pkce_state") != q.state) {
alert("Invalid state");
} else {
// Base64 encode client credentials
const base64Credentials = btoa(config.client_id + ':' + config.client_secret);
// Build the token URL
var url = config.token_endpoint
+ "?grant_type=authorization_code"
+ "&client_id=" + encodeURIComponent(config.client_id)
+ "&code=" + q.code
+ "&redirect_uri=" + encodeURIComponent(config.redirect_uri);
// Send POST request to token endpoint to retrieve access token
fetch(url, {
method: "POST",
headers: {
"Authorization": "Basic " + base64Credentials,
'Content-Type': 'application/x-www-form-urlencoded',
}
}).then(response => response.json())
.then(result => {
console.log(result);
// Extracting tokens from result
const { access_token, refresh_token } = result;
// Save login to pinia 和 tokens to cookies
auth.login({ access_token, refresh_token, user: null });
router.push({ name: 'home', replace: true });
})
.catch(error => console.log('error', error));
}
// Clean up local storage
localStorage.removeItem("pkce_state");
}
</script>
当使用带有停用的安全功能的浏览器时(not次判断COR),这完全可以正常工作.无论是登录还是其他请求.
现在,当使用普通浏览器时,重定向到/login
完全可以正常工作.但是,当向token
端点发送请求时,出现以下错误:
获取位置的访问权限 ‘http://localhost:8081/oauth2/token?grant_type=authorization_code&;client_id=core-server&;code=Ps19gIUUePThDLr15xX0U-UWEMd0HgHfyAOCcZjGmfXUqED80GOMLBykuldNrL7k23dxEydcP49hX_kGigKsZjcFCTS93xU7kwdwAIm6-eIcIk_ayN5i0mZPLeg_bDYx&;redirect_uri=http%3A%2F%2Flocalhost%3A5173%2F’ 发端的http://localhost:5173‘已被CORS策略拦截: 对印前判断请求的响应未通过访问控制判断:否 "Access-Control-Allow-Origin"标头位于请求的 资源.如果不透明的响应满足您的需求,请设置请求的 将模式设置为‘no-CORS’可在禁用CORS的情况下获取资源.
在停用CORS的浏览器中,我得到了以下标头:
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
以下是我的SecurityConfig
文件以及CorsConfig
:
@Configuration
public class
CorsConfig {
private static final Logger logger = LoggerFactory.getLogger(CorsConfig.class);
/**
* Cors configuration
*/
@Bean(name="corsConfigurationSource")
CorsConfigurationSource corsConfigurationSource() {
logger.info("Creating corsConfigurationSource bean");
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(
"http://localhost:5173",
"http://192.168.2.144:5173"
));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(List.of(
"Authorization",
"Content-Type",
"Accept",
"Origin",
"X-Requested-With"
));
configuration.setExposedHeaders(List.of(
"Cache-Control",
"Content-Language",
"Content-Type",
"Expires",
"Last-Modified",
"Pragma"
));
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
和
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// claim names used in the bearer token
private static final String ROLES_CLAIM = "user-authorities";
private static final String SCOPES_CLAIM = "scope";
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Bean
@Order(1)
public CorsFilter corsFilter(CorsConfigurationSource corsConfigurationSource) {
logger.info("Creating corsFilter bean");
return new CorsFilter(corsConfigurationSource);
}
/**
* Configures the authorization server endpoints.
*/
@Bean
@Order(2)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, RegisteredClientRepository clientRepository) throws Exception {
logger.info("Creating authorizationServerSecurityFilterChain bean");
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.registeredClientRepository(clientRepository) // autowired from ClientConfig.java
.oidc(Customizer.withDefaults());
http.exceptionH和ling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
http.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
/**
* Secures pages used to log in, log out, register etc.
* Sets custom login menu.
*/
@Bean
@Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/admin/**")));
logger.info("Creating defaultSecurityFilterChain bean");
http.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers(new AntPathRequestMatcher("/register")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/recover/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/css/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/js/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/favicon.ico")).permitAll()
.anyRequest().authenticated());
http.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
// set custom login form
http.formLogin(form -> {
form.loginPage("/login");
form.permitAll();
});
http.logout(conf -> {
// default logout url
conf.logoutSuccessH和ler(logoutSuccessH和ler());
});
// Temp disable CSRF
http.csrf(AbstractHttpConfigurer::disable);
http.cors(AbstractHttpConfigurer::disable);
return http.build();
}
/**
* Secures admin endpoints with a bearer token. Does not use session authentication.
*/
@Bean
@Order(4)
public SecurityFilterChain adminResourceFilterChain(HttpSecurity http) throws Exception {
logger.info("Creating adminResourceFilterChain bean");
// h和le out custom endpoints in this filter chain
http.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")
.anyRequest().authenticated());
http.sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults()));
// Temp disable CSRF
http.csrf(AbstractHttpConfigurer::disable);
http.cors(AbstractHttpConfigurer::disable);
return http.build();
}
// ...
我try 了很多不同的方法:
- 更改了筛选器链(例如,我在每个安全Bean的顶部添加了CORS配置,而不是
Order(1)
) - Went ahead 和 implemented the CORS Tutorial one by one.
- 已try 手动设置请求标头(which isn't a solution in the long-term).
- try 模式匹配以临时允许每个传入请求(不起作用,因此这显然是CORS配置本身的问题)