错误:User must be authenticated with Spring Security before authorization can be completed.
简要说下错误原因:
该错误是oauth2 授权码模式 访问接口 /oauth/authorize 时,由于SpringSecurity上下文中没有用户认证信息,所以在spring security 中拦截报错。源码中 在/oauth/authorize 接口debug可以看到问题。
处理方法
错误信息已经说明了是要先认证,所以我们在请求/oauth/authorize接口前认证用户信息即可
1.1 通过写一个用户身份过滤器进行处理,代码如下
@Component
@Slf4j
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Autowired
AuthenticationManager authenticationManager;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(HttpConstant.Header.AUTHENTICATION);
if (StrUtil.isBlank(token)) {
//解析请求参数 request.getQueryString()
Map<String, String> parameters = HttpParamUtil.getRequestParameters(request);
if (SecurityConstants.AUTH_CODE_URL.equals(request.getRequestURI())&¶meters.size()>0) {
//参数中提取 clientId ,根据client 数据库查询用户信息进行身份认证
String clientId = parameters.get("client_id");
//根据clientId查询用户信息 省略数据库用户信息查询
String username="admin";
String password="123456";
//账号密码认证
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(username, password);
// 通过authenticationManager
Authentication authentication = authenticationManager.authenticate(jwtAuthenticationToken);
// 认证成功后将认证信息存储到SpringSecurity上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
//在过滤器中添加return,是为了结束该方法的运行,也就是结束过滤
return;
}
}
}
方法逻辑说明:用户请求中没有认证令牌token,判断请求路径是否是/oauth/authorize,如果是同时判断是否携带有参数(携带参数是请求授权的请求,生成授权码的请求没有参数),如果有则解析参数获取cliend_id,根据cliend_id 查询数据库该用户信息,完成spring security 的鉴权并将认证信息息存储到SpringSecurity上下文中
1.2 spring security 核心配置类中 注册以上的过滤器,并指定该过滤器为spring security 一系列过滤器的首个过滤器
1.3 spring security 核心配置类中 对 以 /oauth 开头的请求放行
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private MyLogOutSuccessHandler logOutSuccessHandler;
@Autowired
private SimpleAccessDeniedHandler simpleAccessDeniedHandler;
@Autowired
private SimpleAuthenticationEntryPoint simpleAuthenticationEntryPoint;
@Autowired
private TokenAuthenticationFilter tokenAuthenticationFilter;
/**
* http请求拦截认证
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
http.csrf().disable();
http.authorizeRequests()
.and()
.formLogin() //新增login form支持用户登录及授权
//自定义登录页面
.loginPage("http://localhost:3000/login")
//认证成功跳转的页面
.successForwardUrl("http://localhost:3000/index")
.failureForwardUrl("http://localhost:3000/login")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("http://localhost:3000/login")
.logoutSuccessHandler(logOutSuccessHandler)
.permitAll()
//wagger 相关请求放行
.and()
.authorizeRequests()
//oauth相关请求放行
.antMatchers("/oauth/**").permitAll()
.antMatchers("/login/**").permitAll()
.antMatchers("/logout/**").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources").permitAll()
.antMatchers("/swagger-ui/*").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/v3/api-docs").permitAll()
.antMatchers("/webjars/**").permitAll()
// 其他所有请求需要身份认证
.anyRequest().authenticated()
//配置自定义异常处理
.and().exceptionHandling()
.accessDeniedHandler(simpleAccessDeniedHandler)
.authenticationEntryPoint(simpleAuthenticationEntryPoint);
// 添加自定义 过滤器 ,放置在身份认证过滤器前
http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
// 使用自定义登录身份认证组件
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
/**
* AuthenticationManager对象在OAuth2认证服务中要使用,提前放入IOC容器中
*/
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}