Spring Security如何优雅的增加OAuth2协议授权模式
一、什么是OAuth2协议?
OAuth2.0是一个关于授权的开放的网络协议,是目前最流行的授权机制。
数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
由于授权的场景众多,OAuth2.0协议定义了获取令牌的四种授权方式,分别是:
- 授权码模式:授权码模式(authorizationcode)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
- 简化模式:简化模式(implicitgranttype)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
- 密码模式:密码模式(ResourceOwnerPasswordCredentialsGrant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
- 客户端模式:客户端模式(ClientCredentialsGrant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。
四种授权模式分别使用不同的grant_type来区分
二、为什么要自定义授权类型?
虽然OAuth2协议定义了4种标准的授权模式,但是在实际开发过程中还是远远满足不了各种变态的业务场景,需要我们去扩展。
例如增加图形验证码、手机验证码、手机号密码登录等等的场景
而常见的做法都是通过增加过滤器Filter的方式来扩展SpringSecurity授权,但是这样的实现方式有两个问题:
- 脱离了OAuth2的管理
- 不灵活:例如系统使用密码模式授权,网页版需要增加图形验证码校验,但是手机端APP又不需要的情况下,使用增加Filter的方式去实现就比较麻烦了。
所以目前在SpringSecurity中比较优雅和灵活的扩展方式就是通过自定义grant_type来增加授权模式。
三、实现思路
在扩展之前首先需要先了解SpringSecurity的整个授权流程,我以密码模式为例去展开分析,如下图所示
3.1.流程分析
整个授权流程关键点分为以下两个部分:
第一部分:关于授权类型grant_type的解析
- 每种grant_type都会有一个对应的TokenGranter实现类。
- 所有TokenGranter实现类都通过CompositeTokenGranter中的tokenGranters集合存起来。
- 然后通过判断grantType参数来定位具体使用那个TokenGranter实现类来处理授权。
第二部分:关于授权登录逻辑
- 每种授权方式都会有一个对应的AuthenticationProvider实现类来实现。
- 所有AuthenticationProvider实现类都通过ProviderManager中的providers集合存起来。
- TokenGranter类会new一个AuthenticationToken实现类,如UsernamePasswordAuthenticationToken传给ProviderManager类。
- 而ProviderManager则通过AuthenticationToken来判断具体使用那个AuthenticationProvider实现类来处理授权。
具体的登录逻辑由AuthenticationProvider实现类来实现,如DaoAuthenticationProvider。
3.2.扩展分析
根据上面的流程,扩展分为以下两种场景
场景一:只对原有的授权逻辑进行增强或者扩展,如:用户名密码登录前增加图形验证码校验。
该场景需要定义一个新的grantType类型,并新增对应的TokenGranter实现类添加扩展内容,然后加到CompositeTokenGranter中的tokenGranters集合里即可。
参考代码:PwdImgCodeGranter.java
场景二:新加一种授权方式,如:手机号加密码登录。
该场景需要实现以下内容:
- 定义一个新的grantType类型,并新增对应的TokenGranter实现类添加到CompositeTokenGranter中的tokenGranters集合里
- 新增一个AuthenticationToken实现类,用于存放该授权所需的信息。
- 新增一个AuthenticationProvider实现类实现授权的逻辑,并重写supports方法绑定步骤二的AuthenticationToken实现类
参考代码:MobilePwdGranter.java
四、代码实现
下面以场景二新增手机号加密码授权方式为例,展示核心的代码实现
4.1.创建AuthenticationToken实现类
创建MobileAuthenticationToken类,用于存储手机号和密码信息
publicclassMobileAuthenticationTokenextendsAbstractAuthenticationToken{ privatestaticfinallongserialVersionUID=SpringSecurityCoreVersion.SERIAL_VERSION_UID; privatefinalObjectprincipal; privateObjectcredentials; publicMobileAuthenticationToken(Stringmobile,Stringpassword){ super(null); this.principal=mobile; this.credentials=password; setAuthenticated(false); } publicMobileAuthenticationToken(Objectprincipal,Objectcredentials, Collectionauthorities){ super(authorities); this.principal=principal; this.credentials=credentials; super.setAuthenticated(true); } @Override publicObjectgetCredentials(){ returnthis.credentials; } @Override publicObjectgetPrincipal(){ returnthis.principal; } @Override publicvoidsetAuthenticated(booleanisAuthenticated){ if(isAuthenticated){ thrownewIllegalArgumentException( "Cannotsetthistokentotrusted-useconstructorwhichtakesaGrantedAuthoritylistinstead"); } super.setAuthenticated(false); } @Override publicvoideraseCredentials(){ super.eraseCredentials(); } }
4.2.创建AuthenticationProvider实现类
创建MobileAuthenticationProvider类,实现登录逻辑,并绑定MobileAuthenticationToken类
@Setter publicclassMobileAuthenticationProviderimplementsAuthenticationProvider{ privateZltUserDetailsServiceuserDetailsService; privatePasswordEncoderpasswordEncoder; @Override publicAuthenticationauthenticate(Authenticationauthentication){ MobileAuthenticationTokenauthenticationToken=(MobileAuthenticationToken)authentication; Stringmobile=(String)authenticationToken.getPrincipal(); Stringpassword=(String)authenticationToken.getCredentials(); UserDetailsuser=userDetailsService.loadUserByMobile(mobile); if(user==null){ thrownewInternalAuthenticationServiceException("手机号或密码错误"); } if(!passwordEncoder.matches(password,user.getPassword())){ thrownewBadCredentialsException("手机号或密码错误"); } MobileAuthenticationTokenauthenticationResult=newMobileAuthenticationToken(user,password,user.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); returnauthenticationResult; } @Override publicbooleansupports(Class>authentication){ returnMobileAuthenticationToken.class.isAssignableFrom(authentication); } }
4.3.创建TokenGranter实现类
创建MobilePwdGranter类并定义grant_type的值为mobile_password
publicclassMobilePwdGranterextendsAbstractTokenGranter{ privatestaticfinalStringGRANT_TYPE="mobile_password"; privatefinalAuthenticationManagerauthenticationManager; publicMobilePwdGranter(AuthenticationManagerauthenticationManager,AuthorizationServerTokenServicestokenServices ,ClientDetailsServiceclientDetailsService,OAuth2RequestFactoryrequestFactory){ super(tokenServices,clientDetailsService,requestFactory,GRANT_TYPE); this.authenticationManager=authenticationManager; } @Override protectedOAuth2AuthenticationgetOAuth2Authentication(ClientDetailsclient,TokenRequesttokenRequest){ Mapparameters=newLinkedHashMap<>(tokenRequest.getRequestParameters()); Stringmobile=parameters.get("mobile"); Stringpassword=parameters.get("password"); parameters.remove("password"); AuthenticationuserAuth=newMobileAuthenticationToken(mobile,password); ((AbstractAuthenticationToken)userAuth).setDetails(parameters); userAuth=authenticationManager.authenticate(userAuth); if(userAuth==null||!userAuth.isAuthenticated()){ thrownewInvalidGrantException("Couldnotauthenticatemobile:"+mobile); } OAuth2RequeststoredOAuth2Request=getRequestFactory().createOAuth2Request(client,tokenRequest); returnnewOAuth2Authentication(storedOAuth2Request,userAuth); } }
4.4.加到CompositeTokenGranter中的集合里
//添加手机号加密码授权模式 tokenGranters.add(newMobilePwdGranter(authenticationManager,tokenServices,clientDetailsService,requestFactory));
4.5.测试
使用以下地址,指定grant_type为mobile_password进行授权获取access_token
/oauth/token?grant_type=mobile_password&mobile={mobile}&password={password}
五、参考样例
详细的代码实现可以参考
https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-uaa
到此这篇关于SpringSecurity如何优雅的增加OAuth2协议授权模式的文章就介绍到这了,更多相关SpringSecurityOAuth2内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。