Oauth2 自定义异常信息返回(springboot无法全局捕获invalid_client,unauthorized,invalid_token)

@[toc]

在SpringBoot当中使用Oauth2的时候,发现部分Oauth2的异常无法被springboot的ControllerAdvance全局异常捕获!!!

特此记录下相关的处理方式:

invalid_client Bad client credentials

当使用password模式时,如果client_id 或者client_secret错误时,请求接口会返回以下格式的数据!!!

{
"error": "invalid_client",
"error_description": "Bad client credentials"
}

在项目当中通常有自己的统一返回代码,需要返回统一的封装,方便前端接口的处理!!!

处理方式

1 首先创建异常信息的处理

/** * @author hjljy * Oauth2异常信息返回处理 */ @Component @Slf4j public class CustomAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { log.error(e.getMessage()); //如果是client_id和client_secret相关异常 返回自定义的数据格式 if(e instanceof BadCredentialsException){ response.setStatus(HttpStatus.OK.value()); response.setHeader("Content-Type", "application/json;charset=UTF-8"); ResultInfo<Boolean> result = ResultInfo.error(ResultCode.INVALID_CLIENT); result.setData(false); response.getWriter().write(JacksonUtil.obj2String(result)); }else { super.commence(request,response,e); } } }

2 客户端配置自定义的异常信息处理

@Override public void configure(AuthorizationServerSecurityConfigurer security) { CustomAuthenticationEntryPoint authenticationEntryPoint = new CustomAuthenticationEntryPoint(); ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter(); filter.setAuthenticationManager(authenticationManager); filter.setAuthenticationEntryPoint(authenticationEntryPoint); filter.afterPropertiesSet(); security.addTokenEndpointAuthenticationFilter(filter); security // 允许表单登录 需要注释掉 // .allowFormAuthenticationForClients() // 密码加密编码器 .passwordEncoder(passwordEncoder) // 允许所有的checkToken请求 .checkTokenAccess("permitAll()"); }

unauthorized Full authentication is required to access this resource

当访问资源服务器,未携带token时,就会出现这个错误!如果携带了token,但是权限不足的话,可以全局异常切面可以通过拦截AccessDeniedException进行处理。

{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}

处理方式

1 首先创建异常信息的处理

/** * @author hjljy * Oauth2异常信息返回处理 */ @Component @Slf4j public class CustomAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { log.error(e.getMessage()); if(e instanceof BadCredentialsException){ //如果是client_id和client_secret相关异常 返回自定义的数据格式 response.setStatus(HttpStatus.OK.value()); response.setHeader("Content-Type", "application/json;charset=UTF-8"); ResultInfo<Boolean> result = ResultInfo.error(ResultCode.INVALID_CLIENT); result.setData(false); response.getWriter().write(JacksonUtil.obj2String(result)); }else if(e instanceof InsufficientAuthenticationException){ //如果是没有携带token response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setHeader("Content-Type", "application/json;charset=UTF-8"); ResultInfo<Boolean> result = ResultInfo.error(ResultCode.TOKEN_NOT_FOUND); result.setData(false); response.getWriter().write(JacksonUtil.obj2String(result)); }else { super.commence(request,response,e); } } }

2 在资源配置当中配置异常处理

@Configuration @EnableResourceServer public class Oauth2ResourceConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { CustomAuthenticationEntryPoint customAuthenticationEntryPoint = new CustomAuthenticationEntryPoint(); resources.authenticationEntryPoint(customAuthenticationEntryPoint); } }

invalid_token Cannot convert access token to JSON

当携带的Token格式错误,或者token值不对,例如本应该是

Authorization: bearer token值 结果直接是Authorization: token值 就会报错,或者token的值错误

{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}

对应处理方式

如果已经配置了上面unauthorized Full authentication is required to access this resource这个异常的处理方式,会发现token值异常也会返回上面的异常信息,这是因为两个异常都是InsufficientAuthenticationException,只是提示不一样而已,所以简单的处理方式就是将上面的返回提示就改为:未携带TOKEN或无效TOKEN,返回给前端即可。

处理方式总结

总的来说,处理方式主要是定义一个自己的CustomAuthenticationEntryPoint,并将这个类注入到Oauth2服务段和客户端的配置里面,然后复写commence这个方法,将需要返回的数据格式封装到response里面即可。

2021-07-13 更新

在上述的操作处理完毕之后,会发现loadUserByUsername会进入两次,第一次进入传过来的username是clientId的值,第二次传过来的username才是账号,处理方式如下:

具体代码如下:

@Autowired ClientDetailsService clientDetailsService; /** * 这里根据传进来的用户账号进行用户信息的构建 * 通常的做法是 * 1 根据username查询数据库对应的用户信息 * 2 根据用户信息查询出用户权限信息 例如菜单添加权限 sys:menu:add * 3 根据用户账号,密码,权限构建对应的UserDetails对象返回 * 这里实际上是没有进行用户认证功能的,真正的验证是在UsernamePasswordAuthenticationFilter对象当中 * UsernamePasswordAuthenticationFilter对象会自动根据前端传入的账号信息和UserDetails对象对比进行账号的验证 * 通常情况下,已经满足常见的使用常见,不过如果有特殊需求,需要使用自己实现的具体认证方式,可以继承UsernamePasswordAuthenticationFilter对象 * 重写attemptAuthentication 方法和successfulAuthentication方法 * 最后在WebSecurityConfiguration里面添加自己的过滤器即可 * @param username 用户账号 * @return UserInfo * @throws UsernameNotFoundException 用户不存在异常 */ @Override public UserInfo loadUserByUsername(String username) throws UsernameNotFoundException { //由于关闭了allowFormAuthenticationForClients 这个选项,所以需要判断传进来的是否是clientId的值 if(username.equals("clientId")){ ClientDetails details = clientDetailsService.loadClientByClientId(username); return new UserInfo(details.getClientId(), details.getClientSecret(),details.getAuthorities()); } //TODO 根据账号获取数据库里面的用户信息,权限信息 List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList("sys:user:info","sys:user:add"); String password = SecurityUtils.encryptPassword("123456"); UserInfo user =new UserInfo(username,password,authorityList); user.setEmail("hjljy@outlook.com"); user.setNickName("海加尔金鹰"); user.setUserId(10000000000000L); return user; }

标题:Oauth2 自定义异常信息返回(springboot无法全局捕获invalid_client,unauthorized,invalid_token)
作者:hjljy
地址:https://www.aliuying.com/articles/2021/06/25/1624610094533.html

评论

取消