自定义的授权:
@Component("rbacService")
public class RBACService {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
if (principal instanceof LoginUser) {//instanceof编译器会检查 obj 是否能转换成右边的class类型
LoginUser LoginUser = (LoginUser) principal;
//req.getRequestURI() 当前请求的路径
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getRequestURI());
//userDetails.getAuthorities() // 用户所拥有的所有资源
Collection<? extends GrantedAuthority> authorities = LoginUser.getAuthorities();
System.out.println(simpleGrantedAuthority);
// 输出:/api/test1
System.out.println(authorities);
// 输出:[{"authority":"/api/test1"}, {"authority":"/api/test2"}, {"authority":"/api/test3"}, {"authority":"/api/test4"}]
boolean contains = authorities.contains(simpleGrantedAuthority);
System.out.println(contains);
// 输出:false(问题1:为什么总是为false?权限中是存在有“/api/test1”)
return contains;
}
return false;
}
}
问题2:一次请求,而这个RBACService被调用两次,这是为何?
配置类如下:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
return http
// 跨域配置
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 配置路径是否需要认证
// .authorizeHttpRequests()
.authorizeRequests()
// 允许匿名访问
.antMatchers("/api/auth/**").permitAll()
// 注意:.anyRequest()只能出现一次,否则报错:Caused by: java.lang.IllegalStateException: Can't configure anyRequest after itself
.anyRequest().access("@rbacService.hasPermission(request,authentication)")
.and()
//关闭csrf (注意:允许匿名访问,但不关闭csrf访问也是报403)
.cors().configurationSource(this.corsConfigurationSource())
.and()
// 禁用缓存,使用无状态session,即不使用session缓存数据
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
/**
*跨域资源配置
*/
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration cors = new CorsConfiguration();
cors.addAllowedOriginPattern("*");
// TODO 正式项目为保安全须设置cors
// cors.setAllowedOriginPatterns(List.of("http://192.168.1.6:8887"));
cors.setAllowCredentials(true);
cors.addAllowedHeader("*");
cors.addAllowedMethod("*");
cors.addExposedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cors);
return source;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* TODO 四 4.4 基于用户名和密码或使用用户名和密码进行身份验证
* @param config
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
问题一:
在您的配置代码中,看起来主要问题在于您的自定义授权逻辑@rbacService.hasPermission(request,authentication)
被设置为anyRequest().access()
,但这种方式可能会导致许多问题,包括Spring Security无法正确解析您的自定义逻辑并为请求提供正确的授权。
为了解决这个问题,建议使用更直接的方式配置自定义授权。您可以继承WebSecurityConfigurerAdapter
并在configure(HttpSecurity http)
方法中进行配置,以使用AccessDecisionManager
来实现您的自定义授权逻辑。
首先,创建一个自定义的AccessDecisionManager
实现,例如:
@Component
public class RBACAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<? extends GrantedAuthority> authorities) {
// 在这里实现您的自定义授权逻辑,例如检查用户的权限是否包含请求的URL等
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
然后,在您的SecurityConfiguration
中使用这个RBACAccessDecisionManager
,并配置http.authorizeRequests().accessDecisionManager()
,例如:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RBACAccessDecisionManager rbacAccessDecisionManager;
// ...
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.accessDecisionManager(rbacAccessDecisionManager)
.and()
// 其他配置...
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().configurationSource(this.corsConfigurationSource())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
// ...
}
在RBACAccessDecisionManager
的decide()
方法实现中,您可以使用authentication
对象获取当前用户的信息,然后根据您的逻辑与用户权限进行比较,从而判断是否授权。
问题2:
在您的配置中,RBACService
被调用两次的原因可能是由于您的配置中使用了两个不同的Spring Security过滤器。
首先,在您的SecurityConfiguration
中,您使用了jwtAuthenticationFilter
作为UsernamePasswordAuthenticationFilter
之前的过滤器。这个过滤器负责验证JWT并将用户信息添加到Spring Security的上下文中。
其次,在您的RBACService
中,您使用了@PreAuthorize
注解来进行权限验证。这个注解会在方法执行之前进行权限验证。
因此,当您发送一次请求时,首先会经过jwtAuthenticationFilter
进行JWT验证和用户信息添加,然后请求将继续到达您的控制器方法,同时RBACService
也会被调用来进行权限验证。
如果您希望减少RBACService
被调用的次数,可以考虑将权限验证逻辑移动到jwtAuthenticationFilter
中,或者在jwtAuthenticationFilter
中缓存权限验证的结果,以避免多次调用RBACService
。
另外,还可以检查您的代码中是否存在其他地方调用了RBACService
,或者在其他地方使用了@PreAuthorize
注解,这也可能导致RBACService
被调用多次。
在你提供的代码中,存在两个问题:
问题1:校验总是返回false的原因
在你的代码中,将请求的URI作为权限进行校验,但是在用户的权限列表中,权限是以SimpleGrantedAuthority对象的形式存在的,而不是以字符串的形式。因此,直接使用authorities.contains(simpleGrantedAuthority)会返回false,因为集合中存储的是对象而不是字符串。
解决方法是,将请求的URI作为字符串进行比较,而不是SimpleGrantedAuthority对象。可以使用SimpleGrantedAuthority的getAuthority()方法获取权限的字符串表示形式,并与请求的URI进行比较,如下所示:
java
Copy code
String requiredAuthority = request.getRequestURI();
boolean contains = authorities.stream()
.anyMatch(authority -> authority.getAuthority().equals(requiredAuthority));
问题2:RBACService被调用两次的原因
在Spring Security中,为了进行请求匹配和授权决策,可能会对URL进行多次匹配。这可能导致自定义的RBACService被多次调用。
要解决这个问题,可以使用WebExpressionVoter的setAllowEmptyAuthorization方法将空的认证对象(没有认证信息)视为已授权。这样可以确保RBACService只在有认证信息时被调用一次。
在你的SecurityConfiguration类中,可以添加以下代码来解决这个问题:
java
Copy code
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = Arrays.asList(
new WebExpressionVoter().setExpressionHandler(new DefaultWebSecurityExpressionHandler()),
new RoleVoter(),
new AuthenticatedVoter()
);
AffirmativeBased accessDecisionManager = new AffirmativeBased(decisionVoters);
accessDecisionManager.setAllowIfAllAbstainDecisions(false);
return accessDecisionManager;
}
这样配置后,RBACService应该只被调用一次。
希望以上解答能帮到你,如果还有其他问题,请随时提问。