百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

OAuth2.0 权限验证过程是咋样的,带你来解析

xsobi 2024-12-15 17:31 1 浏览

OAuth2.0最直观配置

我们知道,spring 中有很多内置的过滤器,我们一个普通的请求,会经过下图这些过滤器的处理

其中最重要的就是

org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter

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);
}

这里会通过 org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor#extract 来获取请求 header 中的 token 信息(如 Bearer 2c563440-bdd8-48a0-afca-f4ceb18c20ae)

public Authentication extract(HttpServletRequest request) {
    String tokenValue = extractToken(request);
    if (tokenValue != null) {
        PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, "");
        return authentication;
    }
    return null;
}

如果能获取并正确解析,则按照已授权一路通行无阻,在具体的业务代码中还能获取到当前登录用户信息。

如果没有获取到 token 或解析失败,则按照未授权路线,判断当前访问的 url 是否允许匿名访问(可以看之前的文章,怎么配置匿名访问资源),如果不允许则抛出 AccessDenied 异常,这个过程是通过下面的代码实现的。

org.springframework.security.web.access.expression.WebExpressionVoter#vote

public void decide(Authentication authentication, Object object,
        Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
    int deny = 0;

    for (AccessDecisionVoter voter : getDecisionVoters()) {
        int result = voter.vote(authentication, object, configAttributes);

        if (logger.isDebugEnabled()) {
            logger.debug("Voter: " + voter + ", returned: " + result);
        }

        switch (result) {
        case AccessDecisionVoter.ACCESS_GRANTED:
            return;

        case AccessDecisionVoter.ACCESS_DENIED:
            deny++;

            break;

        default:
            break;
        }
    }

    if (deny > 0) {
        throw new AccessDeniedException(messages.getMessage(
                "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
    }

    // To get this far, every AccessDecisionVoter abstained
    checkAllowIfAllAbstainDecisions();
}
public int vote(Authentication authentication, FilterInvocation fi,
        Collection<ConfigAttribute> attributes) {
    assert authentication != null;
    assert fi != null;
    assert attributes != null;

    WebExpressionConfigAttribute weca = findConfigAttribute(attributes);

    if (weca == null) {
        return ACCESS_ABSTAIN;
    }

    EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
            fi);
    ctx = weca.postProcess(ctx, fi);

    return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
            : ACCESS_DENIED;
}

等到 ExceptionTranslationFilter 过滤器中时,如果发现有异常,直接调用 AuthenticationEntryPoint#commence 来处理,这里也很重要,给了我们一个可以自己处理异常的机会。

org.springframework.security.web.access.ExceptionTranslationFilter#sendStartAuthentication

protected void sendStartAuthentication(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain,
        AuthenticationException reason) throws ServletException, IOException {
    // SEC-112: Clear the SecurityContextHolder's Authentication, as the
    // existing Authentication is no longer considered valid
    SecurityContextHolder.getContext().setAuthentication(null);
    requestCache.saveRequest(request, response);
    logger.debug("Calling Authentication entry point.");
    authenticationEntryPoint.commence(request, response, reason);
}

就像这里的 ResourceAuthExceptionEntryPoint 是我们自定义的处理类

@Slf4j
@Component
@AllArgsConstructor
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
	private final ObjectMapper objectMapper;

	@Override
	@SneakyThrows
	public void commence(HttpServletRequest request, HttpServletResponse response,
						 AuthenticationException authException) {
		response.setCharacterEncoding(CommonConstants.UTF8);
		response.setContentType(CommonConstants.CONTENT_TYPE);
		R<String> result = new R<>();
		result.setCode(HttpStatus.HTTP_UNAUTHORIZED);
		if (authException != null) {
			result.setMsg("error");
			result.setData(authException.getMessage());
		}
		response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
		PrintWriter printWriter = response.getWriter();
		printWriter.append(objectMapper.writeValueAsString(result));
	}
}

可以返回一个自定义的 json 对象(一般前后端分离的项目中,都是 ajax 交互,所以 json 对象是最通用的数据结构)

{
    "code": 401,
    "msg": "error",
    "data": "Full authentication is required to access this resource"
}

这样在终端获取到 401 的 code 时,可以通过全局拦截的配置(比如 axios),控制页面跳转到登录页

// HTTPresponse拦截
axios.interceptors.response.use(res => {
  const status = Number(res.status) || 200
  const message = res.data.msg || errorCode[status] || errorCode['default']
  if (status === 401) {
    store.dispatch('FedLogOut').then(() => {
      router.push({path: '/login'})
    })
    return
  }
}, error => {
  return Promise.reject(new Error(error))
})

好了,到这里,我们把一次页面请求的权限验证过程解析完了,能更清晰的认识 OAuth 的授权和验证原理了。

相关推荐

好用的云函数!后端低代码接口开发,零基础编写API接口

前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...

快速上手:Windows 平台上 cURL 命令的使用方法

在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...

使用 Golang net/http 包:基础入门与实战

简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...

#小白接口# 使用云函数,人人都能编写和发布自己的API接口

你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...

极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...

window使用curl命令的注意事项 curl命令用法

cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...

Linux 系统curl命令使用详解 linuxctrl

curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...

Tornado 入门:初学者指南 tornados

Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...

PHP Curl的简单使用 php curl formdata

本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...

Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介

本章涵盖使用Actix提供静态网页...

我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些

这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...

linux文件下载、服务器交互(curl)

基础环境curl命令描述...

curl简单使用 curl sh

1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...

常用linux命令:curl 常用linux命令大全

//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...

三十七,Web渗透提高班之hack the box在线靶场注册及入门知识

一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...