代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客
目录
C3.用此客户端信息和用户信息登录后返回的token信息是:
C4.OAuth2ClientAuthenticationFilter过滤器认证的客户端信息
C5.OAuth2TokenEndpointFilter过滤器认证的用户信息
C6.OAuth2TokenEndpointFilter过滤器认证的授权token信息
C7.OAuth2TokenIntrospectionEndpointFilter过滤器认证的自省认证的信息
C8.BearerTokenAuthenticationFilter过滤器认证的token认证的信息
C1.开启 @EnableGlobalMethodSecurity,并添加 @PreAuthorize注解
C2.PigCustomOpaqueTokenIntrospector
前两篇文章的学习中,有涉及到用户权限这一块儿,但是我没仔细记住,本来想开发动态鉴权呢,但是觉得对用户权限这一块儿还不是很熟悉,所以在根据授权服务端流程再巩固一下!!!然后实现动态鉴鉴权。
A1.整理用户权限和客户端权限的保存与使用逻辑
我打算先从头尾两处看起,首先先确认用户都有哪些权限,客户端都有哪些权限,然后看登陆成功的token信息中都返回了哪些权限,看先两处对比的格式。
然后在其代码里面查找处理逻辑,首先客户端认证时会处理客户端权限,用户认证时会处理用户权限,再去这两个地方查看逻辑。
最终,再把这些串起来。
B1.程序中的权限与最终返回的权限
C1.程序中持久化的用户信息是:
@Bean
UserDetailsService userDetailsService(){
UserDetails userDetails = User.builder()
.username("qc")
.password("123")
.passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
.roles("read","write")
.build();
//new一个用户管理业务,注入一盒用户信息
return new InMemoryUserDetailsManager(userDetails);
}
C2.程序中持久化的客户端信息是:
private RegisteredClient createRegisteredClient(final String id) {
return RegisteredClient.withId(UUID.randomUUID().toString())
// 客户端ID和密码
.clientId("qingchen")
// 此处为了避免频繁启动重复写入仓库
.id(id)
// client_secret_basic 客户端需要存明文 服务器存密文
.clientSecret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("secret"))
// 名称 可不定义
.clientName("qingchen")
// 其它Scope
.scope("server")
.build();
}
C3.用此客户端信息和用户信息登录后返回的token信息是:
{
"sub": "qc",
"clientId": "qingchen",
"iss": "https://pig4cloud.com",
"token_type": "Bearer",
"access_token": "qingchen::qc::b421f83e-8858-414f-96ad-f700e41fcafc",
"refresh_token": "fBo4y6sm6RpWcgB8rR00kW-m1l8w5djNH9jRlr19-LzLVzYrwyRG4yWNzcKO6qxLaeL87AKk0Lal414nrsZqfT8FKgDo4njMmDCLfjUdm4gEwIX6GKQAsHj_n3ov9V_y",
"aud": [
"qingchen"
],
"license": "https://pig4cloud.com",
"nbf": 1664164585.066,
"user_info": {
"password": null,
"username": "qc",
"authorities": [
{
"authority": "ROLE_read"
},
{
"authority": "ROLE_write"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"scope": [
"server"
],
"exp": 1664168185.066,
"expires_in": 3599,
"iat": 1664164585.066,
"jti": "55373257-827c-4222-959a-2505a13b9394"
}
C4.OAuth2ClientAuthenticationFilter过滤器认证的客户端信息
C5.OAuth2TokenEndpointFilter过滤器认证的用户信息
C6.OAuth2TokenEndpointFilter过滤器认证的授权token信息
C7.OAuth2TokenIntrospectionEndpointFilter过滤器认证的自省认证的信息
C8.BearerTokenAuthenticationFilter过滤器认证的token认证的信息
A2.资源鉴权的三种方式
通常有三种方式可以实现资源鉴权:1.基于配置的接口访问权限;2.基于注解的接口访问权限;3.基于过滤器的动态访问权限;
1.基于配置的接口访问权限,这个就类似前两篇里面,过滤链加的路径配置,可以设置为permit(),可以设置为authenticated(),也可以添加hasRole()。但是这样是在代码里面配置的,而且不够灵活。
2.基于注解的接口访问权限,这个是在接口处添加注解配置的,在过滤链中只会对需要认证的请求校验是否已认证,然后分发执行接口前会先调用注解对应的鉴权操作,检验是否有权限,有则执行接口,无则返回鉴权不通过。security提供了基础的鉴权操作,我们也可以实现自定义的鉴权操作。这样的鉴权非常容易理解,而且只需要给需要鉴权的接口添加注解就可以,但是,这样的方式还是不够灵活,基于编程的静态方式,具有一定的局限性。(本篇着重实现这一部分)
3.基于过滤器的动态访问权限,前面两种方式都有局限性,无法实现管理人员动态的配置和分配权限的。而动态配置就是动态处理了权限(角色)与资源(可以理解为接口URL)的映射关系,比如我们可以存储一个资源表,表中有资源URI、权限标识;我们会给一个用户某些角色/权限,角色会对应权限(用户也可以直接对应权限),然后对应到权限关联的资源URL。当登录用户访问某资源(某接口)时,先拿到该资源所对应的权限,然后在判断是否包含在当前用户拥有的权限里,有就可以访问,无就无权访问。
B1.基于注解的接口访问权限(资源服务端)
C1.开启 @EnableGlobalMethodSecurity,并添加 @PreAuthorize注解
基于注解的方式很简单,我们采用基于表达式进行方法访问控制。只需要开启全局基于注解的安全功能,然后在接口上添加注解即可;
我们首先在启动类上添加@EnableGlobalMethodSecurity( prePostEnabled = true)注解,然后在AuthenticatedController类中新建一个read方法,然后添加注解@PreAuthorize("hasAnyAuthority('ROLE_read')"),设置为拥有ROLE_read的权限的认证用户就可以访问。
然后我们启动访问/read,发现返回了403!
原因在于,我们通过accesstoken拿到的Authentication值,是默认转化的数据:
此时authorities里面存储的不是用户的权限,而是客户端的权限!
那么我们需要怎么做呢?有两种,看业务需要:
1.更改token认证后生成的authentication,实现自定义的token信息转化类生成BearerTokenAuthentication,这样就可以保证authorities属性是用户的权限;
2.自定义一个权限校验方式,通过注解鉴权时,走我们的自定义鉴权方式。
当然,我们也可以两种结合着实现,这样既能保证Authentication是我们想要的格式又能保证鉴权格式也是我们想要的!
C2.PigCustomOpaqueTokenIntrospector
首先先修改第一点,核心是在OpaqueTokenIntrospector接口下,通过token拿到用户信息的,我们就实现这个接口,原理可以仿照SpringOpaqueTokenIntrospector:
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
//将token转化成请求实体
//使用RestOperations调用请求实体,并拿到响应实体
//获取响应实体里面的claims
//将claims转化成AuthenticatedPrincipal
return this.convertClaimsSet(claims);
}
private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
...
Collection<GrantedAuthority> authorities = new ArrayList();
Iterator var4 = ((List)((Map)claims.get("user_info")).get("authorities")).iterator();
while(var4.hasNext()) {
Map scope = (Map)var4.next();
authorities.add(new SimpleGrantedAuthority((String) scope.get("authority")));
}
return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
}
重点就是this.convertClaimsSet()方法,我们只修改authorities就行,按照token里返回的格式获取!
最终的BearerTokenAuthentication的属性authorities已存储为用户权限:
此时,我们直接执行程序访问就可以访问成功啦!!!
C3.PermissionService
由于系统默认给的@PreAuthorize("hasAnyAuthority('ROLE_read')")注解不好扩展,我们可以自定义一个校验方式,先创建一个校验的业务类,提供一个校验方法,然后使用该注解@PreAuthorize("@校验业务类beanName.方法名称('所需权限值')")。那么当请求该接口时,回先进入该方法进行校验返回的是true才会执行接口,否则返回403;
public class PermissionService {
/**
* @Description: 判断接口是否有任意xxx,xxx权限
* @param permissions
* @Return: boolean
*/
public boolean hasPermission(String... permissions) {
//判断所需权限是否为空,为空则直接返回false
//拿到请求中已认证用户拥有的权限,若为null则直接返回false
//判断拥有的权限中是否有所需权限,若有则返回true,否则返回false
return ;
}
}
记得在PigResourceServerAutoConfiguration里面注入该类的的bean哦
访问成功!!
B2.基于过滤器的动态访问权限(后期补充~)
之前写过一篇关于security动态鉴权的,记录一下:spring security——学习笔记(day06)-实现授权认证-FilterSecurityInterceptor、SecurityMetadataSource、AccessDecisionM_清晨敲代码的博客-CSDN博客
这里就不再补充了,不过,我刚刚又发现,其实我们完全可以在PermissionService类里面添加全局角色与资源映射关系,然后注解中参数不再传递所需权限,而是传递当前接口唯一标识(也就是资源唯一标识,例如访问路径),然后在鉴权方法里面根据资源唯一标识拿到持久化的所需权限,然后再与已认证用户的权限进行对比!
这样就可以不用自定义那么多过滤器类了呀,不过我并没有发现有项目是这样的鉴权逻辑,改天试一下性能!!