1、場景
最近照著《Spring Security實戰》學習,學到第18章,使用Keycloak作為授權服務器,使用
org.springframework.boot:spring-boot-starter-oauth2-resource-server
?實現資源服務器,調用資源服務器的接口返回403,具體錯誤信息如下:
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token.", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
?那就檢查scope吧
2、檢查scope
2.1 查看access_token(JWK)
到這個網址查看明文的令牌:JSON Web Tokens - jwt.io
看看 scope?都有哪些值。
2.2?資源服務配置
@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> authorize
// .requestMatchers(HttpMethod.POST, "/workout/").hasAuthority("SCOPE_fitnessapp").requestMatchers(HttpMethod.POST, "/workout/").access(hasScope("fitnessapp")).requestMatchers(HttpMethod.DELETE, "/**").hasAuthority("fitnessadmin").anyRequest().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));return http.build();}
上一步看到 scope 包含 fitnessapp,那就設置?
access(hasScope("fitnessapp"))
注:實際上資源服務器不設置 scope 也是可以的,因為根本原因不在 scope!!!
3、根本原因以及解決辦法
3.1 開啟 Spring Security 的日志
在?application.yml 添加配置:
logging:level:org.springframework.security: DEBUG?
再次調用接口,發現這樣一段信息:
ExpressionAuthorizationDecision [granted=false, expressionAttribute=#workout.user == authentication.name]?
出問題的代碼就是下面這個方法:
@PreAuthorize("#workout.user == authentication.name")
public void saveWorkout(Workout workout) {workoutRepository.save(workout);
}
于是打印看認證后獲取的用戶名
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("### authentication.name=" + name);
?果然,name不是預期的用戶名,而是一段UUID字符串,問DeepSeek這是怎么回事,接著再細看JWK的明文,name 就是token中的 sub 字段的值。
3.2 解決辦法
登錄Keycloak控制臺添加映射器,Client scopes -> fitnessapp -> Mappers
?
?
?
然后重新獲取token,看看 sub 字段的值是不是對應的用戶名,是的話就沒問題了。
最后使用新的token重新調用資源服務器接口,返回200,調用成功!
?