前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Pig4Cloud之登陆验证(一)客户端认证处理

Pig4Cloud之登陆验证(一)客户端认证处理

原创
作者头像
一觉睡到小时候
发布2022-12-07 14:10:14
1.1K0
发布2022-12-07 14:10:14
举报
文章被收录于专栏:国产程序员国产程序员

## 前端登陆

```

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`

![image](https://img2022.cnblogs.com/blog/1901531/202211/1901531-20221123165400637-1358659661.png)

`authenticationManager`调用`authenticate`

![image](https://img2022.cnblogs.com/blog/1901531/202211/1901531-20221123170021767-1869891249.png)

调用 `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`

![image](https://img2022.cnblogs.com/blog/1901531/202211/1901531-20221123180634728-559620415.png)

```

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`

![image](https://img2022.cnblogs.com/blog/1901531/202211/1901531-20221123180800409-1050358306.png)

![image](https://img2022.cnblogs.com/blog/1901531/202211/1901531-20221123181335632-1869430188.png)

如果认证成功且返回的结果不为 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。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档