[OAuth2]关于oauth2登陆的流程和配置详解(上)

992 阅读4分钟

我们的登陆配置从这里开始,OAuth2支持几种登陆方式,这里hrms采用的是用户名密码登陆

/oauth/token?client_id=client&client_secret=secret&grant_type=password&username=10223&password=123456&p_phone_no=111111

这是一个标准的hrms登陆url,这里面的参数会被分成三个部分:

client_id=client&client_secret=secret&grant_type=password //client认证参数

username=10223&password=123456 //用户名密码

p_phone_no=111111 //其他参数

这个访问会被交到org.springframework.security.oauth2.provider.endpoint.TokenEndpoint这个类中去处理:



这个类里有两个/oauth/token入口,区分为get方式和post方式,这里注意观察红色部分,使用get方式提交会直接抛出异常,所以这里我们只能用post方式提交

通过password和username换取token的过程可以大致概括为:p/u + client-->Authentication(未验证)-->Authentication(通过验证)-->token 这是一连串变化的过程,前者的信息被包含在了后者之中,下面我们细细分析

这里我们会有一个client的概念,作用是区分不同的模块(或者不同的应用)的登陆,并且不同的client会拥有不同的权限以及不同的验证方式,为了方便,我们把这些数据存在服务器数据库里,如图所示:



为了将这些client数据存在数据库里而不是写死在某个xml文件中,我们需要做如下的配置:

<beans:bean id="clientDetailsService" class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
    <beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>

dataSource是我们在服务器配置的jndi数据源。而JdbcClientDetailsService是实现了ClientDetailsService接口的一个实现类,另外还有InMemoryClientDetailsService可以选择。
通过JdbcClientDetailsService的loadClientByClientId方法,我们从数据库里取到了client的完整数据,并包装到ClientDetails中



用户名密码已经其他传入的参数则被包装成了TokenRequest,至此,所有需要的数据都被装载到ClientDetails和TokenRequest这两个类的实例中。接下来便是依据参数生成Authentication并进行验证,最后用Authentication换取Token并返回了。而这一串过程都被包含在了下面这一句代码中:

OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

getTokenGranter返回给我们的是接口TokenGranter的实现类,依据我们的grant_type=password,给我们返回了专用于密码验证的ResourceOwnerPasswordTokenGranter实现类,这个类的grant方法调用了两个方法:1、自己重写的getOAuth2Authentication方法,返回Authentication。2、tokenServices的createAccessToken方法,传入Authentication返回我们最终需要的Token。-

先看第一步:

		Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
		((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		try {
			userAuth = authenticationManager.authenticate(userAuth);
		}
		catch (AccountStatusException ase) {
			//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
			throw new InvalidGrantException(ase.getMessage());
		}

传入用户名和密码生成的Authentication进行验证,同时又返回这个经过处理的Authentication,在后面我们会看到两者的变化。注意这里的authenticationManager,它有一个集合属性

private List<AuthenticationProvider> providers = Collections.emptyList();

是的,这里的providers 便是我们在standardSecurity.xml中配置的authentication-manager:

	<authentication-manager alias="defaultAuthenticationManager">
		<authentication-provider ref="daoAuthenticationProvider"/>		
		<authentication-provider ref="loginAuthenticationProvider"/>
		<authentication-provider ref="wxLoginAuthenticationProvider"/>
	</authentication-manager>

其中,loginAuthenticationProvider和wxLoginAuthenticationProvider都是我们自己定义的,而这里的getOAuth2Authentication方法中使用的是一个for循环调用Provider的authenticate方法来对Authentication进行验证

			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}

所以有多种验证方式时,只要有一个Provider验证通过便可以获得Token,不同的验证方法可以并存。

在我们自定义的Provider中,验证通过后,通常我们会赋予其一个角色(role),用以确定其token权限:

authorities.add(new SimpleGrantedAuthority("ROLE_UNITY"));

验证之前的Authentication:


验证通过之后的Authentication:



注意到我们添加的角色信息 “ROLE_UNITY“ 已经有了,并且authenticated已经通过为true。

最后是调用tokenServices的createAccessToken方法,将Authentication转化为Token,这里的tokenService也是我们在xml文件中重新指定的:

<beans:bean id="tokenServices" class="hstaff.core.token.CustomTokenServices">
    <beans:property name="tokenStore" ref="tokenStore"/>
    <beans:property name="supportRefreshToken" value="false"/>
    <beans:property name="accessTokenValiditySeconds" value="1296000"/>
    <beans:property name="clientDetailsService" ref="clientDetailsService"/>
</beans:bean>

最后调用的tokenStore的getAccessToken方法将Authentication和生成的Token一一对应并保存起来,而Authentication中几乎包含了所有的参数,所以我们也就明白了通过对AccessToken的解析,我们可以重新找回认证Authentication中的所有信息,包括username,也包括后期验证通过后发放的role,甚至包括其他自定义的参数

让tokenStore将token存入数据库中,对应xml中如下的配置

<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
    <beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>
<beans:bean id="userApprovalHandler"
            class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">

    <beans:property name="tokenStore" ref="tokenStore"/>
    <beans:property name="clientDetailsService" ref="clientDetailsService"/>
    <beans:property name="requestFactory" ref="requestFactory"/>

</beans:bean>

至此,可配置项基本都说到了,password验证流程结束。