进学阁

业精于勤荒于嬉,行成于思毁于随

0%

集成Oauth2.1认证中心

概念

新版本中的springcloud已经将springcloudoauth2包取消掉,oauth2新增了一个spring-authorization-server的项目,在spring-authorization-server下现在已经有3个可用版本,其中0.3.0对应的是jdk11版本, 0.4.0对应的是jdk8版本,1.0版本对应的是jdk17。以下的实例是以jdk11为例

建立授权服务

创建服务的过程这里不做赘述,项目需要的pom文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>


<!--auth -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>

创建数据表

Spring authorization Server的建表文件在:

1
2
3
4
5
org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql

org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql

Spring Security的建表语句在

1
org/springframework/security/core/userdetails/jdbc/users.ddl

总共5张表,这里放出0.3.0版本的sql其他版本需要自己找一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
CREATE TABLE oauth2_authorization_consent (
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorities varchar(1000) NOT NULL,
PRIMARY KEY (registered_client_id, principal_name)
);



/*
IMPORTANT:
If using PostgreSQL, update ALL columns defined with 'blob' to 'text',
as PostgreSQL does not support the 'blob' data type.
*/
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL,
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
attributes blob DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value blob DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata blob DEFAULT NULL,
access_token_value blob DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata blob DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value blob DEFAULT NULL,
oidc_id_token_issued_at timestamp DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata blob DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata blob DEFAULT NULL,
PRIMARY KEY (id)
);


CREATE TABLE oauth2_registered_client (
id varchar(100) NOT NULL,
client_id varchar(100) NOT NULL,
client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
client_secret varchar(200) DEFAULT NULL,
client_secret_expires_at timestamp DEFAULT NULL,
client_name varchar(200) NOT NULL,
client_authentication_methods varchar(1000) NOT NULL,
authorization_grant_types varchar(1000) NOT NULL,
redirect_uris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);



create table users
(
username varchar(50) not null
primary key,
password varchar(500) not null,
enabled tinyint(1) not null
);

create table authorities
(
username varchar(50) not null,
authority varchar(50) not null,
constraint ix_auth_username
unique (username, authority),
constraint fk_authorities_users
foreign key (username) references users (username)
);


修改配置文件

1
2
3
4
5
6
7
8
9
10
11
# Tomcat
server:
port: 1067
spring:
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/oauth2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root

自定义授权服务器

在springsecurity5.6以后配置文件为新建Configuration,然后覆盖Bean的形式进行配置,所以这里我们创建配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class AuthorizationServerConfig {
@Autowired()
JdbcTemplate jdbcTemplate;
@Autowired()
DataSource dataSource;

/**
* 这是个Spring security 的过滤器链,默认会配置
* <p>
* OAuth2 Authorization endpoint
* <p>
* OAuth2 Token endpoint
* <p>
* OAuth2 Token Introspection endpoint
* <p>
* OAuth2 Token Revocation endpoint
* <p>
* OAuth2 Authorization Server Metadata endpoint
* <p>
* JWK Set endpoint
* <p>
* OpenID Connect 1.0 Provider Configuration endpoint
* <p>
* OpenID Connect 1.0 UserInfo endpoint
* 这些协议端点,只有配置了他才能够访问的到接口地址(类似mvc的controller)。
*
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);

return http.build();
}

/**
* 这个也是个Spring Security的过滤器链,用于Spring Security的身份认证。
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());

return http.build();
}

/**
* 配置用户信息,或者配置用户数据来源,主要用于用户的检索。
* @return
*/
@Bean
public UserDetailsService userDetailsService() {
// UserDetails userDetails = User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
// return new InMemoryUserDetailsManager();

return new JdbcUserDetailsManager(dataSource);
}

/**
* oauth2 用于第三方认证,RegisteredClientRepository 主要用于管理第三方(每个第三方就是一个客户端)
* @return
*/
@Bean
public RegisteredClientRepository registeredClientRepository() {
// RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
// .clientId("messaging-client")
// .clientSecret("{noop}secret")
// .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
// .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
// .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
// .redirectUri("http://127.0.0.1:1067/login/oauth2/code/messaging-client-oidc")
// .redirectUri("http://127.0.0.1:1067/authorized")
// .scope(OidcScopes.OPENID)
// .scope("message.read")
// .scope("message.write")
// .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
// .build();
//
// return new InMemoryRegisteredClientRepository(registeredClient);
return new JdbcRegisteredClientRepository(jdbcTemplate);
}

/**
* 用于给access_token签名使用。
* @return
*/
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}

/**
* 生成秘钥对,为jwkSource提供服务。
* @return
*/
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}

@Bean
public OAuth2AuthorizationService authorizationService() {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository());
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository());
}
/**
* 配置Authorization Server实例
* @return
*/
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
}

在spring-authorization-server中实现了jdbc模式的可以直接链接数据库进行操作

添加用户

1
2
3
4
5
6
7
8
9
10
11
    @Autowired
private UserDetailsManager userDetailsManager;
@Test
void testSaveUser() {
UserDetails userDetails = User.builder().passwordEncoder(s -> "{bcrypt}" + new BCryptPasswordEncoder().encode(s))
.username("user")
.password("password")
.roles("ADMIN")
.build();
userDetailsManager.createUser(userDetails);
}

使用 UserDetailsManager 创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @Autowired
private RegisteredClientRepository registeredClientRepository;



@Test
void testSaveClient() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{bcrypt}" + new BCryptPasswordEncoder().encode("secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:1069/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:1069/authorized")
.scope(OidcScopes.OPENID).scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
registeredClientRepository.save(registeredClient);
}

以registeredClientRepository创建客户端

测试

打开:http://127.0.0.1:1067/oauth2/authorize?response_type=code&client_id=messaging-client&scope=message.read&redirect_uri=http://127.0.0.1:1067/authorized

输入前面的添加的账号密码

报错了,不过没关系。因为在oath2中第一步是拿到code 拿到code之后才能换取token

我们只要拿到地址栏后面的code信息就好。复制出code信息,打开postman进行post请求

点击访问