问题描述:
使用springsecurity中,对用户进行权限验证,自定义权限控制管理器始终无法执行decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)方法和getAttributes(Object object)方法。
一开始以为是两个实现类没有加入到spring容器中,后来为他们添加了构造函数,项目启动的时候执行了构造,说明他们已经早容器中了。
问题原因
这个问题困扰了我一天多的时间,真的是太后悔使用shiro、springsecurity这种授权框架了,自己写一个都很简单,言归正传。原因是WebSecurityConfig中配置出错。我贴一下正确的配置方式代码(重要的部分不多不用自己看,看分析就行)
package com.luntek.security.springsecurity.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luntek.security.springsecurity.entity.User;
import com.luntek.security.springsecurity.service.impl.PasswordEncoderImpl;
import com.luntek.security.springsecurity.util.JsonUtil;
import com.luntek.security.springsecurity.util.JwtTokenUtil;
import com.luntek.security.springsecurity.util.RedisOperator;
import com.luntek.security.springsecurity.util.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义的用户名密码认证配置类
*
* @Date 2020/3/5 0005 下午 2:32
* @Created by Czw
*/
@Slf4j
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Qualifier("userDetailsService")
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RedisOperator redisOperator;
@Autowired
@Qualifier("customAccessDecisionManager")
private AccessDecisionManager accessDecisionManager;
@Autowired
@Qualifier("customFilterInvocationSecurityMetadataSource")
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
//密码编码器,对比账号密码前的密码加密规则,使用自定义的密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoderImpl();
}
//权限提供者
@Bean
public AuthenticationProvider authenticationProvider() {
log.info("***authenticationProvider***");
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
//对默认的UserDetailsService进行覆盖
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
/**
* 提供User的 bean
*
* @auther Czw
* @date 2020/3/16 0016 上午 11:31
*/
@Bean
public User getUser() {
return new User();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//基于数据库查数据
log.info("***WebSecurityConfig.configure()基于数据库查数据***");
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
//从数据库中动态获取后添加
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("***WebSecurityConfig.configure()拦截机制***");
AuthenticationProvider authenticationProvider = authenticationProvider();
http.authenticationProvider(authenticationProvider)
.httpBasic()
//未登录时,进行json格式的提示,不用单独写一个又一个的类
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(302, "未登录或已过期,请重新登录")));
out.flush();
out.close();
})
.and()
.authorizeRequests()
.anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
.and()
.formLogin() //使用自带的登录
.permitAll()
//登录失败,返回json
.failureHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
ResponseResult responseResult = new ResponseResult<String>();
responseResult.setState(4004);
responseResult.setSuccess(false);
if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
responseResult.setMessage("用户名或密码错误");
} else if (ex instanceof DisabledException) {
responseResult.setMessage("账号被禁用");
} else {
responseResult.setMessage("登录失败");
}
out.write(objectMapper.writeValueAsString(responseResult));
out.flush();
out.close();
})
//登录成功,返回json
.successHandler((request, response, authentication) -> {
sendSuccessResponse(response, authentication);
})
.and()
.exceptionHandling()
//没有权限,返回json
.accessDeniedHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(403, "用户权限不足")));
out.flush();
out.close();
})
.and()
.logout()
//退出成功,返回json
.logoutSuccessHandler((request, response, authentication) -> {
sendLogoutResponse(response, authentication);
})
.permitAll();
//开启跨域访问
http.cors().disable();
//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();
}
/**
* 忽略拦截的路径
*
* @param web
*/
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/ignore");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
private void sendSuccessResponse(HttpServletResponse response, Authentication authentication) throws IOException {
response.setContentType("application/json;charset=utf-8");
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
PrintWriter out = response.getWriter();
String mobile = userDetails.getUsername();
String token = JwtTokenUtil.createToken(mobile);
//将 用户信息 & 权限信息 放入到Redis中
redisOperator.set(RedisOperator.USER_INFO_SUFFIX + mobile, JsonUtil.objectToJson(userDetails));
List<String> permissionList = new ArrayList<>();
for (GrantedAuthority authority : userDetails.getAuthorities()) {
permissionList.add(authority.getAuthority());
}
redisOperator.set(RedisOperator.USER_PERMISSION + mobile, JsonUtil.objectToJson(permissionList));
out.write(objectMapper.writeValueAsString(new ResponseResult<>(token)));
out.flush();
out.close();
}
private void sendLogoutResponse(HttpServletResponse response, Authentication authentication) throws IOException {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String mobile = userDetails.getUsername();
redisOperator.delByKey(RedisOperator.USER_INFO_SUFFIX + mobile);
redisOperator.delByKey(RedisOperator.USER_PERMISSION + mobile);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(new ResponseResult<>(200, "系统登出成功")));
out.flush();
out.close();
}
/**
* 自定义 FilterSecurityInterceptor ObjectPostProcessor 以替换默认配置达到动态权限的目的
*
* @return ObjectPostProcessor
*/
private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
return new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
return object;
}
};
}
}
由于登录成功、失败等全部写在里面所以代码有点多。分析一下,其实重要的代码并不多,只有不到十行。无法进入的原因是没有配置
.authorizeRequests().anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
我是因为没有配置withObjectPostProcessor参数,所以就一直无法执行代码
问题解决
添加withObjectPostProcessor参数,并且添加一个filterSecurityInterceptorObjectPostProcessor()方法,贴一下这个方法的内容,其实就是将两个实现类设置到ObjectPostProcessor中
@Autowired
private AccessDecisionManager accessDecisionManager;
@Autowired
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
/**
* 自定义 FilterSecurityInterceptor ObjectPostProcessor
* 以替换默认配置达到动态权限的目的
*
* @return ObjectPostProcessor
*/
private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
return new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
return object;
}
};
}
尾声:此时问题已经得到解决,两个类中的方法能够执行到。其中,上面的ObjectPostProcessor也可以使用实现类的方法实现。如下,实现ObjectPostProcessor<FilterSecurityInterceptor>接口重写postProcess方法,个人认为没什么问题,但在启动的时候总是报错NoUniqueBeanDefinitionException(不是唯一的bean),在这也没时间去找原因,用的是上面内部类的方式
package com.luntek.security.springsecurity.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.stereotype.Component;
/**
* @Date 2020/3/16 0016 下午 3:45
* @Created by Czw
*/
@Qualifier("mySecurityObjectPostProcessor")
@Component
public class SecurityObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
fsi.setAccessDecisionManager(new CustomAccessDecisionManager()); //权限决策处理类
fsi.setSecurityMetadataSource(new CustomFilterSecurityMetadataSource()); //路径(资源)拦截处理
return fsi;
}
}
lz在写一个springsecurity+jwt+springboot的user、role、permission、两张关系表的全部基于数据库的Demo,如果需要的话可以私聊我关注我csdn,后面会发出来。花了一天多的时间找这个问题,点个赞吧,嘿嘿