吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1996|回复: 3
收起左侧

[Java 转载] oauth使用token时自定义返回结构体

[复制链接]
RicardoZym 发表于 2022-4-9 16:18

自定义获取token,使用token异常时的返回结构体

我们都知道在使用OAuth2获取token及刷新和check token时,返回的结果是不规律的,如下
image-20220407105411097.png

但正常情况下,许多接口调用都希望有统一的返回结构体,以便于能够正常解析,如 code,msg,data这种格式的返回结构体。
我们通过查看源码,发现TokenEndpoint这个类是token调用的接口类,我们找到获取token的接口代码如下:

image-20220409155016513.png
我们无法编辑oauth2的源代码,但我们可以通过切面的方式进行数据处理。

如下:

@Component
@Aspect
public class AuthTokenAspect {

    //日志
    private static final Logger LOG = LoggerFactory.getLogger(AuthTokenAspect.class);

    /** 定义获取token切入点 */
    @Pointcut("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
    public void tokenPoint(){
    }

    @Around("tokenPoint()")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        GenericResult result = new GenericResult();
        try {
            Object proceed = pjp.proceed();
            if (null != proceed) {
                ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>)proceed;
                OAuth2AccessToken body = responseEntity.getBody();
                if (responseEntity.getStatusCode().is2xxSuccessful()) {
                    result.setMsg(BaseConstants.SUCCESS);
                    result.setData(body);
                } else {
                    LOG.error("获取token 错误:{}", responseEntity.getStatusCode().toString());
                    result.setCode(String.valueOf(responseEntity.getStatusCode().value()));
                    result.setMsg(responseEntity.getStatusCode().name());
                    result.setData(body);
                }
            }
        } catch (Exception e) {
            String message = e.getMessage();
            if (message.contains("expired")) {
                result.setCode("ec_expired_token");
                result.setMsg("token过期,请重新登陆");
                result.setData(message);
            } else {
                result.setCode("ec_invalid_token");
                result.setMsg(message);
            }
        }
        return ResponseEntity
                .status(200)
                .body(result);
    }
}

在这里进行如此处理,就能够在获取token接口时返回一个自定义的返回结构体了。

但是以上处理只是针对获取token接口的返回结构体,当你在调用其他方法接口需要进行token验证但你携带的token过期了或者是无效token时,我们会被直接拦截在网关getway里。因为网关这里会对你的调用携带的token进行校验,不通过直接返回,这样就会导致又无法统一返回结构体了。

我们可以在网关中做如下配置:

首先我们找到出现此问题的原因源代码: OAuth2AuthenticationProcessingFilter  这个过滤器 找到如下这段作妖的代码,当你的携带的token过期或失效的时候,执行这行代码就会出错,Authentication authentication = tokenExtractor.extract(request);然后被catch到,进行数据处理,然后返回。所以我们要针对catch后的数据处理做调整。

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
            ServletException {

        final boolean debug = logger.isDebugEnabled();
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;

        try {

            Authentication authentication = tokenExtractor.extract(request);

            if (authentication == null) {
                if (stateless && isAuthenticated()) {
                    if (debug) {
                        logger.debug("Clearing security context.");
                    }
                    SecurityContextHolder.clearContext();
                }
                if (debug) {
                    logger.debug("No token in request, will continue chain.");
                }
            }
            else {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
                if (authentication instanceof AbstractAuthenticationToken) {
                    AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
                    needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
                }
                Authentication authResult = authenticationManager.authenticate(authentication);

                if (debug) {
                    logger.debug("Authentication success: " + authResult);
                }

                eventPublisher.publishAuthenticationSuccess(authResult);
                SecurityContextHolder.getContext().setAuthentication(authResult);

            }
        }
        catch (OAuth2Exception failed) {
            SecurityContextHolder.clearContext();

            if (debug) {
                logger.debug("Authentication request failed: " + failed);
            }
            eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
                    new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

            authenticationEntryPoint.commence(request, response,
                    new InsufficientAuthenticationException(failed.getMessage(), failed));

            return;
        }

        chain.doFilter(request, response);
    }

我们看到这行数据处理,authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed)); 然后我们发现这个是可以进行配置的。所以首先写如下类

package com.travelsky.etermcloud.gateway.config;

import com.alibaba.fastjson.JSON;
import com.travelsky.etermcloud.gateway.vo.GenericResult;
import org.apache.http.entity.ContentType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {

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

        try {
            GenericResult result = new GenericResult();
            String message = authException.getMessage();
            //区分 无效token和 过期token的错误码
            if (message.contains("expired")) {
                result.setCode("ec_expired_token");
                result.setMsg("token过期,请重新登陆");
                result.setData(message);
            } else {
                result.setCode("ec_invalid_token");
                result.setMsg(message);
            }
            response.setContentType(ContentType.APPLICATION_JSON.toString());
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(JSON.toJSONString(result));
        } catch (Exception e) {
            throw new ServletException();
        }
    }

}

然后我们再将它在config中进行配置,如下:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    AuthExceptionEntryPoint authExceptionEntryPoint;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.authenticationEntryPoint(authExceptionEntryPoint);
    }
}

这样就ok了,以上只是我个人的简单处理,如有不正确请指正。有更好的建议也可探讨

[size=1.63]自定义获取token,使用token异常时的返回结构体我们都知道在使用OAuth2获取token及刷新和check token时,返回的结果是不规律的,如下file://C:\Users\zym\Desktop\MD文档\学习\img\image-20220407105411097.png?lastModify=1649492035但正常情况下,许多接口调用都希望有统一的返回结构体,以便于能够正常解析,如 code,msg,data这种格式的返回结构体。我们通过查看源码,发现TokenEndpoint这个类是token调用的接口类,我们找到获取token的接口代码如下:file://C:\Users\zym\Desktop\MD文档\学习\img\image-20220409155016513.png?lastModify=1649492035我们无法编辑oauth2的源代码,但我们可以通过切面的方式进行数据处理。如下:@Component
@Aspect
public class AuthTokenAspect {
&#8203;
    //日志
    private static final Logger LOG = LoggerFactory.getLogger(AuthTokenAspect.class);
&#8203;
    /** 定义获取token切入点 */
    @Pointcut("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
    public void tokenPoint(){
    }
&#8203;
    @Around("tokenPoint()")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        GenericResult result = new GenericResult();
        try {
            Object proceed = pjp.proceed();
            if (null != proceed) {
                ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>)proceed;
                OAuth2AccessToken body = responseEntity.getBody();
                if (responseEntity.getStatusCode().is2xxSuccessful()) {
                    result.setMsg(BaseConstants.SUCCESS);
                    result.setData(body);
                } else {
                    LOG.error("获取token 错误:{}", responseEntity.getStatusCode().toString());
                    result.setCode(String.valueOf(responseEntity.getStatusCode().value()));
                    result.setMsg(responseEntity.getStatusCode().name());
                    result.setData(body);
                }
            }
        } catch (Exception e) {
            String message = e.getMessage();
            if (message.contains("expired")) {
                result.setCode("ec_expired_token");
                result.setMsg("token过期,请重新登陆");
                result.setData(message);
            } else {
                result.setCode("ec_invalid_token");
                result.setMsg(message);
            }
        }
        return ResponseEntity
                .status(200)
                .body(result);
    }
}在这里进行如此处理,就能够在获取token接口时返回一个自定义的返回结构体了。 但是以上处理只是针对获取token接口的返回结构体,当你在调用其他方法接口需要进行token验证但你携带的token过期了或者是无效token时,我们会被直接拦截在网关getway里。因为网关这里会对你的调用携带的token进行校验,不通过直接返回,这样就会导致又无法统一返回结构体了。我们可以在网关中做如下配置:首先我们找到出现此问题的原因源代码: OAuth2AuthenticationProcessingFilter  这个过滤器 找到如下这段作妖的代码,当你的携带的token过期或失效的时候,执行这行代码就会出错,Authentication authentication = tokenExtractor.extract(request);然后被catch到,进行数据处理,然后返回。所以我们要针对catch后的数据处理做调整。    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
            ServletException {
&#8203;
        final boolean debug = logger.isDebugEnabled();
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;
&#8203;
        try {
&#8203;
            Authentication authentication = tokenExtractor.extract(request);
            
            if (authentication == null) {
                if (stateless && isAuthenticated()) {
                    if (debug) {
                        logger.debug("Clearing security context.");
                    }
                    SecurityContextHolder.clearContext();
                }
                if (debug) {
                    logger.debug("No token in request, will continue chain.");
                }
            }
            else {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
                if (authentication instanceof AbstractAuthenticationToken) {
                    AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
                    needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
                }
                Authentication authResult = authenticationManager.authenticate(authentication);
&#8203;
                if (debug) {
                    logger.debug("Authentication success: " + authResult);
                }
&#8203;
                eventPublisher.publishAuthenticationSuccess(authResult);
                SecurityContextHolder.getContext().setAuthentication(authResult);
&#8203;
            }
        }
        catch (OAuth2Exception failed) {
            SecurityContextHolder.clearContext();
&#8203;
            if (debug) {
                logger.debug("Authentication request failed: " + failed);
            }
            eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
                    new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
&#8203;
            authenticationEntryPoint.commence(request, response,
                    new InsufficientAuthenticationException(failed.getMessage(), failed));
&#8203;
            return;
        }
&#8203;
        chain.doFilter(request, response);
    }我们看到这行数据处理,authenticationEntryPoint.commence(request, response,                                        new InsufficientAuthenticationException(failed.getMessage(), failed)); 然后我们发现这个是可以进行配置的。所以首先写如下类package com.travelsky.etermcloud.gateway.config;
&#8203;
import com.alibaba.fastjson.JSON;
import com.travelsky.etermcloud.gateway.vo.GenericResult;
import org.apache.http.entity.ContentType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
&#8203;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
&#8203;
&#8203;
@Component
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {
&#8203;
&#8203;
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws ServletException {
&#8203;
        try {
            GenericResult result = new GenericResult();
            String message = authException.getMessage();
            //区分 无效token和 过期token的错误码
            if (message.contains("expired")) {
                result.setCode("ec_expired_token");
                result.setMsg("token过期,请重新登陆");
                result.setData(message);
            } else {
                result.setCode("ec_invalid_token");
                result.setMsg(message);
            }
            response.setContentType(ContentType.APPLICATION_JSON.toString());
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(JSON.toJSONString(result));
        } catch (Exception e) {
            throw new ServletException();
        }
    }
&#8203;
}然后我们再将它在config中进行配置,如下:@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    AuthExceptionEntryPoint authExceptionEntryPoint;
&#8203;
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.authenticationEntryPoint(authExceptionEntryPoint);
    }
}这样就ok了,以上只是我个人的简单处理,如有不正确请指正。有更好的建议也可探讨

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

justyvan 发表于 2022-4-14 22:48
mark一下
孤梦丨 发表于 2022-5-13 22:01
枕下的悲情 发表于 2022-5-29 13:48
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 06:30

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表