一.
项目自springsecurity5升级到6 配置类修改。其中突出的问题是认证管理器的变化。
如下图报错:
ervlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause
at jdk.proxy3/jdk.proxy3.$Proxy144.authenticate(Unknown Source)
初始化开始:
1.
2.初始化没找到认证管理实例走到懒加载
3.他还是加载他自己,循环引用了
2.解决方案
springsecurity6的认证管理Bean方法被设置为私有方法。因此无法以以下形式进行:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
而应当使用以下配置,ss 配置类
/**
* @Description: spring security 配置类
*/
@Configuration //配置类
// 开启注解 @PreAuthorize @PostAuthorize @Secured
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
// // 授权处理器
// @Resource
// private AccessDeniedHandlerImpl accessDeniedHandler;
// jwt过滤器
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// 手机号短信登录
@Lazy
@Resource
private SmsCodeAuthenticationProvider smsCodeAuthenticationProvider;
// 手机号账号登录
@Lazy
@Resource
private MobileAccountAuthenticationProvider mobileAccountAuthenticationProvider;
// 微信登录
@Lazy
@Resource
private WeChatAuthenticationProvider weChatAuthenticationProvider;
@Resource
private AuthenticationManagerProcessingFilter authenticationManagerProcessingFilter;
/**
* 获取AuthenticationManager(认证管理器),登录时认证使用
*/
@Bean
public AuthenticationManager authenticationManager(List<AuthenticationProvider> myAuthenticationProviders) {
myAuthenticationProviders.addAll(List.of(smsCodeAuthenticationProvider, mobileAccountAuthenticationProvider, weChatAuthenticationProvider));
return new ProviderManager(myAuthenticationProviders);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 跨域配置
*
* @return corsConfigurationSource
*/
// private CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration corsConfiguration = new CorsConfiguration();
// corsConfiguration.setAllowedHeaders(List.of("*"));
// //允许从任意站点跨域
// corsConfiguration.setAllowedOrigins(List.of("*"));
// corsConfiguration.setAllowedMethods(List.of("*"));
// // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
// corsConfiguration.setMaxAge(3600L);
// // 允许携带凭证
// corsConfiguration.setAllowCredentials(true);
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// // 对所有请求都生效
// source.registerCorsConfiguration("/**", corsConfiguration);
// return source;
// }
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/**");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement(AbstractHttpConfigurer::disable)
// .cors(cros -> cros.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/test/**",
"/member/artist/v1/getArtist/**",
"/member/artist/v1/search/**",
"/member/artist/v1/artist/**",
"/member/artist/v1/list/**",
"/member/province-university/**",
"/member/user/v1/list/**").permitAll()
.requestMatchers("/music-score/score/v1/list",
"/music-score/score/v1/search/**",
"/music-score/score/v1/popular/**",
"/music-score/score/v1/info/**",
"/music-score/score/v1/score-file/**",
"/music-score/score/v1/artist/score",
"/music-score/score/v1/score-collect/**",
"/music-score/instrument/v1/**",
"/music-score/score-collection/v1/**",
"/music-score/gaokao-music/v1/**",
"/music-score/province-university/v1/**",
"/music-score/artist-collection/v1/**",
"/music-score/comment/v1/list",
"/music-score/comment/v1/children/list/**").permitAll()
.anyRequest().authenticated()); // 除上述放行的url,其余全部鉴权认证
// http.exceptionHandling()
// .accessDeniedHandler(accessDeniedHandler);
http.addFilterBefore(authenticationManagerProcessingFilter,
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationTokenFilter,
AuthenticationManagerProcessingFilter.class);
return http.build();
}
}
认证执行流程实现类:
@Slf4j
@Component
public class AuthenticationManagerProcessingFilter extends AbstractAuthenticationProcessingFilter {
private final boolean postOnly = false;
private final ObjectMapper objectMapper = JacksonInstance.getInstance();
@Resource
private JwtUtil jwtUtil;
@Resource
private RedissonCache redissonCache;
@Resource
private AuthStrategyContent authStrategyContent;
@Resource
private SecurityThreadPoolConfig securityThreadPoolConfig;
@Lazy
@Autowired
public AuthenticationManagerProcessingFilter(AuthenticationManager authenticationManager,
AuthenticationSuccessFilter authenticationSuccessHandler,
AuthenticationFailureFilter authenticationFailureHandler) {
super("/member/auth/v1/login");
setAuthenticationManager(authenticationManager);
setAuthenticationSuccessHandler(authenticationSuccessHandler);
setAuthenticationFailureHandler(authenticationFailureHandler);
}
/**
* 使用相同的凭证执行身份验证
*
* @param request 从中提取参数并执行身份验证
* @param response 如果实现必须将重定向作为多阶段身份验证过程的一部分(例如OpenID),则可能需要此响应。
* @return 通过身份验证的用户。返回的对象必须是Authentication的实现
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 获取用户信息
InputUserAuth inputUserAuth = getUserByRequest(request);
if (inputUserAuth == null) {
throw new AuthenticationServiceException(EnumHttpCode.BAD_REQUEST.getMsg() + "用户信息不能为空!");
}
// 验证用户信息
Authentication authentication = verifyAuthInfo(inputUserAuth);
UserAuth userAuth = (UserAuth) authentication.getPrincipal();
if (userAuth == null) {
throw new AuthenticationServiceException(EnumHttpCode.LOGIN_ERROR.getMsg());
}
// 登录成功后将密码置空
userAuth.setPassword(null);
// 统计登录次数
loginCount(request, userAuth);
//获取userId
Long id = userAuth.getId();
OutputUserAuth outputUserAuth = BeanCopyUtils.copyBean(userAuth, OutputUserAuth.class);
try {
redissonCache.setCacheObject(ConstantsCache.LOGIN_TOKEN_KEY + id,
outputUserAuth,
jwtUtil.getExpire(),
TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("redisson初始化失败", e);
throw new AuthenticationServiceException(EnumHttpCode.SYSTEM_ERROR.getMsg());
}
//通过后用userid生成一个jwt存入ResponseResult
String jwt = jwtUtil.createJWT(String.valueOf(id));
userAuth.setTokenHead(JwtUtil.JWT_TOKEN_PREFIX);
userAuth.setToken(jwt);
return authentication;
}
/**
* 获取request中的json用户信息
*
* @param request 请求
* @return 用户信息
* @throws IOException IO异常
*/
private InputUserAuth getUserByRequest(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream is = request.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String s = "";
while ((s = br.readLine()) != null) {
sb.append(s);
}
String userInfo = sb.toString();
InputUserAuth inputUserAuth = null;
try {
inputUserAuth = objectMapper.readValue(userInfo, InputUserAuth.class);
} catch (JsonProcessingException e) {
log.info("json转换异常: {}", e.getMessage());
throw new AuthenticationServiceException(EnumHttpCode.BAD_REQUEST.getMsg() + "json转换异常");
}
return inputUserAuth;
}
/**
* 验证用户信息
*
* @param inputUserAuth 用户信息
* @return 认证信息
*/
private Authentication verifyAuthInfo(InputUserAuth inputUserAuth) {
String authType = inputUserAuth.getAuthType();
// 认证策略模式
AuthEnums enumerateInstances = AuthEnums.getEnum(authType);
if (enumerateInstances == null) {
throw new AuthenticationServiceException(EnumHttpCode.BAD_REQUEST.getMsg() + "authType认证方式错误!");
}
return authStrategyContent.authType(enumerateInstances, inputUserAuth, this.getAuthenticationManager());
}
/**
* 登录次数统计
*/
private void loginCount(HttpServletRequest request, UserAuth userAuth) {
Executor asyncExecutor = securityThreadPoolConfig.getAsyncExecutor();
CompletableFuture.runAsync(() -> {
try {
log.info("当前线程id: {},当前线程名称: {}",
Thread.currentThread().getId(),
Thread.currentThread().getName());
// 登录次数统计 联网查询ip地址
String ip = IpUtil.getIp(request);
String ipAddr = String.valueOf(IpUtil.getIpAddr(ip));
userAuth.setIpAddr(ipAddr);
userAuth.setStatus(ConstantsSystemLog.USER_LOGIN_STATUS_VALUE);
log.info("{}", objectMapper.writeValueAsString(userAuth));
} catch (JsonProcessingException e) {
log.error("登录次数统计异常: {}", e.getMessage());
}
}, asyncExecutor);
}
}