Spring Security Oauth2 自定义 OAuth2 Exception

本文介绍了如何在Spring Security Oauth2中自定义登录失败和Token异常的返回信息,包括新增CustomOauthException和CustomOauthExceptionSerializer,修改配置以指定自定义异常处理,以及实现AuthExceptionEntryPoint和CustomAccessDeniedHandler。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

付出就要得到回报,这种想法是错的。

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/spring-security-OAuth208.png

前言

在使用Spring Security Oauth2登录和鉴权失败时,默认返回的异常信息如下

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

。它与我们自定义返回信息不一致,并且描述信息较少。那么如何自定义Spring Security Oauth2异常信息呢,下面我们简单实现以下。格式如下:


{
"error": "400",
"message": "坏的凭证",
"path": "/oauth/token",
"timestamp": "1527432468717"
}

自定义登录失败异常信息

新增CustomOauthException
  • 添加自定义异常类,指定json序列化方式
@JsonSerialize(using = CustomOauthExceptionSerializer.class)
public class CustomOauthException extends OAuth2Exception {
    public CustomOauthException(String msg) {
        super(msg);
    }
}
新增CustomOauthExceptionSerializer
  • 添加CustomOauthException的序列化实现
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
    public CustomOauthExceptionSerializer() {
        super(CustomOauthException.class);
    }

    @Override
    public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        gen.writeStartObject();
        gen.writeStringField("error", String.valueOf(value.getHttpErrorCode()));
        gen.writeStringField("message", value.getMessage());
//        gen.writeStringField("message", "用户名或密码错误");
        gen.writeStringField("path", request.getServletPath());
        gen.writeStringField("timestamp", String.valueOf(new Date().getTime()));
        if (value.getAdditionalInformation()!=null) {
            for (Map.Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
                String key = entry.getKey();
                String add = entry.getValue();
                gen.writeStringField(key, add);
            }
        }
        gen.writeEndObject();
    }
}
添加CustomWebResponseExceptionTranslator
  • 添加CustomWebResponseExceptionTranslator,登录发生异常时指定exceptionTranslator
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
    public CustomOauthExceptionSerializer() {
        super(CustomOauthException.class);
    }

    @Override
    public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        gen.writeStartObject();
        gen.writeStringField("error", String.valueOf(value.getHttpErrorCode()));
        gen.writeStringField("message", value.getMessage());
//        gen.writeStringField("message", "用户名或密码错误");
        gen.writeStringField("path", request.getServletPath());
        gen.writeStringField("timestamp", String.valueOf(new Date().getTime()));
        if (value.getAdditionalInformation()!=null) {
            for (Map.Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
                String key = entry.getKey();
                String add = entry.getValue();
                gen.writeStringField(key, add);
            }
        }
        gen.writeEndObject();
    }
}
修改MerryyouAuthorizationServerConfig
  • 指定自定义customWebResponseExceptionTranslator
@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
        //扩展token返回结果
        if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancerList = new ArrayList();
            enhancerList.add(jwtTokenEnhancer);
            enhancerList.add(jwtAccessTokenConverter);
            tokenEnhancerChain.setTokenEnhancers(enhancerList);
            //jwt
            endpoints.tokenEnhancer(tokenEnhancerChain)
                    .accessTokenConverter(jwtAccessTokenConverter);
        }
        endpoints.exceptionTranslator(customWebResponseExceptionTranslator);
    }

自定义Token异常信息

添加AuthExceptionEntryPoint
  • 自定义AuthExceptionEntryPoint用于tokan校验失败返回信息
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {


    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException)
            throws  ServletException {

        Map map = new HashMap();
        map.put("error", "401");
        map.put("message", authException.getMessage());
        map.put("path", request.getServletPath());
        map.put("timestamp", String.valueOf(new Date().getTime()));
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getOutputStream(), map);
        } catch (Exception e) {
            throw new ServletException();
        }
    }
}
添加CustomAccessDeniedHandler
  • 授权失败(forbidden)时返回信息
@Slf4j
@Component("customAccessDeniedHandler")
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
            Map map = new HashMap();
            map.put("error", "400");
            map.put("message", accessDeniedException.getMessage());
            map.put("path", request.getServletPath());
            map.put("timestamp", String.valueOf(new Date().getTime()));
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(objectMapper.writeValueAsString(map));
    }
}
修改MerryyouResourceServerConfig
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(new AuthExceptionEntryPoint())
        .accessDeniedHandler(CustomAccessDeniedHandler);
    }

效果如下

登录异常

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth209.gif

token异常

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth210.gif

禁止访问

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth211.gif

token失效

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/security/spring-security-oauth212.gif

代码下载

推荐文章

  1. Java创建区块链系列
  2. Spring Security源码分析系列
  3. Spring Data Jpa 系列
  4. 【译】数据结构中关于树的一切(java版)
  5. SpringBoot+Docker+Git+Jenkins实现简易的持续集成和持续部署

https://raw.githubusercontent.com/longfeizheng/longfeizheng.github.io/master/images/wechat/xiaochengxu.png

������关注微信小程序java架构师历程
上下班的路上无聊吗?还在看小说、新闻吗?不知道怎样提高自己的技术吗?来吧这里有你需要的java架构文章,1.5w+的java工程师都在看,你还在等什么?

### Spring Security OAuth2 中实现自定义认证流程 为了在Spring Security OAuth2中实现自定义认证流程,可以按照如下方法进行配置: #### 创建自定义 `UserDetailsService` 实现类 通过创建一个实现了 `UserDetailsService` 的服务来处理用户的认证逻辑。这允许开发者根据业务需求定制化身份验证过程。 ```java @Slf4j @Service("customUserDetailService") public class CustomUserDetailServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<User> userOptional = userRepository.findByUsername(username); return userOptional.map(CustomUserDetails::new).orElseThrow(() -> new UsernameNotFoundException("User not found")); } } ``` 此部分代码展示了如何构建一个名为 `CustomUserDetailServiceImpl` 的类用于替代默认的身份验证机制[^4]。 #### 配置安全设置 接着,在继承了 `WebSecurityConfigurerAdapter` 类的安全配置文件里指定新的 `UserDetailsService` 和其他必要的选项。 ```java @Configuration @EnableWebSecurity public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailServiceImpl customUserDetailService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/signIn.html") // 自定义登录页面路径 .defaultSuccessUrl("/") // 登录成功后的跳转地址 .failureUrl("/signInError"); // 登录失败后的错误页 http.authorizeRequests() .antMatchers("/signIn.html", "/signInError").permitAll() // 对特定URL放行访问权限 .anyRequest().authenticated(); // 所有请求都需要经过身份验证 http.csrf().disable(); // 关闭CSRF保护(仅作示例用途) http.userDetailsService(customUserDetailService); // 设置自定义的UserDetailsService } } ``` 上述代码片段说明了怎样调整HTTP安全策略以及引入前面提到的服务实例[^3][^5]。 #### 使用自定义认证提供者 (可选) 对于更复杂的场景,还可以注册自定义的 `AuthenticationProvider` 来进一步控制整个认证过程。例如,当需要支持多种不同的认证模式时,可以通过这种方式集成额外的功能模块。 ```java @Component public class CustomAuthProvider implements AuthenticationProvider { @Autowired private CustomUserDetailServiceImpl customUserDetailService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String name = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails userDetails = null; try { userDetails = this.customUserDetailService.loadUserByUsername(name); } catch (UsernameNotFoundException e) { throw new BadCredentialsException("Invalid credentials"); } if (!password.equals(userDetails.getPassword())) { throw new BadCredentialsException("Invalid credentials"); } Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); return new UsernamePasswordAuthenticationToken(name, password, authorities); } @Override public boolean supports(Class<?> authentication) { return true; // 或者返回具体的类型判断条件 } } ``` 这段代码展示了一种可能的方法来自定义认证提供商,并将其应用于项目之中。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java干货

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值