SpingSecurity 重定向uri白名单使用域名校验

文章目录

背景

oauth2.0 授权码模式登录,需要对重定向 uri 进行白名单校验

默认规则:SpingSecurity 关于重定向 uri 白名单的校验是使用”等于“,即重定向 uri 需要完全等于白名单内其中一个 uri 才能通过检测

需求:重定向 uri 的域名等于白名单内其中一个 uri 即可通过检测

 

分析

经过百度和谷歌一通搜索,并没有找到相关的文章,而 SpingSecurity 的官网解析得也比较难懂,所以决定通过 debug 寻找切入点

  1. 授权码登录请求 /oauth/authorize,类名为 AuthorizationEndpoint,对重定向 uri 的校验位于 redirectResolver.resolveRedirect 方法

在这里插入图片描述

  1. 进入接口 RedirectResolver
    在这里插入图片描述

  2. 接口默认实现类为 DefaultRedirectResolver,校验方法为 obtainMatchingRedirect(registeredRedirectUris, requestedRedirect)在这里插入图片描述

  3. 进入 obtainMatchingRedirect 方法,发现校验关键方法为 redirectMatches(requestedRedirect, redirectUri)
    在这里插入图片描述

  4. 进入 redirectMatches,可以看到需要域名、端口号、路径、请求参数都一致才能校验通过
    在这里插入图片描述

分析到这里,已经找到关键点了,下面就是如何修改了
 

优化

  1. 创建新类 RedirectResolver,实现 RedirectResolver 接口
  2. 重写 redirectMatches 方法,修改为只对重定向 uri 域名的校验
  3. 重写 obtainMatchingRedirect 方法
  4. AuthorizationServerEndpointsConfigurer 载入 RedirectResolver

redirectMatches 方法,只保留对 host 域名、port 端口的校验

@Override
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
    UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
    UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();

    boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
    boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
    boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
    boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;

    return schemeMatch && userInfoMatch && hostMatch && portMatch;
}

obtainMatchingRedirect 方法,重定向 uri 修改为请求 uri

private String obtainMatchingRedirect(Set<String> redirectUris, String requestedRedirect) {
    Assert.notEmpty(redirectUris, "Redirect URIs cannot be empty");

    if (redirectUris.size() == 1 && requestedRedirect == null) {
        return redirectUris.iterator().next();
    }

    for (String redirectUri : redirectUris) {
        if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) {
                return requestedRedirect;
        }
    }

    throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect
            + " does not match one of the registered values.");
}

RedirectResolver 全貌

注意: obtainMatchingRedirect 方法为 private,不能重写,所以需要重写 resolveRedirect 方法


@Configuration
public class RedirectResolver extends DefaultRedirectResolver {

    /**
     * 重定向
     *
     * @param redirectUris      白名单
     * @param requestedRedirect 请求uri
     * @return
     */
    private String obtainMatchingRedirect(Set<String> redirectUris, String requestedRedirect) {
        Assert.notEmpty(redirectUris, "Redirect URIs cannot be empty");

        if (redirectUris.size() == 1 && requestedRedirect == null) {
            return redirectUris.iterator().next();
        }

        for (String redirectUri : redirectUris) {
            if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) {
                    return requestedRedirect;
            }
        }

        throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect
                + " does not match one of the registered values.");
    }

    /**
     * 判断重定向 uri 是否符合规则
     *
     * @param requestedRedirect 请求uri
     * @param redirectUri       白名单
     * @return
     */
    @Override
    protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
        UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
        UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();

        boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
        boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
        boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
        boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;

        return schemeMatch && userInfoMatch && hostMatch && portMatch;
    }


    /**
     * 父类函数
     */
    private boolean matchPorts = true;
    private Collection<String> redirectGrantTypes = Arrays.asList("implicit", "authorization_code");
    private boolean matchSubdomains = false;

    private boolean isEqual(String str1, String str2) {
        if (StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)) {
            return true;
        } else if (!StringUtils.isEmpty(str1)) {
            return str1.equals(str2);
        } else {
            return false;
        }
    }

    @Override
    public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception {

        Set<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
        if (authorizedGrantTypes.isEmpty()) {
            throw new InvalidGrantException("A client must have at least one authorized grant type.");
        }
        if (!containsRedirectGrantType(authorizedGrantTypes)) {
            throw new InvalidGrantException(
                    "A redirect_uri can only be used by implicit or authorization_code grant types.");
        }

        Set<String> registeredRedirectUris = client.getRegisteredRedirectUri();
        if (registeredRedirectUris == null || registeredRedirectUris.isEmpty()) {
            throw new InvalidRequestException("At least one redirect_uri must be registered with the client.");
        }
        return obtainMatchingRedirect(registeredRedirectUris, requestedRedirect);
    }

    /**
     * @param grantTypes some grant types
     * @return true if the supplied grant types includes one or more of the redirect types
     */
    private boolean containsRedirectGrantType(Set<String> grantTypes) {
        for (String type : grantTypes) {
            if (redirectGrantTypes.contains(type)) {
                return true;
            }
        }
        return false;
    }
}

AuthorizationServerEndpointsConfigurer 载入 RedirectResolver

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    RedirectResolver redirectResolver;
    
	...
	
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
 		 ...
         endpoints.redirectResolver(redirectResolver);
         ...
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值