自实现oauth2验证与spring-security的结合

自实现oauth2验证与spring-security的结合

前言

前面写了一篇关于spring-security-oauth2适配的文章,但是种种原因,项目中正在使用的spring-security版本暂时不能更换,没法直接使用spring-security-oauth2,无奈只能自己实现验证过程。
本文主要总结一下自实现oauth2验证遇到的一些问题及解决办法。

一、自实现oauth2验证v1.0

自己实现一个简单oauth2验证过程,很简单,不需要像开源软件那样考虑各种模块的配合和弹性,也不需要各种模式都支持。
我的第一个版本就是把所有过程放在一个函数中处理,回调函数传回code之后,使用code去换取AT,然后用AT去获取用户信息,这样就验证过程就结束了,是不是很简单?从功能上来看,这么做是没问题的,但是会有两个问题:

1、没有与spring-security结合起来

也就是验证虽然结束了,但是后续的权限控制之类的跟安全相关的功能其实是用不起来的。这个最终是使用设置安全上下文的方式来解决的:

//auth就是认证成功后自己生成的UsernamePasswordAuthenticationToken
SecurityContextHolder.getContext().setAuthentication(auth);

2、session id没有变更

对安全比较了解的同学都知道,验证成功后,需要改变session id,防止之前的session id被利用。而我这个版本的验证过程,因为没有与任何已有的security组件配合,session id肯定是不会变更的。这个问题最终被我用一个比较暴力的方法解决掉:

//request就是请求的HttpServletRequest
request.changeSessionId()

上面就是我之前遇到的两个问题,虽然都解决了,但是心里肯定比较虚,是不是还有其它问题没有发现?毕竟spring-security做了那么多工作,而我搞的这么简单,没出问题,不代表没有啊。。。

二、 自实现oauth2验证v2.0

由于有了上面的顾虑,我就考虑利用spring-security自身的机制来完成这个验证过程,最终,被我找到了一个不算优雅,但是比较取巧的方法–利用spring-security自身的用户名密码验证机制来帮助实现这一过程。

1、 用户名密码验证机制

用户名密码的配置一般在WebSecurityConfigurerAdapter中配置,类似:

//实现安全的一些配置
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) {
    //设置验证接口
    http.formLogin().loginProcessingUrl("/login/auth")
                //设置用户名、密码的参数名称
                .usernameParameter("account")
                .passwordParameter("password");

        ...
    }

    //将验证过程交给自定义验证工具
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new MyAuthenticationProvider());
    }
}

实现自定义验证过程:

public class MyAuthenticationProvider implements AuthenticationProvider {
    public Authentication authenticate(Authentication authentication) {
        //获取用户名、密码
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
    }
}

2、利用用户名密码验证机制

从上面的用户名密码验证机制可以看出,只要我们能够利用getName()来获取code,那剩下的验证过程通过重写AuthenticationProvider肯定能够实现,也就是类似:

http.formLogin().loginProcessingUrl("/login/auth")
                //设置用户名、密码的参数名称
                .usernameParameter("code")

too naive,试了一下,不行。。。老办法,debug,过程不赘述,简单讲一下涉及用户名密码验证的几个类及遇到的问题。

1) AbstractAuthenticationProcessingFilter

这个是实现安全验证的一个核心filter,安全验证的过滤器都是继承这个类来完成整个验证过程。该类的doFilter函数:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    if (!this.requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
    } else {
        Authentication authResult;
        try {
            authResult = this.attemptAuthentication(request, response);

            //成功验证后的session处理
            this.sessionStrategy.onAuthentication(authResult, request, response);
        } catch (InternalAuthenticationServiceException var8) {
            ...
        }
        //成功验证后处理
        this.successfulAuthentication(request, response, chain, authResult);
    }
}

其中,this.sessionStrategy.onAuthentication就是处理上文中提到的session id变更的问题,而this.successfulAuthentication就是处理上文中提到的安全上下文设置的问题。虽然从源码中看,主要涉及的也就是这两项,但是源码中名显然做了更多的工作,利用spring-security自身机制始终是更好的选择。。。

继续,我的请求是在requiresAuthentication这个函数中被拒掉的,原因是http method不是post。。。直接引出了第二个类。

2)FormLoginConfigurer

该类就是http.formLogin()函数返回的设置类,而设置监听接口的函数:

protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
    return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}

看到这段代码,心里立马有一万只CNM飘过。。。为毛POST是写死的,为毛访问控制是protected。。。
没有办法,翻来覆去,确认了确实没有办法在原有代码的基础上设置http method。。。
咱也不是菜鸟,重写,仿照FormLoginConfigurer重写了configer,当然这个过程很简单,最主要的其实只要重写这个函数就行,其它的函数基本都可以删除,然后,需要的是将自定义configer应用到HttpSecurity上:

MyLoginConfigure<HttpSecurity> loginConfigure = new MyLoginConfigure<>();
loginConfigure.setBuilder(http);

loginConfigure.loginProcessingUrl("/login/auth")
        .usernameParameter("code");

http.apply(loginConfigure);

跟默认设置差不多,只不过需要apply一下。

继续,又不行,吐血,直接引出第三个类。

3)UsernamePasswordAuthenticationFilter

该类继承AbstractAuthenticationProcessingFilter,实现attemptAuthentication函数来执行验证工作:

//attemptAuthentication函数节选
if (this.postOnly && !request.getMethod().equals("POST")) {
    throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} 

这次是挂在this.postOnly,默认是true,简单,这个在自定义configer构造函数中直接设置一下就ok:

getAuthenticationFilter().setPostOnly(false);

总结

最终结果,只需要重新实现一个configer就能达到目的,其他的功能都是按照spring-security自身的机制在运作。不算优雅,总之还算是个不错的方法。
遗留一个问题,oauth2模式是支持携带state来防止CSRF的(个人觉得意义不大,code本身应该能解决这个问题,更多的我认为还是防暴力攻击),通常做法是state存在session中,然后跟回调的state相比较,如果不一致,则请求可以丢弃。 但是在调试过程中,发现存在请求的session与回调的session(全新session)不一致的情况,这尼玛就没法搞了啊,state肯定不一样啊。。。不是必现,但是频率比较高,没定位出来,尚不清楚是三方服务器的原因还是代码的原因,spring-security-oauth2也有这个问题。虽然不影响功能使用,但是有时间还是需要研究一下的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值