一、认证服务器基于jdbc方式
如果不懂请移步上一篇文章:Spring security OAuth2 授权服务器搭建-CSDN博客
在上一篇文章中,TokenStore的默认实现为 InHenoryTokenStore 即内存存储,对于 CLient 信息,userDetaitsServce 接负责从存储库中读取数据,在上面的案例中默认使用的也是 InHemoryCLientDetalsService 实现类。如果要想使用政据库存储,只要提供这些接口的实现类即可,而框架已经为我们写好 dbekenStore 和 dbcclientDetatsService
1.1 数据库所需表
我们使用spring security 自带的sql 语句生成对应的表结构,我们关注一下 oauth_client_details 这个表,这个表用来储存用户密钥信息,相当于前面的这个地方
clients.inMemory() //基于内存,后期可以入库查出来 .withClient("client-lq") .secret(passwordEncoder.encode("secret-lq"))
-- 写入客户端信息
INSERT INTO oauth_client_details
VALUES ('client-id', NULL,'$2a$10$2McX6ml8CVK3RUNpLkX1zeQeNkrEvLCPOJ2hhpG18XMeIMbJWIJnK', 'read', 'authorization_code,refresh_token','http://www.baidu.com',NULL, NULL, NULL, NULL,NULL);client_secret 需要加密存进去,否则会报不成功
-- structure for clientdetails
DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE clientdetails (
appId varchar(256) NOT NULL,
resourceIds varchar(256) DEFAULT NULL,
appSecret varchar(256) DEFAULT NULL,
scope varchar(256) DEFAULT NULL,
grantTypes varchar(256) DEFAULT NULL,
redirectUrl varchar(256) DEFAULT NULL,
authorities varchar(256) DEFAULT NULL,
access_token_validity int(11) DEFAULT NULL,
refresh_token_validity int(11) DEFAULT NULL,
additionalInformation varchar(4096) DEFAULT NULL,
autoApproveScopes varchar(256) DEFAULT NULL,
PRIMARY KEY (appId)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_access_token
DROP TABLE IF EXISTS oauth_access_token;
CREATE TABLE oauth_access_token (
token_id varchar(256) DEFAULT NULL,
token blob,
authentication_id varchar(256) NOT NULL,
user_name varchar(256) DEFAULT NULL,
client_id varchar(256) DEFAULT NULL,
authentication blob ,
refresh_token varchar(256) DEFAULT NULL,
PRIMARY KEY (authentication_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_approvals
DROP TABLE IF EXISTS oauth_approvals;
CREATE TABLE oauth_approvals (
userId varchar(256) DEFAULT NULL,
clientId varchar(256) DEFAULT NULL,
scope varchar(256) DEFAULT NULL,
status varchar(10) DEFAULT NULL,
expiresAt timestamp NOT NULL DEFAULT current_timestamp ON UPDATE current_timestamp,
lastModifiedAt date null
)ENGINE=InnODB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_client_details
DROP TABLE IF EXISTS oauth_client_details;
CREATE TABLE oauth_client_details (
client_id varchar(256) NOT NULL,
resource_ids varchar(256) DEFAULT NULL,
client_secret varchar(256) DEFAULT NULL,
scope varchar(256) DEFAULT NULL,
authorized_grant_types varchar(256) DEFAULT NULL,
web_server_redirect_uri varchar(256) DEFAULT NULL,
authorities varchar(256) DEFAULT NULL,
access_token_validity int(11) DEFAULT NULL,
refresh_token_validity int(11) DEFAULT NULL,
additional_information varchar(4096) DEFAULT NULL,
autoapprove varchar(256) DEFAULT NULL,
PRIMARY KEY (client_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_clienttoken
DROP TABLE IF EXISTS oauth_client_token;
CREATE TABLE oauth_client_token (
token_id varchar(256) DEFAULT NULL,
token blob,
authentication_id varchar(256) NOT NULL,
user_nam varchar(256) DEFAULT NULL,
client_id varchar(256) DEFAULT NULL,
PRIMARY KEY (authentication_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_code
DROP TABLE IF EXISTS oauth_code;
CREATE TABLE oauth_code (
code varchar(256) DEFAULT NULL,
authentication blob
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for oauth_refresh_token
DROP TABLE IF EXISTS oauth_refresh_token;
CREATE TABLE oauth_refresh_token (
token_id varchar(256) DEFAULT NULL,
token blob,
authentication blob
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
select * from oauth_refresh_token;
-- 写入客户端信息
INSERT INTO oauth_client_details
VALUES ('client-id', NULL,'$2a$10$2McX6ml8CVK3RUNpLkX1zeQeNkrEvLCPOJ2hhpG18XMeIMbJWIJnK', 'read', 'authorization_code,refresh_token','http://www.baidu.com',NULL, NULL, NULL, NULL,NULL);
1.2 认证服务器配置
@Configuration
@EnableAuthorizationServer //指定当前应用为授权服务器
public class JdbcAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;
@Autowired
public JdbcAuthorizationServerConfig(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, AuthenticationManager authenticationManager, DataSource dataSource) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
this.authenticationManager = authenticationManager;
this.dataSource = dataSource;
}
@Bean
public ClientDetailsService clientDetails() {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
// 使用哪一种加密方式
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
// 用来配置授权服务器可以为哪些客户端授权 client_id,secret redirect_url
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());// 使用jdbc存储
}
@Bean
public TokenStore tokenStore() {
JdbcTokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);
return jdbcTokenStore;
}
// 配置令牌存储
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());// 配置令牌存储为数据库存储
endpoints.userDetailsService(userDetailsService);
// 配置 tokenServices 参数
DefaultTokenServices tokenServices = new DefaultTokenServices();//修改默认令牌生成服务
tokenServices.setTokenStore(endpoints.getTokenStore());// 基于数据库令牌生成
tokenServices.setSupportRefreshToken(true);//是否支持刷新令牌
tokenServices.setReuseRefreshToken(true); // 是否重复使用刷新令牌( 直到过期
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());//设置客户端信息
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());//用来控制令牌存储增强策略
// 访问令牌的默认有效期( 以秒为单位)。过期的令牌为零或负数。
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
endpoints.tokenServices(tokenServices);// 使用配置令牌服务
}
}
1.3 security 配置
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("root").password(passwordEncoder().encode("123456")).roles("ADMIN").build());
return inMemoryUserDetailsManager;
}
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()// 开启表单登录
.and()
.csrf().disable();
}
}
1.4 数据库配置
@Configuration
@Slf4j
public class MysqlDsConfig {
/**
* 配置数据源
* @return
*/
@Primary
@Bean
public DataSource mysqlDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/oauth3");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("12345678");
return dataSource;
}
}
1.5 启动测试
当我们刷新token的时候 oauth_refresh_token 会有值进去,使用 apifox 测试令牌
二、资源服务器基于 jdbc 方式
需要连接数据库信息,跟认证服务器数据源一样,因为资源服务器最终也要去读取表信息
所需的依赖,资料参考 哔哩博主,不良人编程
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据源支持 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.1 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final DataSource dataSource;
@Autowired
public ResourceServerConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
2.2 测试controller
@RestController
public class HelloController {
@RequestMapping("hello")
public String hello() {
System.out.println("hello resource server ");
return "hello resource server";
}
}
2.3 测试结果
当我们这个时候访问的时候就需要带上 Authorization:Bearer 参数,否则提示 Full authentication is required to access this resource
curl -H "Authorization:Bearer token 对应的值" http://127.0.0.1:8083/hello
三、认证服务器基于 jwt 方式
jwt 分为三部分,用三个点隔开 ,第一部分标识 header 标识用那种加密方式;中间部分为subject 主体信息,可以是用户信息,也可以是一个json数据;最后一部分为sign 签名信息,这个信息需要设置足够复杂才能被破解;
3.1 认证服务器配置
可能在疑问,为什么还是要用数据库,这次我们只需要从库里面读取一次用户信息,只是使用一张表 oauth_client_details 其他信息都不会使用,jwt 有自动过期时间
@Configuration
@EnableAuthorizationServer //指定当前应用为授权服务器
public class JwtAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;
@Autowired
public JwtAuthorizationServerConfig(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, AuthenticationManager authenticationManager, DataSource dataSource) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
this.authenticationManager = authenticationManager;
this.dataSource = dataSource;
}
@Bean
public ClientDetailsService clientDetails() {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
// 使用哪一种加密方式
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
// 用来配置授权服务器可以为哪些客户端授权 client_id,secret redirect_url
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());// 使用jdbc存储
}
// 使用同一个秘钥编码
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("12332111");
return jwtAccessTokenConverter;
}
// jwt生成方式生成令牌
@Bean
public TokenStore tokenStore() {
JwtTokenStore jdbcTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
return jdbcTokenStore;
}
// 配置令牌存储
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore())// 配置jwt令牌存储为数据库存储
.accessTokenConverter(jwtAccessTokenConverter());
}
//http://127.0.0.1:8082/oauth/authorize?client_id=client&response_type=code&redirect_url=https://www.baidu.com
}
3.2 测试效果
1、页面授权效果有所不同,获取code
2、返回的令牌有所不同
api fox 效果
四、资源服务器基于 jwt 方式
我们只需要跟认证服务器加密规则设置相同即可,因为jwt 自带加密算法,就类似于我们两个定义了一套rsa的秘钥,我们加密规则相同,约定一个签名戳,这个别人获取不到,你请求过来的时候我验证合法性,所以就不需要用到数据库了
4.1 资源服务器配置
@Configuration
@EnableResourceServer
public class JwtResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
// jwt生成方式生成令牌
@Bean
public TokenStore tokenStore() {
JwtTokenStore jdbcTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
return jdbcTokenStore;
}
// 使用同一个秘钥编码
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("12332111");
return jwtAccessTokenConverter;
}
}
4.2 测试效果
curl -H "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjYzMTY1MTAsInVzZXJfbmFtZ
SI6InJvb3QiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjEyYTgyMmNhLWQ3MTgtNDE1Yy1hYWQ3LTA5ZjIxODNjNzY0YiIsImNsaWVudF9pZCI6ImNsaWVudC1scSIsInNjb3BlI
jpbInJlYWQ6dXNlciJdfQ.myzFE0VJOhlFLOsddBknOeB6Y499RwZ1X2zTM4PxC00" http://127.0.0.1:8083/hello
Z1X2zTM4PxC00" http://127.0.0.1:8083/hello
hello resource server
D:\java-tool\idea-project\spring-security-study>
使用apifox 测试