java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > OAuth2.0原理

Java中关于OAuth2.0的原理分析

作者:还没秃的小菜鸡

这篇文章主要介绍了Java中关于OAuth2.0的原理分析,OAuth是一个关于授权的开放网络标准,允许用户授权第三 方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,需要的朋友可以参考下

授权服务器

@EnableAuthorizationServer解析

我们都知道 一个授权认证服务器最最核心的就是 @EnableAuthorizationServer , 那么 @EnableAuthorizationServer 主要做了什么呢?

我们看下 @EnableAuthorizationServer 源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}

我们可以看到其源码内部导入了 AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration 这2个配置类。 接下来我们分别看下这2个配置类具体做了什么。

AuthorizationServerEndpointsConfiguration

@Configuration
@Import(TokenKeyEndpointRegistrar.class)
public class AuthorizationServerEndpointsConfiguration {
    // 省略 其他相关配置代码
  ....
    // 1、 AuthorizationEndpoint 创建
    @Bean
    public AuthorizationEndpoint authorizationEndpoint() throws Exception {
        AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
        FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
        authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
        authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
        authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
        authorizationEndpoint.setTokenGranter(tokenGranter());
        authorizationEndpoint.setClientDetailsService(clientDetailsService);
        authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
        authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
        authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
        authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
        authorizationEndpoint.setRedirectResolver(redirectResolver());
        return authorizationEndpoint;
    }
    // 2、 TokenEndpoint 创建
    @Bean
    public TokenEndpoint tokenEndpoint() throws Exception {
        TokenEndpoint tokenEndpoint = new TokenEndpoint();
        tokenEndpoint.setClientDetailsService(clientDetailsService);
        tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
        tokenEndpoint.setTokenGranter(tokenGranter());
        tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
        tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
        tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
        return tokenEndpoint;
    }
    // 省略 其他相关配置代码
   ....

通过源码我们可以很明确的知道:

AuthorizationServerSecurityConfiguration

@EnableResourceServer

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}

从源码中我们可以看到其导入了 ResourceServerConfiguration 配置类,这个配置类最核心的配置是 应用了 ResourceServerSecurityConfigurer ,我这边贴出 ResourceServerSecurityConfigurer 源码 最核心的配置代码如下:

public void configure(HttpSecurity http) throws Exception {
    AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);
    this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
    this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
    this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
    if (this.eventPublisher != null) {
        this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);
    }
    if (this.tokenExtractor != null) {
        this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);
    }
    this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);
    this.resourcesServerFilter.setStateless(this.stateless);
    ((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);
}
private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) {
    OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
    if (this.authenticationManager != null) {
        if (!(this.authenticationManager instanceof OAuth2AuthenticationManager)) {
            return this.authenticationManager;
        }
        oauthAuthenticationManager = (OAuth2AuthenticationManager)this.authenticationManager;
    }
    oauthAuthenticationManager.setResourceId(this.resourceId);
    oauthAuthenticationManager.setTokenServices(this.resourceTokenServices(http));
    oauthAuthenticationManager.setClientDetailsService(this.clientDetails());
    return oauthAuthenticationManager;
}

源码中最核心的 就是 官方文档中介绍的 OAuth2AuthenticationProcessingFilter 过滤器, 其配置分3步:

1、 创建 OAuth2AuthenticationProcessingFilter 过滤器 对象

2、 创建 OAuth2AuthenticationManager 对象 对将其作为参数设置到 OAuth2AuthenticationProcessingFilter 中

3、 将 OAuth2AuthenticationProcessingFilter 过滤器添加到过滤器链上

AuthorizationEndpoint生成授权码

@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
                              SessionStatus sessionStatus, Principal principal) {
    //  1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象
    AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
    Set<String> responseTypes = authorizationRequest.getResponseTypes();
    if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
        throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
    }
    if (authorizationRequest.getClientId() == null) {
        throw new InvalidClientException("A client id must be provided");
    }
    try {
        // 2、 判断  principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因
        if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
            throw new InsufficientAuthenticationException(
                    "User must be authenticated with Spring Security before authorization can be completed.");
        }
        // 3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息
        ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
        // 4、 获取参数中的回调地址并且与系统配置的回调地址对比
        String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
        String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
        if (!StringUtils.hasText(resolvedRedirect)) {
            throw new RedirectMismatchException(
                    "A redirectUri must be either supplied or preconfigured in the ClientDetails");
        }
        authorizationRequest.setRedirectUri(resolvedRedirect);
        //  5、 验证 scope 
        oauth2RequestValidator.validateScope(authorizationRequest, client);
        //  6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true)  )
        authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
                (Authentication) principal);
        boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
        authorizationRequest.setApproved(approved);
        if (authorizationRequest.isApproved()) {
            if (responseTypes.contains("token")) {
                return getImplicitGrantResponse(authorizationRequest);
            }
            if (responseTypes.contains("code")) {
                // 7 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址
                return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
                        (Authentication) principal));
            }
        }
        model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
        model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));
        return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
    }
    catch (RuntimeException e) {
        sessionStatus.setComplete();
        throw e;
    }
}

1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象

2、 判断 principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因

3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息

4、 获取参数中的回调地址并且与系统配置的回调地址(步骤3获取到的client信息)对比

5、 与步骤4一样 验证 scope

6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true))

7、 由于我们设置 autoApprove(true) 则 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址

8、 真实生成Code 的方法时 generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) 方法: 其内部是authorizationCodeServices.createAuthorizationCode()方法生成code的

TokenEndpoint 生成token

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
        Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
    // 1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
    if (!(principal instanceof Authentication)) {
        throw new InsufficientAuthenticationException(
                "There is no client authentication. Try adding an appropriate authentication filter.");
    }
    // 2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置客户端信息
    String clientId = getClientId(principal);
    ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
    // 3、 通过客户端信息生成 TokenRequest 对象
    TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
 ......
    // 4、 调用 TokenGranter.grant()方法生成 OAuth2AccessToken 对象(即token)
    OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
    if (token == null) {
        throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
    }
    // 5、 返回token
    return getResponse(token);
}

1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )

2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置的客户端信息

3、 通过客户端信息生成 TokenRequest 对象

4、 将步骤3获取到的 TokenRequest 作为TokenGranter.grant() 方法参照 生成 OAuth2AccessToken 对象(即token)

5、 返回 token

TokenGranter

TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的 grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。

五种类型分别是:

OAuth2AccessToken

@JsonSerialize(
    using = OAuth2AccessTokenJackson1Serializer.class
)
@JsonDeserialize(
    using = OAuth2AccessTokenJackson1Deserializer.class
)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(
    using = OAuth2AccessTokenJackson2Serializer.class
)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
    using = OAuth2AccessTokenJackson2Deserializer.class
)
public interface OAuth2AccessToken {
    String BEARER_TYPE = "Bearer";
    String OAUTH2_TYPE = "OAuth2";
    String ACCESS_TOKEN = "access_token";
    String TOKEN_TYPE = "token_type";
    String EXPIRES_IN = "expires_in";
    String REFRESH_TOKEN = "refresh_token";
    String SCOPE = "scope";
}

AuthorizationServerTokenServices

public interface AuthorizationServerTokenServices {
    //创建
    OAuth2AccessToken createAccessToken(OAuth2Authentication var1) throws AuthenticationException;
	//刷新
    OAuth2AccessToken refreshAccessToken(String var1, TokenRequest var2) throws AuthenticationException;
	//获取
    OAuth2AccessToken getAccessToken(OAuth2Authentication var1);
}

流程

在这里插入图片描述

资源服务器

@EnableResourceServer

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}

ResourceServerConfiguration

protected void configure(HttpSecurity http) throws Exception {
    ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
    ResourceServerTokenServices services = this.resolveTokenServices();
    if (services != null) {
        resources.tokenServices(services);
    } else if (this.tokenStore != null) {
        resources.tokenStore(this.tokenStore);
    } else if (this.endpoints != null) {
        resources.tokenStore(this.endpoints.getEndpointsConfigurer().getTokenStore());
    }
    if (this.eventPublisher != null) {
        resources.eventPublisher(this.eventPublisher);
    }
    Iterator var4 = this.configurers.iterator();
    ResourceServerConfigurer configurer;
    while(var4.hasNext()) {
        configurer = (ResourceServerConfigurer)var4.next();
        configurer.configure(resources);
    }
    ((HttpSecurity)((HttpSecurity)http.authenticationProvider(new AnonymousAuthenticationProvider("default")).exceptionHandling().accessDeniedHandler(resources.getAccessDeniedHandler()).and()).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).csrf().disable();
    http.apply(resources);
    if (this.endpoints != null) {
        http.requestMatcher(new ResourceServerConfiguration.NotOAuthRequestMatcher(this.endpoints.oauth2EndpointHandlerMapping()));
    }
    var4 = this.configurers.iterator();
    while(var4.hasNext()) {
        configurer = (ResourceServerConfigurer)var4.next();
        configurer.configure(http);
    }
    if (this.configurers.isEmpty()) {
        ((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
    }
}

ResourceServerSecurityConfigurer

public void configure(HttpSecurity http) throws Exception {
    AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);
    //创建OAuth2核心过滤器
    this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
    this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
    //设置OAuth2的身份认证处理器,没有交给spring管理(避免影响非普通的认证流程)
    this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
    if (this.eventPublisher != null) {
        this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);
    }
    if (this.tokenExtractor != null) {
        //设置TokenExtractor默认的实现BearerTokenExtractor
        this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);
    }
    this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);
    this.resourcesServerFilter.setStateless(this.stateless);
    // @formatter:off
    ((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);
}

OAuth2AuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    boolean debug = logger.isDebugEnabled();
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;
    try {
        //从请求中取出身份信息,即access_token,封装到 PreAuthenticatedAuthenticationToken
        Authentication authentication = this.tokenExtractor.extract(request);
        if (authentication == null) {
            .....
        } else {
            request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
            if (authentication instanceof AbstractAuthenticationToken) {
                AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
                needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
            }
			//认证身份
            Authentication authResult = this.authenticationManager.authenticate(authentication);
            if (debug) {
                logger.debug("Authentication success: " + authResult);
            }
            this.eventPublisher.publishAuthenticationSuccess(authResult);
            //将身份信息绑定到SecurityContextHolder中
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
    } catch (OAuth2Exception var9) {
        SecurityContextHolder.clearContext();
        if (debug) {
            logger.debug("Authentication request failed: " + var9);
        }
        this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
        this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
        return;
    }
    chain.doFilter(request, response);
}

整个filter步骤最核心的是下面2个:

1、 调用 tokenExtractor.extract() 方法从请求中解析出token信息并存放到 authentication 的 principal 字段 中

2、 调用 authenticationManager.authenticate() 认证过程: 注意此时的 authenticationManager 是 OAuth2AuthenticationManager

在解析@EnableResourceServer 时我们讲过 OAuth2AuthenticationManager 与 OAuth2AuthenticationProcessingFilter 的关系,这里不再重述,我们直接看下 OAuth2AuthenticationManager 的 authenticate() 方法实现:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (authentication == null) {
        throw new InvalidTokenException("Invalid token (token not found)");
    }
    // 1、 从 authentication 中获取 token
    String token = (String) authentication.getPrincipal();
    // 2、 调用 tokenServices.loadAuthentication() 方法  通过 token 参数获取到 OAuth2Authentication 对象 ,这里的tokenServices 就是我们资源服务器配置的。
    OAuth2Authentication auth = tokenServices.loadAuthentication(token);
    if (auth == null) {
        throw new InvalidTokenException("Invalid token: " + token);
    }
    Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
    if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
        throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
    }
    // 3、 检测客户端信息,由于我们采用授权服务器和资源服务器分离的设计,所以这个检测方法实际没有检测
    checkClientDetails(auth);
    if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        // Guard against a cached copy of the same details
        if (!details.equals(auth.getDetails())) {
            // Preserve the authentication details from the one loaded by token services
            details.setDecodedDetails(auth.getDetails());
        }
    }
    // 4、 设置认证成功标识并返回
    auth.setDetails(authentication.getDetails());
    auth.setAuthenticated(true);
    return auth;
}

整个 认证逻辑分4步:

1、 从 authentication 中获取 token

2、 调用 tokenServices.loadAuthentication() 方法 通过 token 参数获取到 OAuth2Authentication 对象 ,这里的tokenServices 就是我们资源服务器配置的。

3、 检测客户端信息,由于我们采用授权服务器和资源服务器分离的设计,所以这个检测方法实际没有检测

4、 设置认证成功标识并返回 ,注意返回的是 OAuth2Authentication (Authentication 子类)。

到此这篇关于Java中关于OAuth2.0的原理分析的文章就介绍到这了,更多相关OAuth2.0原理 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文