前言
针对之前的授权做个补充,这里集成OAuth2来实现
目前支持5种方式,inMemory,jdbc,redis,jwt,jwk
对于公司内部
使用呢,主推 jwt > redis > jdbc > inMemory
。。。jwk
没玩明白,但是安全性挺高。。。整明白了,觉得可以主推!!!
而认证类型呢,也有5种,authorization_code,refresh_token,password,implicit,client_credentials
对于公司内部
,主推 password > authorization_code,
refresh_token一般都是会采用的,所以不区分优先级,后面两种非常的不安全,直接不推荐了
授权服务器-核心代码
0. 在启动类上加注解
@SpringBootApplication
@EnableAuthorizationServer // 开启授权服务器注解
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class);
}
}
1. SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean // 暴漏 manager 给其它类使用
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager user = new InMemoryUserDetailsManager();
// 使用内存认证,必须指定角色(roles),实际使用中,肯定是需要自定义对接数据库的,这里为了演示
user.createUser(User.withUsername("a").password(passwordEncoder().encode("123")).roles("ADMIN").build());
return user;
}
@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();
}
}
ps: 以下4中Config只能同时采用1种,切记
ps: 以下4中Config只能同时采用1种,切记
ps: 以下4中Config只能同时采用1种,切记
2. InMemoryAuthServerConfig
@Configuration
public class InMemoryAuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
UserDetailsService detailsService;
@Resource
PasswordEncoder encoder;
@Resource
AuthenticationManager manager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用内存模式
clients.inMemory()
// 创建client为 a, secret 为 123, 这里框架要求secret必须强制加密,否则会报错
.withClient("a").secret(encoder.encode("123"))
// 权限,all所有, read, write
.scopes("all")
// 自动授权
.autoApprove("all")
// 转发跳转URL
.redirectUris("https://www.baidu.com")
// 认证类型 包含
.authorizedGrantTypes(AuthorizationGrantType.AUTHORIZATION_CODE.getValue(), // authorization_code
AuthorizationGrantType.REFRESH_TOKEN.getValue(), // refresh_token
AuthorizationGrantType.IMPLICIT.getValue(), // implicit
AuthorizationGrantType.PASSWORD.getValue(), // password
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue() // client_credentials
);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.userDetailsService(detailsService);// 开启刷新令牌必须指定, 它需要后台自动去认证
endpoints.authenticationManager(manager); // 使用密码模式必须指定 manager,它需要后台自动去认证
}
}
3. JdbcAuthServerConfig
@Configuration
public class JdbcAuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
PasswordEncoder encoder;
@Resource
AuthenticationManager manager;
@Resource
DataSource dataSource;
@Bean // 配置token存储数据源
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean // 声明ClientDetailsService,配置数据源 以及 加密方式
public ClientDetailsService clientDetails(){
JdbcClientDetailsService service = new JdbcClientDetailsService(dataSource);
service.setPasswordEncoder(encoder);
return service;
}
@Override // 配置 客户端 采用jdbc 模式,
// 使用 jdbc() ,不能直接这么写 clients.jdbc(dataSource) 因为secret经过bcrypt加密,所以要指定加密方式
// 所以要自定义 clientDetails
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Override // 配置认证端点
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(manager);//认证管理器
endpoints.tokenStore(tokenStore());//配置令牌存储为数据库存储
// 配置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天
//刷新令牌的有效性(以秒为单位)。如果小于或等于零,则令牌将不会过期
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(3)); //3天
endpoints.tokenServices(tokenServices);//使用配置令牌服务
}
}
初始化脚本
SET NAMES utf8mb4;
SET
FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table 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 DEFAULT 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_client_token
-- ----------------------------
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_name` 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;
SET
FOREIGN_KEY_CHECKS = 1;
-- 写入客户端信息
-- client_id 为 a
-- client_secret 为 123
INSERT INTO `oauth_client_details`
VALUES ('a', NULL, '$2a$10$tbFi2xQwHQcjIp4jt5eGZ..CER7ws.Pzg9RiBM1tTGH1omkNprXqC', 'all',
'authorization_code,refresh_token,password,client_credentials', 'https://www.baidu.com', NULL, NULL, NULL, NULL, NULL);
4. RedisAuthServerConfig
@Configuration
public class RedisAuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder encoder;
@Resource
private AuthenticationManager manager;
@Resource
UserDetailsService detailsService;
final RedisConnectionFactory factory;
public RedisAuthServerConfig(RedisConnectionFactory factory) {
this.factory = factory;
}
@Override //配置使用 redis 方式颁发令牌,同时配置 redis 转换器
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore()) // 设置令牌生成方式
.accessTokenConverter(converter()) // 设置令牌转换器
.authenticationManager(manager) // 使用 AuthenticationManager 管理
.userDetailsService(detailsService); // 刷新令牌必须使用
}
@Bean //使用redis方式生成令牌
public TokenStore tokenStore() {
// 设置redis存储
return new RedisTokenStore(factory);
}
@Bean// 使用默认的转换器
public DefaultAccessTokenConverter converter() {
return new DefaultAccessTokenConverter();
}
@Override//使用内存方式
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 设置 clientDetails
clients.inMemory().withClient("a").secret(encoder.encode("123"))
.scopes("all")
.autoApprove("all")
.redirectUris("https://www.baidu.com")
.authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials");
}
}
5. JwtAuthServerConfig
@Configuration
public class JwtAuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder encoder;
@Resource
private AuthenticationManager manager;
@Resource
private DataSource dataSource;
@Override //配置使用 jwt 方式颁发令牌,同时配置 jwt 转换器
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore()) // 设置令牌生成方式
.accessTokenConverter(converter()) // 设置jwt令牌转换器
.authenticationManager(manager); // 使用 AuthenticationManager 管理
}
@Bean //使用JWT方式生成令牌
public TokenStore tokenStore() {
// 设置jwt令牌转换器
return new JwtTokenStore(converter());
}
@Bean//使用同一个密钥来编码 JWT 中的 OAuth2 令牌
public JwtAccessTokenConverter converter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");//可以采用属性注入方式 生产中建议使用复杂字符串,或者字符串加密,或者加盐
return converter;
}
@Override//使用数据库方式客户端存储
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 设置 clientDetails
// 默认是 inMemory() ,基于内存
// 如果使用 jdbc() ,不能直接这么写 clients.jdbc(dataSource) 因为secret经过bcrypt加密,所以要指定加密方式
// 即需要声明 ClientDetails
clients.withClientDetails(clientDetails());
}
@Bean // 声明 ClientDetails实现
public ClientDetailsService clientDetails() {
// 设置数据源
JdbcClientDetailsService service = new JdbcClientDetailsService(dataSource);
// 设置加密方式
service.setPasswordEncoder(encoder);
return service;
}
}
资源服务器-核心代码
@Configuration
@EnableResourceServer // 开启资源服务器(也可以放在启动类上)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
final DataSource dataSource;
final RedisConnectionFactory factory;
@Autowired
public ResourceServerConfig(DataSource dataSource, RedisConnectionFactory factory) {
this.dataSource = dataSource;
this.factory = factory;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
@Bean//使用同一个密钥来编码 JWT 中的 OAuth2 令牌
public JwtAccessTokenConverter converter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");//可以采用属性注入方式 生产中建议使用复杂字符串,或者字符串加密,或者加盐
return converter;
}
@Bean
public TokenStore tokenStore() {
// 基于内存
// 但是呢,如果授权服务器和资源服务器在一起,那么授权服务器需要登录,然后获得授权信息,
// 此时访问同一服务下的资源,也是可以ok的,所以在这种情况下,这里就没必要了
return new InMemoryTokenStore();
// 基于JWT
// return new JwtTokenStore(converter());
// 基于数据库
// return new JdbcTokenStore(dataSource);
// 基于redis
// return new RedisTokenStore(factory);
}
// 当然,也可以直接发http请求的方式校验
// ## OAuth2 标准接口
//- /oauth/authorize:授权端点
//- /oauth/token:获取令牌端点
//- /oauth/confirm_access:用户确认授权提交端点
//- /oauth/error:授权服务错误信息端点
//- /oauth/check_token:用于资源服务访问的令牌解析端点
//- /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
}
pom
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.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>
<!--引入 jdbc 依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入 redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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>