前言

本文是梳理整合SpringCloud和SpringSecurity OAuth2的搭建流程!好久没撸SpringSecurity OAuth2这系列代码了,都快忘了,特写此文章梳理脉络!开干!!!

Maven版本

微服务版本

<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>

<!-- spring cloud alibaba 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

SpringSecurity OAuth2版本

<!--安全模块-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>

通过微服务版本限定后spring-security-oauth2-autoconfigure的最终版本自动适配为2.1.2

授权码模式

刚开始我这里就不一次性把一大堆配置放上来,需要什么就写什么,不然到时候都搞不清那个配置是干嘛,有什么用的!这也是我写这个文章的缘由!

授权服核心配置-AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

}

SpringSecurity核心配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

启动服务器-访问测试
访问​​​http://localhost:3000/oauth/authorize?response_type=code&client_id=tao&redirect_uri=http://baidu.com&scope=all​

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide

任意输入账号密码试试

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide_02


这是因为我们啥也没配置!

配置密码加密
​​​WebSecurityConfig中​

@Bean
public PasswordEncoder passwordEncoder() {//密码加密
return new BCryptPasswordEncoder();
}

配置登录用户账号密码
​​​WebSecurityConfig中​

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("USER","ADMIN").authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2"));
//这里配置全局用户信息
}

授权服配置端点信息
​​​AuthorizationServerConfig​

@Autowired
PasswordEncoder passwordEncoder;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

//基于内存便于测试
clients.inMemory()// 使用in-memory存储
.withClient("tao")// client_id
//.secret("secret")//未加密
.secret(passwordEncoder.encode("secret"))//加密
//.resourceIds("res1")//资源列表
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all", "ROLE_ADMIN", "ROLE_USER")// 允许的授权范围
//.autoApprove(false)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://baidu.com");
}

重启服务测试

​​ http://localhost:3000/oauth/authorize?response_type=code&client_id=tao&redirect_uri=http://baidu.com&scope=all​

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_03


登录成功得到授权码

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide_04


授权码获取token

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_05


这里授权码就基本搞定了!接下来我们试试密码模式

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide_06


​Unsupported grant type: password​​,默认不支持密码模式,需要而外配置下!

密码模式

配置认证管理器-AuthenticationManager
​​​WebSecurityConfig中​

@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

授权服配置密码模式
​​​AuthorizationServerConfig​

@Autowired
private AuthenticationManager authenticationManager;


@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {//配置令牌的访问端点和令牌服务
endpoints
.authenticationManager(authenticationManager)//认证管理器
;

}

重启访问测试

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_07


成功!

简化模式

这个就简单了,token直接是显示在地址栏上的​​http://localhost:3000/oauth/authorize?client_id=tao&response_type=token&scope=all&redirect_uri=http://baidu.com​

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_08


客户端模式这里就不演示了,实际上用的并不多!那么到这里,授权基本上的就搞定了,至于其他配置下文会深入,这里我们既然得到了Token那么我们就可以测试一下认证!

认证测试

创建测试资源

@Slf4j
@RestController
@RequestMapping("/mbb")
public class MbbController {

@GetMapping("/init")
public R init(){
return R.ok();
}

}

@Slf4j
@RestController
@RequestMapping("/oth")
public class OthController {

@GetMapping("/init")
public R init(){
return R.ok();
}
}

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

@GetMapping("/init")
public R init(){
return R.ok();
}

}

实际上也就是三个请求,/oth/init、/test/init、/test/init

认证测试

我们通过密码模式做授权得到Token

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_09


访问测试/oth/init

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_10


同样访问其他的也是一样!思考下是什么问题?刚开始检查了下代码以为是WebSecurityConfig中没有配置安全策略,那么我们配置一下!

配置安全策略
​​​WebSecurityConfig中​

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()//系统中所有请求
.antMatchers("/**")//SpringSecurity接管的请求/**系统所有请求
.and()
.authorizeRequests()//得到SpringSecurity接管的请求
.antMatchers("/test/*")//给接管的请求(/**)中的/test/*
.hasAnyAuthority("p3")//配置需要p1权限
.antMatchers("/mbb/*")//给接管的请求(/**)中的/mbb/*
.permitAll()//放行,无需权限
.anyRequest()//其他请求
.authenticated()//都需要认证
.and()
.csrf().disable()//关闭csrf
;
}

​注意​

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide_11


简化模式:http://localhost:3000/oauth/authorize?client_id=tao&response_type=token&scope=all&redirect_uri=http://baidu.com


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_12


这不授权码模式,简化模式跳转/login就403了,为什么呢,原因是这里没有配置登录页!,我们给SpringSecurity配置一下


formLogin

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.requestMatchers()//系统中所有请求
.antMatchers("/**")//SpringSecurity接管的请求/**系统所有请求
.and()
.authorizeRequests()//得到SpringSecurity接管的请求
.antMatchers("/test/*")//给接管的请求(/**)中的/test/*
.hasAnyAuthority("p3")//配置需要p1权限
.antMatchers("/mbb/*")//给接管的请求(/**)中的/mbb/*
.permitAll()//放行,无需权限
.anyRequest()//其他请求
.authenticated()//都需要认证
.and()
.csrf().disable()//关闭csrf
;
}

测试下!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_13


接下来进入正题!这里我们配置了SpringSecurity接受所有请求也就是/** 然后给/test/* 配置了需要p1的权限,给 /mbb/ * 配置了直接放行,无需权限!访问测试!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_14


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring_15


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_16


这里好像出了问题,按照之前写SpringSecurity的思路,这里Token携带了,并且我们用户默认携带了p1的权限,理论上/oth/init只需要携带token即可访问,/test/init携带Token了还需要p1权限才能访问(当然这里是有p1权限的),所以应该也是可以访问的,但是只有/mbb/init配置放行的可以访问,这个倒是没毛病,这里可以先思考一下!我们看看返回的异常信息!code为403,这是未授权的意思呀!问题出在这个鉴权的地方,这里有个只是铺垫下请看往期文章​​SpringSecurity OAuth开发前后端分离认证框架介绍

​​,这篇文章中末尾有个关键信息!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_17


原因很简单,我们这里虽然是携带了Token,但是现在的代码无法支撑解析这个Token!这里解析这个Token就是通过上篇文章提到的OAuth2AuthenticationProcessingFilter,我们直接进入源码查看到底有没有这个过滤器!至于整么查找过滤器可以看看往期文章!​​SpringBoot整合SpringSecurityOAuth2后产生的登录、授权、鉴权一系列问题

​​这篇文章中有这个步骤!源码分析

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring_18


这里有两组过滤器链,第一个是SpringSecurity OAuth2默认的,一个是我们自己配置的SpringSecurity的,看看里面都有什么

Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_19


​SpringSecurity OAuth2这个默认只接管/oauth/token、/oauth/token_key、/oauth/check_token,也就是这三个请求会被SpringSecurity OAuth2处理,那么我们自己配置的SpringSecurity的就只处理/**很显然我们的/test/init、/oth/init、/mbb/init会被SpringSecurity接管,但是注意这个过滤器链上的过滤器中并没有解析Token的OAuth2AuthenticationProcessingFilter这个过滤器,这也就是导致我们即使加了Token,权限满足的情况下,响应的还是403未授权的原因!​​​ 正常情况下在SpringSecurity OAuth2体系中SpringSecurity只是配合做授权操作的,而认证这块是交给资源服处理的,这里还提一句,​​正常情况下,授权服和资源服并不是搭建在同一台服务器中的,这里你们可以理解为认证服和资源服代码并不是写在一起的就行了!那么我们又想资源服接管授权服的资源怎么办呢!​​当然也是有办法的,只是比较绕罢了!跟着我,老司机带你们出坑!

资源服接管请求

上面有提到资源服有两种接入方法,一种还资源和授权服写在一起,第二种是资源服和授权服独立开发!这两种我这里都演示一下!

​授权服和资源服写在一起​

创建ResourceServerConfig

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)//开启注解鉴权
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

//资源服id标识
public static final String RESOURCE_ID = "res1";

//配置资源服安全规则
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//授权的请求
.anyRequest()//任何请求
.authenticated()//需要身份认证
.and().csrf().disable();
}
}

目前我们配置的资源服是接管所有请求,也就是所有请求都需要认证!SpringSecurity的安全策略还是保持上面的不变!重启服务器,访问测试!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_20


貌似出问题了,我们调试下源码,查看一下过滤链组!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_21


滤器链组是有了,资源服的也加了,相对于处理Token的过滤器OAuth2AuthenticationProcessingFilter也会有,但是这里为什么返回的响应还是显示无效Token呢,原因是Token的存储策略,因为这里我们配置了资源服,并没有告诉资源服的存储策略!所以这里资源服即使得到了Token也不知道在哪里检验,所以我们需要配置一下Token的存储策略!

配置Token存储策略

@Configuration
public class TokenConfig {

@Bean
public TokenStore tokenStore() {
//使用内存存储令牌(普通令牌)
return new InMemoryTokenStore();
}

}

定义AuthorizationServerTokenServices
​​​AuthorizationServerConfig类中​

@Autowired
private TokenStore tokenStore;

@Autowired
private ClientDetailsService clientDetailsService;

//令牌管理服务
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);//客户端详情服务
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}

更改AuthorizationServerEndpointsConfigurer配置
​​​AuthorizationServerConfig类中​

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {//配置令牌的访问端点和令牌服务
endpoints
.authenticationManager(authenticationManager)//认证管理器
.tokenServices(tokenService())
;
}

资源服配置Token存储策略-ResourceServerSecurityConfigurer
​​​ResourceServerConfig类中​

@Autowired
TokenStore tokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)
.stateless(true);
}

修改资源权限

@Slf4j
@RestController
@RequestMapping("/mbb")
public class MbbController {

@PreAuthorize("hasAuthority('p8')")
@GetMapping("/init")
public R init(){
return R.ok();
}

}

@Slf4j
@RestController
@RequestMapping("/oth")
public class OthController {

@PreAuthorize("hasAuthority('p1')")
@GetMapping("/init")
public R init(){
return R.ok();
}

}

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

@GetMapping("/init")
public R init(){
return R.ok();
}

}

这里我们配置的是/mbb/init无需权限,/oth/init需要p1权限,/test/init需要p8,上面刚开始我们给用户默认配置的是p1和p2的权限,SpringSecurity中的安全策略还是不变,还是如下,这里还是SpringSecurity接管/**请求,/test/*需要p1权限,/mbb/*是全部放行,这里貌似和Controller接口上的注解标注有冲突!这其实是我故意这么写的!等下演示下现象!

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.requestMatchers()//系统中所有请求
.antMatchers("/**")//SpringSecurity接管的请求/**系统所有请求
.and()
.authorizeRequests()//得到SpringSecurity接管的请求
.antMatchers("/test/*")//给接管的请求(/**)中的/test/*
.hasAnyAuthority("p3")//配置需要p1权限
.antMatchers("/mbb/*")//给接管的请求(/**)中的/mbb/*
.permitAll()//放行,无需权限
.anyRequest()//其他请求
.authenticated()//都需要认证
.and()
.csrf().disable()//关闭csrf
;
}

重启服务-测试

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_22


Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_23


Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_24


这里就很有意思了/oth/init可以访问,原因hi当前用户具备p1和p2的权限所以可以访问,/test/init可以访问,因为这里没有设置任何权限,但是这里SpringSecurity的安全策略是配置的p3权限,这里用户只有p1、p2权限,那么理论上应该是无法访问的,但是实际上确实可以访问的,所以这里是有冲突的,这个问题我先标记出来 ​​标记问题1​​ 放在后面解释,/mbb/init这个无法访问!原因是这里/mbb/init请求上标注注解需要p8,而用户不具备p8所以无法访问,但是这里有和SpringSecurity中的安全策略冲突了,SpringSecurity中的安全策略配置的是/mbb/*请求全部放行!这个问题其实和上面标注的问题1是一样的!其实讲到这里这个资源服和授权服写在一起的情况已经能限制资源了,下面来解释下 ​标记问题1标记问题1

这里我们看看往期文章​​SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解​

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring_25


这里在解释下吧,就是服务器目前有三组过滤器链,第一个我们不管,第二个是资源服的,第三个是SpringSecurity的,这里是有顺序d额,这个很关键,我们知道一个路径只能被一个过滤器处理,这里资源服接管的是所有请求,SpringSecurity接管的也是所有请求,那么既然这里资源服和SpringSecurity都接管了所有请求,但是一个请求只能被一个过滤器链处理,所以这里也就是说哪个过滤器链在前面,那个过滤器配置的规则就生效,这个详情可以看上面的那篇文章,这里关于这个过滤器链组的顺序也是可以调整,可以通过Order进行调整!

​资源服单独部署​

创建测试服

pom和上面的一样即可

Spring Cloud整合SpringSecurity OAuth2(全网最强)_ide_26

资源服controller和之前的一样即可

@Slf4j
@RestController
@RequestMapping("/mbb")
public class MbbController {

@PreAuthorize("hasAuthority('p8')")
@GetMapping("/init")
public R init(){
return R.ok();
}

}


@Slf4j
@RestController
@RequestMapping("/oth")
public class OthController {

@PreAuthorize("hasAuthority('p1')")
@GetMapping("/init")
public R init(){
return R.ok();
}

}


@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

@GetMapping("/init")
public R init(){
return R.ok();
}

}

资源服配置

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

public static final String RESOURCE_ID = "res1";

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenServices(tokenService())//验证令牌的服务
.stateless(true);
}

@Override
public void configure(HttpSecurity http) throws Exception {

http
.authorizeRequests()//授权的请求
.anyRequest()//任何请求
.authenticated()//需要身份认证
.and().csrf().disable();
}

@Bean
@Primary
public ResourceServerTokenServices tokenService() {
//如果资源服和认证服在一起可以使用DefaultTokenServices进行本地检验
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:3000/oauth/check_token");
service.setClientId("tao");
service.setClientSecret("secret");
return service;
}


}

将授权服中的资源服配置剔除
直接将ResourceServerConfig删除即可!

开放授权服远程检验token
这里我们因为将资源服和授权服分开写了,所以Token无法共享,可以通过远程的方式校验Token,授权服AuthorizationServerConfig配置如下

/**
* @Description: 用来配置令牌端点(Token Endpoint)的安全约束
* Token令牌默认是有签名的,并且资源服务需要验证这个签名,
* 因此呢,你需要使用一个对称的Key值,用来参与签名计算,
* 这个Key值存在于授权服务以及资源服务之中。
* 或者你可以使用非对称加密算法来对Token进行签名,
* Public Key公布在/oauth/token_key这个URL连接中,
* 默认的访问安全规则是"denyAll()",
* 即在默认的情况下它是关闭的,
* 你可以注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启
* (例如使用"permitAll()"来开启可能比较合适,因为它是一个公共密钥)。
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")//开放获取tokenkey(这个是做JWT的时候开放的!也就是资源服可以通过这个请求得到JWT加密的key,目前这里没什么用,可以不用配置)
.checkTokenAccess("permitAll()")//开放远程token检查 /oauth/check_token body token
.allowFormAuthenticationForClients();//允许client使用form的方式进行authentication的授权

}

其他配置不动!重启服务器测试!

通过密码模式得到Token

Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring_27


Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_28


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring_29


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_30


这里请求只被资源服接管,所以这里的请求都是符合预期的,这里如果我们有成千上百台资源服,那么每个请求都要远程调用授权服进行认证,那么我们的授权服压力会很大,所以我们可以使用上面代码中提到的JWT,下面我们就开启配置JWT颁发Token!

在搞JWT之前我建议先了解下TokenStore,在了解TokenStore的时候呢顺便有可以了解下AuthorizationCodeServices

AuthorizationCodeServices

​​SpringSecurity OAuth2关于AuthorizationCodeServices​​

TokenStore

​​SpringSecurity OAuth2关于TokenStore​​

JWT替换默认Token

​​SpringSecurity OAuth使用JWT替换默认Token​​

自定义JWT数据

​​SpringSecurityOAuth2采用JWT生成Token的模式自定义JWT数据​​

获取JWT中的数据

​​SpringSecurityOAuth2获取JWT中的数据​​

那么到这里,我们对token的生成,存储策略有了进一步的了解,那么现在我们的授权服是使用JWT生成Token,同时自定义了一些数据,然后Redis中也存储一份,虽然JWT是自包含,且可以设置过期时间,但是这个是为了满足实际业务,例如,当我们给用户禁用后,然后之前颁发的JWT格式的Token还是能解析出数据,那么这里就不符合业务逻辑,所以这里存储一份在Redis中是为了当我们将用户禁用后,把Redis中的Token也删除,这样用户禁用后携带的老JWT格式Token再到Redis中对比就对比失败,那么这样就能及时更新用户状态信息!接下来我们采用现在的代码,测试一下资源服鉴权

资源服鉴权JWT格式Token

因为我们采用 是JWT格式的Token,那么这个Token中是携带权限信息的,只要配置好资源服,JWT的Token格式资源服已经帮我们实现好了解析!我们按在上面的文章,将JWT格式Token在授权服中配置好后,授权服就不用动了,既然Token中携带了权限信息那么也就意味着资源服无需远程访问授权服进行Token检查,我们改造下资源服核心配置!如下!
改造资源服核心配置

/**
* @description: 资源服核心配置
* @author TAO
* @date 2021/9/15 23:40
*/

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

public static final String RESOURCE_ID = "res1";

@Autowired
TokenStore tokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)
.stateless(true);
}

@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//授权的请求
.anyRequest()//任何请求
.authenticated()//需要身份认证
.and().csrf().disable();
}

}

注意这里有一个tokenStore注入,那么此时需要和授权服保持一致,授权服采用的是Redis那么这里也需要采用Redis,那么这时资源服就知道从哪读取Token然后通过Redis中得到的Token解析对比,所以这个​​tokenStore至关重要!​​,通常我们会将tokenStore抽离出来,让资源服和授权服共用同一个tokenStore,那么这样就可以保证tokenStore的存取方式是一致的!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_31


TokenStore

/**
* @description: Token存储核心配置,默认为程序内存,当配置tokenStore时根据配置来
* @author TAO
* @date 2021/9/15 22:22
*/
@Slf4j
@Configuration
public class TokenStoreConfig {

/**
* Redis中OAuth相关前缀
*/
String OAUTH_ACCESS = "yy:access:";


@Autowired
private RedisConnectionFactory redisConnectionFactory;

/**
*使用内存存储令牌(普通令牌)
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "security.oauth2", name = "tokenStore", havingValue = "memory" ,matchIfMissing=true)
public TokenStore tokenStoreInMemory() {
log.info("===>"+"tokenStoreInMemory");
return new InMemoryTokenStore();
}

/**
* Token持久化Redis
* 使用redis时的配置,
* prefix检查配置的前缀
* name检查的属性
* havingValue当值为redis时该配置类才生效
* 默认生效(matchIfMissing = true)
* @author tao
*/
@Bean
@ConditionalOnProperty(prefix = "security.oauth2", name = "tokenStore", havingValue = "redis")
public TokenStore tokenStoreInRedis() {
log.info("===>"+"tokenStoreInRedis");
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(OAUTH_ACCESS);//设置Redis中OAuth相关前缀
return tokenStore;
}

}

我这里的是将授权服和资源服分了模块,让将整个SpringSecurity OAuth2共用的配置写到common中,然后授权服和资源服去引用common中的配置,这样后期使用起来非常方便,只需要创建普通额Spring Web项目,需要做授权服的就直接引用授权服依赖即可,需要做资源服额就直接引用资源服额依赖即可,这样保证我们无需开发冗余代码,而且这样使得架构复用性提高!

重启服务器测试资源服检验JWT格式Token

资源权限还是不变

Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_32


/mbb/init还是需要p8权限、/oth/init还是需要p1、/test/init不设置权限,但是需要Token,我们的用户默认还是携带p1、p2权限,那么测试开始!

Spring Cloud整合SpringSecurity OAuth2(全网最强)_java_33


Spring Cloud整合SpringSecurity OAuth2(全网最强)_安全策略_34


Spring Cloud整合SpringSecurity OAuth2(全网最强)_spring boot_35


测试成功!那么到这里独立资源服鉴权JWT格式Token就完成了,那么上面既然提到JWT格式的Token是自包含,那么我们就解析JWT中的数据,这里还有一个问题,就是这个认证失败的返回,这里/mbb/init权限不够,返回的信息是SpringSecurity OAuth2默认的信息,不是很准确,对前端、用户不是很友好,那么这个将会在后面进行改造!归结为SpringSecurity OAuth2异常处理!

解析JWT自定义数据

过去写过一篇这个文章,介绍很详细,这里就不徒劳了,​​SpringSecurityOAuth2获取JWT中的数据

SpringSecurity OAuth2异常处理

由于SpringSecurity OAuth2异常处理这部分篇幅较长,单独拎出来写篇文章!​​SpringSecurity OAuth2异常处理OAuth2Exception​​

SpringSecurity OAuth2自定义授权模式(短信验证码授权)

之前写过一篇这个文章,在我写这篇文章的时候也是按照那篇文章复制粘贴过来的,直接可以用!​​SpringSecurityOauth2自定义授权模式​​

这篇文章还有一些相关联的,大家可以参看阅读一下!
​​SpringSecurityOAuth2授权流程源码分析(自定义验证码模式)​​SpringSecurityOAuth2授权流程加载源码分析
SpringSecurityOAuth2授权流程源码分析

文章到这里那么就还剩打通数据库的操作了,本文之前都是没有打通数据库的,端点信息是写死在代码中的,然后用户信息也是写死在代码中的,那么接下来就完成主流程的最后一步,连接数据库!

持久化数据库

这里持久化数据库有两部分数据,一部分是端点数据,一部分是用户数据,我这里只写端点数据配置读取数据库,因为用户数据这块之前写过,这里也提一嘴,为什么我愿意在SpringSecurity这个框架上花这么多时间,其实是因为我写的第一篇博客其实就是关于SpringSecurity的,所以,从​​Springboot+Mybatis+Springsecurity+MySQL的整合​​这篇文章开始,到​​Spring Cloud整合SpringSecurity OAuth2(全网最强)​​这篇文章,我从只会简单接入SpringSecurity,到深入了解整个体系,深入源码,一路下来有点爽!哈哈哈

用户数据部分:
​Springboot+Mybatis+Springsecurity+MySQL的整合​​SpringSecurity从数据库获取用户信息

端点数据部分
这个其实特别简单了就直接贴代码了!前提是想导入数据库数!

@Autowired(required = false)
private DataSource dataSource;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
log.info("===>"+"基于数据库");
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}

这种方式只能使用默认的表名,如果我们想修改表名也是可以的换写为下面的方案!

@Autowired(required = false)
private DataSource dataSource;

String CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
log.info("===>"+"基于数据库");
String selectClientDetailsSql = "select "+ CLIENT_FIELDS + " from 自定义表名where client_id = ?";
String findClientDetailsSql ="select " + CLIENT_FIELDS + " from 自定义表名order by client_id ";
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setSelectClientDetailsSql(selectClientDetailsSql);
jdbcClientDetailsService.setFindClientDetailsSql(findClientDetailsSql);
clients.withClientDetails(jdbcClientDetailsService);
}

这里还提供一种方案,就是我们可以将断点信息存放到缓存中,实际业务中端点信息其实便更并不频繁!那么我们可以使用如下方案!我们编写一个自己的ClientDetailsService继承JdbcClientDetailsService然后重写loadClientByClientId方法即可

创建CacheClientDetailsService

public class CacheClientDetailsServiceextends JdbcClientDetailsService {

public CacheClientDetailsService(DataSource dataSource) {
super(dataSource);
}

/**
* 重写原生方法支持redis缓存
* @param clientId
* @return
*/
@Override
@SneakyThrows
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
public ClientDetails loadClientByClientId(String clientId) {
return super.loadClientByClientId(clientId);
}

}

在改造一下configure中的

JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);

更改为

CacheClientDetailsServiceextends jdbcClientDetailsService = new CacheClientDetailsServiceextends (dataSource);

即可!

那么至此主流程将全部完成,后续会出一篇我关于SpringSecurity 和SpringSecurity OAuth2 的文章合集!