前端登陆
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.$store
.dispatch("LoginByUsername", this.loginForm)
.then(() => {
this.$router.push({path: this.tagWel.value});
})
.catch(() => {
this.refreshCode();
});
}
});
}
看一下LoginByUsername
,在/src/store/modules/user.js中
const scope = 'server'
export const loginByUsername = (username, password, code, randomStr) => {
const grant_type = 'password'
let dataObj = qs.stringify({'username': username, 'password': password})
let basicAuth = 'Basic ' + window.btoa(website.formLoginClient)
// 保存当前选中的 basic 认证信息
setStore({
name: 'basicAuth',
content: basicAuth,
type: 'session'
})
return request({
url: '/auth/oauth2/token',
headers: {
isToken: false,
Authorization: basicAuth
},
method: 'post',
params: {randomStr, code, grant_type, scope},
data: dataObj
})
}
客户端认证
当访问 OAuth2 相关接口时(/oauth2/token
、/oauth2/introspect
、/oauth2/revoke
),授权服务器需要进行客户端认证。
Spring Authorization Server 截至目前支持如下五种客户端认证方式:client_secret_basic
、client_secret_post
、client_secret_jwt
、private_key_jwt
、none
(针对公共客户端)
OAuth2ClientAuthenticationFilter
实现客户端认证的拦截器就是 OAuth2ClientAuthenticationFilter
。 其核心代码如下:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.requestMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authenticationRequest = this.authenticationConverter.convert(request);
if (authenticationRequest instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) authenticationRequest).setDetails(
this.authenticationDetailsSource.buildDetails(request));
}
if (authenticationRequest != null) {
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
}
filterChain.doFilter(request, response);
} catch (OAuth2AuthenticationException ex) {
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
}
}
其核心逻辑就是通过 authenticationConverter
从 request 中解析出客户端认证信息,构建成 Authentication,再通过 authenticationManager
对 Authentication 进行认证。
DelegatingAuthenticationConverter
authenticationConverter
的类型实际上是 DelegatingAuthenticationConverter
,它持有一个 AuthenticationConverter
列表(不同的认证请求,其参数不同,所以会有不同的AuthenticationConverter
实现类)。
DelegatingAuthenticationConverter
在解析请求时会遍历 AuthenticationConverter
列表,当某个 AuthenticationConverter
解析成功时,立即返回,这也能确定此请求是什么认证方式,后续再执行对应的认证逻辑。
private AuthenticationConverter authenticationConverter;
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
RequestMatcher requestMatcher) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.authenticationManager = authenticationManager;
this.requestMatcher = requestMatcher;
this.authenticationConverter = new DelegatingAuthenticationConverter(
Arrays.asList(
new JwtClientAssertionAuthenticationConverter(),
new ClientSecretBasicAuthenticationConverter(),
new ClientSecretPostAuthenticationConverter(),
new PublicClientAuthenticationConverter()));
}
ProviderManager
authenticationManager
的类型实际上是 ProviderManager
,它持有一个 AuthenticationProvider
列表(不同的认证方式,其认证逻辑不同,所以会有不同的AuthenticationProvider
实现类)。
AuthenticationManager
接口的默认实现为 ProviderManager
authenticationManager
调用authenticate
调用 AuthenticationProvider
中的 supports(Class<?> authentication) 方法,判断是否支持当前的 Authentication
请求。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
......
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
只有支持当前 Authentication
请求的 AuthenticationProvider
才会继续后续逻辑处理。
然后调用 AuthenticationProvider
中的 authenticate
方法进行身份认证。
此处的provider为ClientSecretAuthenticationProvider
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
......
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
......
try {
result = provider.authenticate(authentication);
......
}
在ClientSecretAuthenticationProvider
中调用RegisteredClientRepository
,通过clientId
去数据库查询client
如果认证成功且返回的结果不为 null,则执行 authentication details
的拷贝逻辑。
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
......
private void copyDetails(Authentication source, Authentication dest) {
if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
token.setDetails(source.getDetails());
}
}
如果发生 AccountStatusException
或 InternalAuthenticationServiceException
异常,则会通过Spring事件发布器AuthenticationEventPublisher
发布异常事件。
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
......
private void prepareException(AuthenticationException ex, Authentication auth) {
eventPublisher.publishAuthenticationFailure(ex, auth);
}
如果异常为其它类型的 AuthenticationException
,则将此异常设置为lastException
并返回。
catch (AuthenticationException e) {
lastException = e;
}
如果认证结果为 null,且存在父 AuthenticationManager
,则调用父 AuthenticationManager
进行同样的身份认证操作,其处理逻辑基本同上。
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
如果认证结果不为 null,同时,此时的 eraseCredentialsAfterAuthentication
参数为 true,且此时认证后的Authentication
实现了 CredentialsContainer
接口,那么即调用 CredentialsContainer
接口的凭据擦除方法,即eraseCredentials
,擦除相关凭据信息。
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
其中,有一个防止重复发布 AuthenticationSuccessEvent
事件的处理,即 parentResult
为空。如果 parentResult
为 null,则代表父 AuthenticationManager
不存在或者没有身份认证成功,也即没有发布过 AuthenticationSuccessEvent
事件。此时,便由此处发布 AuthenticationSuccessEvent
事件。
如果lastException
为 null,则代表当前的 Authentication
并没有对应支持的 Provider
。此时,便会抛出相应异常。
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
如同防止重复发布 AuthenticationSuccessEvent
事件的处理一样,也有一个防止 AbstractAuthenticationFailureEvent
事件重复发布的逻辑处理。如果 parentException
为 null,则代表父AuthenticationManager
不存在、没有进行身份认证或者发布过 AbstractAuthenticationFailureEvent
事件,此时,便由此处发布 AbstractAuthenticationFailureEvent
事件。
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
最后,抛出 lastException。
博客园
腾讯云
掘金
简书