├── .gitignore
├── README.md
├── oauth2-auth
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── cn
│ │ └── gathub
│ │ └── auth
│ │ ├── Oauth2AuthApplication.java
│ │ ├── api
│ │ ├── CommonResult.java
│ │ ├── IErrorCode.java
│ │ └── ResultCode.java
│ │ ├── component
│ │ └── JwtTokenEnhancer.java
│ │ ├── config
│ │ ├── Oauth2ServerConfig.java
│ │ ├── RedisRepositoryConfig.java
│ │ └── WebSecurityConfig.java
│ │ ├── constant
│ │ ├── MessageConstant.java
│ │ └── RedisConstant.java
│ │ ├── controller
│ │ ├── AuthController.java
│ │ └── KeyPairController.java
│ │ ├── domain
│ │ ├── dto
│ │ │ └── Oauth2TokenDto.java
│ │ └── entity
│ │ │ ├── Client.java
│ │ │ └── User.java
│ │ ├── exception
│ │ └── Oauth2ExceptionHandler.java
│ │ └── service
│ │ ├── ClientService.java
│ │ ├── ResourceService.java
│ │ ├── UserService.java
│ │ ├── impl
│ │ ├── ClientServiceImpl.java
│ │ ├── ResourceServiceImpl.java
│ │ └── UserServiceImpl.java
│ │ └── principal
│ │ ├── ClientPrincipal.java
│ │ └── UserPrincipal.java
│ └── resources
│ ├── application.yml
│ └── jwt.jks
├── oauth2-gateway
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── cn
│ │ └── gathub
│ │ └── gateway
│ │ ├── Oauth2GatewayApplication.java
│ │ ├── api
│ │ ├── CommonResult.java
│ │ ├── IErrorCode.java
│ │ └── ResultCode.java
│ │ ├── authorization
│ │ └── AuthorizationManager.java
│ │ ├── component
│ │ ├── RestAuthenticationEntryPoint.java
│ │ └── RestfulAccessDeniedHandler.java
│ │ ├── config
│ │ ├── IgnoreUrlsConfig.java
│ │ ├── RedisRepositoryConfig.java
│ │ └── ResourceServerConfig.java
│ │ ├── constant
│ │ ├── AuthConstant.java
│ │ └── RedisConstant.java
│ │ └── filter
│ │ ├── AuthGlobalFilter.java
│ │ └── IgnoreUrlsRemoveJwtFilter.java
│ └── resources
│ └── application.yml
├── oauth2-resource
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── cn
│ │ └── gathub
│ │ └── resource
│ │ ├── Oauth2ResourceApplication.java
│ │ ├── controller
│ │ ├── HelloController.java
│ │ └── UserController.java
│ │ └── domain
│ │ └── User.java
│ └── resources
│ └── application.yml
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 | .sts4-cache
12 |
13 | ### IntelliJ IDEA ###
14 | .idea
15 | *.iws
16 | *.iml
17 | *.ipr
18 |
19 | ### NetBeans ###
20 | /nbproject/private/
21 | /build/
22 | /nbbuild/
23 | /dist/
24 | /nbdist/
25 | /.nb-gradle/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spring-cloud-gateway-oauth2
2 |
3 | ## 前言
4 | 我们理想的微服务权限解决方案应该是这样的,认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。
5 | ## 架构
6 | 通过认证服务(`oauth2-auth`)进行统一认证,然后通过网关(`oauth2-gateway`)来统一校验认证和鉴权。采用Nacos作为注册中心,Gateway作为网关,使用nimbus-jose-jwtJWT库操作JWT令牌。
7 | - oauth2-auth:Oauth2认证服务,负责对登录用户进行认证,整合Spring Security Oauth2
8 | - ouath2-gateway:网关服务,负责请求转发和鉴权功能,整合Spring Security Oauth2
9 | - oauth2-resource:受保护的API服务,用户鉴权通过后可以访问该服务,不整合Spring Security Oauth2
10 | ## 具体实现
11 | ### 一、认证服务`oauth2-auth`
12 |
13 | > 1、首先来搭建认证服务,它将作为Oauth2的认证服务使用,并且网关服务的鉴权功能也需要依赖它,在pom.xml中添加相关依赖,主要是Spring Security、Oauth2、JWT、Redis相关依赖
14 |
15 | ```java
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-web
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-security
24 |
25 |
26 | org.springframework.cloud
27 | spring-cloud-starter-oauth2
28 |
29 |
30 | com.nimbusds
31 | nimbus-jose-jwt
32 | 8.16
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-data-redis
38 |
39 |
40 |
41 | ```
42 |
43 | > 2、在application.yml中添加相关配置,主要是Nacos和Redis相关配置
44 |
45 | ```yml
46 | server:
47 | port: 9401
48 | spring:
49 | profiles:
50 | active: dev
51 | application:
52 | name: oauth2-auth
53 | cloud:
54 | nacos:
55 | discovery:
56 | server-addr: localhost:8848
57 | jackson:
58 | date-format: yyyy-MM-dd HH:mm:ss
59 | redis:
60 | database: 0
61 | port: 6379
62 | host: localhost
63 | password:
64 | management:
65 | endpoints:
66 | web:
67 | exposure:
68 | include: "*"
69 |
70 | ```
71 |
72 | > 3、使用keytool生成RSA证书jwt.jks,复制到resource目录下,在JDK的bin目录下使用如下命令即可
73 |
74 | ```shell
75 | keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
76 | ```
77 |
78 | > 4、创建UserServiceImpl类实现Spring Security的UserDetailsService接口,用于加载用户信息
79 |
80 | ```Java
81 | package cn.gathub.auth.service.impl;
82 |
83 | import org.springframework.security.authentication.AccountExpiredException;
84 | import org.springframework.security.authentication.CredentialsExpiredException;
85 | import org.springframework.security.authentication.DisabledException;
86 | import org.springframework.security.authentication.LockedException;
87 | import org.springframework.security.core.userdetails.UserDetails;
88 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
89 | import org.springframework.security.crypto.password.PasswordEncoder;
90 | import org.springframework.stereotype.Service;
91 |
92 | import java.util.ArrayList;
93 | import java.util.List;
94 | import java.util.stream.Collectors;
95 |
96 | import javax.annotation.PostConstruct;
97 |
98 | import cn.gathub.auth.constant.MessageConstant;
99 | import cn.gathub.auth.domain.entity.User;
100 | import cn.gathub.auth.service.UserService;
101 | import cn.gathub.auth.service.principal.UserPrincipal;
102 | import cn.hutool.core.collection.CollUtil;
103 |
104 | /**
105 | * 用户管理业务类
106 | *
107 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
108 | */
109 | @Service
110 | public class UserServiceImpl implements UserService {
111 |
112 | private List userList;
113 | private final PasswordEncoder passwordEncoder;
114 |
115 | public UserServiceImpl(PasswordEncoder passwordEncoder) {
116 | this.passwordEncoder = passwordEncoder;
117 | }
118 |
119 | @PostConstruct
120 | public void initData() {
121 | String password = passwordEncoder.encode("123456");
122 | userList = new ArrayList<>();
123 | userList.add(new User(1L, "admin", password, 1, CollUtil.toList("ADMIN")));
124 | userList.add(new User(2L, "user", password, 1, CollUtil.toList("USER")));
125 | }
126 |
127 | @Override
128 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
129 | List findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
130 | if (CollUtil.isEmpty(findUserList)) {
131 | throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
132 | }
133 | UserPrincipal userPrincipal = new UserPrincipal(findUserList.get(0));
134 | if (!userPrincipal.isEnabled()) {
135 | throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
136 | } else if (!userPrincipal.isAccountNonLocked()) {
137 | throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
138 | } else if (!userPrincipal.isAccountNonExpired()) {
139 | throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
140 | } else if (!userPrincipal.isCredentialsNonExpired()) {
141 | throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
142 | }
143 | return userPrincipal;
144 | }
145 |
146 | }
147 |
148 | ```
149 |
150 | > 5、创建ClientServiceImpl类实现Spring Security的ClientDetailsService接口,用于加载客户端信息
151 |
152 | ```java
153 | package cn.gathub.auth.service.impl;
154 |
155 | import org.springframework.http.HttpStatus;
156 | import org.springframework.security.crypto.password.PasswordEncoder;
157 | import org.springframework.security.oauth2.provider.ClientDetails;
158 | import org.springframework.security.oauth2.provider.ClientRegistrationException;
159 | import org.springframework.stereotype.Service;
160 | import org.springframework.web.server.ResponseStatusException;
161 |
162 | import java.util.ArrayList;
163 | import java.util.List;
164 | import java.util.stream.Collectors;
165 |
166 | import javax.annotation.PostConstruct;
167 |
168 | import cn.gathub.auth.constant.MessageConstant;
169 | import cn.gathub.auth.domain.entity.Client;
170 | import cn.gathub.auth.service.ClientService;
171 | import cn.gathub.auth.service.principal.ClientPrincipal;
172 | import cn.hutool.core.collection.CollUtil;
173 |
174 | /**
175 | * 客户端管理业务类
176 | *
177 | * @author Honghui [wanghonghui_work@163.com] 2021/3/18
178 | */
179 | @Service
180 | public class ClientServiceImpl implements ClientService {
181 |
182 | private List clientList;
183 | private final PasswordEncoder passwordEncoder;
184 |
185 | public ClientServiceImpl(PasswordEncoder passwordEncoder) {
186 | this.passwordEncoder = passwordEncoder;
187 | }
188 |
189 | @PostConstruct
190 | public void initData() {
191 | String clientSecret = passwordEncoder.encode("123456");
192 | clientList = new ArrayList<>();
193 | // 1、密码模式
194 | clientList.add(Client.builder()
195 | .clientId("client-app")
196 | .resourceIds("oauth2-resource")
197 | .secretRequire(false)
198 | .clientSecret(clientSecret)
199 | .scopeRequire(false)
200 | .scope("all")
201 | .authorizedGrantTypes("password,refresh_token")
202 | .authorities("ADMIN,USER")
203 | .accessTokenValidity(3600)
204 | .refreshTokenValidity(86400).build());
205 | // 2、授权码模式
206 | clientList.add(Client.builder()
207 | .clientId("client-app-2")
208 | .resourceIds("oauth2-resource2")
209 | .secretRequire(false)
210 | .clientSecret(clientSecret)
211 | .scopeRequire(false)
212 | .scope("all")
213 | .authorizedGrantTypes("authorization_code,refresh_token")
214 | .webServerRedirectUri("https://www.gathub.cn,https://www.baidu.com")
215 | .authorities("USER")
216 | .accessTokenValidity(3600)
217 | .refreshTokenValidity(86400).build());
218 | }
219 |
220 | @Override
221 | public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
222 | List findClientList = clientList.stream().filter(item -> item.getClientId().equals(clientId)).collect(Collectors.toList());
223 | if (CollUtil.isEmpty(findClientList)) {
224 | throw new ResponseStatusException(HttpStatus.NOT_FOUND, MessageConstant.NOT_FOUND_CLIENT);
225 | }
226 | return new ClientPrincipal(findClientList.get(0));
227 | }
228 | }
229 |
230 | ```
231 |
232 | > 6、添加认证服务相关配置Oauth2ServerConfig,需要配置加载用户信息的服务UserServiceImpl和加载客户端信息的服务ClientServiceImpl及RSA的钥匙对KeyPair
233 |
234 | ```java
235 | package cn.gathub.auth.config;
236 |
237 | import org.springframework.context.annotation.Bean;
238 | import org.springframework.context.annotation.Configuration;
239 | import org.springframework.core.io.ClassPathResource;
240 | import org.springframework.security.authentication.AuthenticationManager;
241 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
242 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
243 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
244 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
245 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
246 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
247 | import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
248 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
249 | import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
250 |
251 | import java.security.KeyPair;
252 | import java.util.ArrayList;
253 | import java.util.List;
254 |
255 | import cn.gathub.auth.component.JwtTokenEnhancer;
256 | import cn.gathub.auth.service.ClientService;
257 | import cn.gathub.auth.service.UserService;
258 | import lombok.AllArgsConstructor;
259 |
260 | /**
261 | * 认证服务器配置
262 | *
263 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
264 | */
265 | @AllArgsConstructor
266 | @Configuration
267 | @EnableAuthorizationServer
268 | public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
269 |
270 | private final UserService userService;
271 | private final ClientService clientService;
272 | private final AuthenticationManager authenticationManager;
273 | private final JwtTokenEnhancer jwtTokenEnhancer;
274 |
275 | @Override
276 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
277 | // clients.inMemory()
278 | // // 1、密码模式
279 | // .withClient("client-app")
280 | // .secret(passwordEncoder.encode("123456"))
281 | // .scopes("read,write")
282 | // .authorizedGrantTypes("password", "refresh_token")
283 | // .accessTokenValiditySeconds(3600)
284 | // .refreshTokenValiditySeconds(86400)
285 | // .and()
286 | // // 2、授权码授权
287 | // .withClient("client-app-2")
288 | // .secret(passwordEncoder.encode("123456"))
289 | // .scopes("read")
290 | // .authorizedGrantTypes("authorization_code", "refresh_token")
291 | // .accessTokenValiditySeconds(3600)
292 | // .refreshTokenValiditySeconds(86400)
293 | // .redirectUris("https://www.gathub.cn", "https://www.baidu.com");
294 | clients.withClientDetails(clientService);
295 | }
296 |
297 | @Override
298 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
299 | TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
300 | List delegates = new ArrayList<>();
301 | delegates.add(jwtTokenEnhancer);
302 | delegates.add(accessTokenConverter());
303 | enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
304 | endpoints.authenticationManager(authenticationManager)
305 | .userDetailsService(userService) //配置加载用户信息的服务
306 | .accessTokenConverter(accessTokenConverter())
307 | .tokenEnhancer(enhancerChain);
308 | }
309 |
310 | @Override
311 | public void configure(AuthorizationServerSecurityConfigurer security) {
312 | security.allowFormAuthenticationForClients();
313 | }
314 |
315 | @Bean
316 | public JwtAccessTokenConverter accessTokenConverter() {
317 | JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
318 | jwtAccessTokenConverter.setKeyPair(keyPair());
319 | return jwtAccessTokenConverter;
320 | }
321 |
322 | @Bean
323 | public KeyPair keyPair() {
324 | // 从classpath下的证书中获取秘钥对
325 | KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "654321".toCharArray());
326 | return keyStoreKeyFactory.getKeyPair("jwt", "654321".toCharArray());
327 | }
328 |
329 | }
330 |
331 | ```
332 |
333 | > 7、如果你想往JWT中添加自定义信息的话,比如说登录用户的ID,可以自己实现TokenEnhancer接口
334 |
335 | ```java
336 | package cn.gathub.auth.component;
337 |
338 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
339 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
340 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
341 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
342 | import org.springframework.stereotype.Component;
343 |
344 | import java.util.HashMap;
345 | import java.util.Map;
346 |
347 | import cn.gathub.auth.service.principal.UserPrincipal;
348 |
349 |
350 | /**
351 | * JWT内容增强器
352 | *
353 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
354 | */
355 | @Component
356 | public class JwtTokenEnhancer implements TokenEnhancer {
357 | @Override
358 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
359 | UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
360 | Map info = new HashMap<>();
361 | // 把用户ID设置到JWT中
362 | info.put("id", userPrincipal.getId());
363 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
364 | return accessToken;
365 | }
366 | }
367 |
368 | ```
369 |
370 | > 8、由于我们的网关服务需要RSA的公钥来验证签名是否合法,所以认证服务需要有个接口把公钥暴露出来
371 |
372 | ```java
373 | package cn.gathub.auth.controller;
374 |
375 | import com.nimbusds.jose.jwk.JWKSet;
376 | import com.nimbusds.jose.jwk.RSAKey;
377 |
378 | import org.springframework.web.bind.annotation.GetMapping;
379 | import org.springframework.web.bind.annotation.RestController;
380 |
381 | import java.security.KeyPair;
382 | import java.security.interfaces.RSAPublicKey;
383 | import java.util.Map;
384 |
385 | /**
386 | * 获取RSA公钥接口
387 | *
388 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
389 | */
390 | @RestController
391 | public class KeyPairController {
392 |
393 | private final KeyPair keyPair;
394 |
395 | public KeyPairController(KeyPair keyPair) {
396 | this.keyPair = keyPair;
397 | }
398 |
399 | @GetMapping("/rsa/publicKey")
400 | public Map getKey() {
401 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
402 | RSAKey key = new RSAKey.Builder(publicKey).build();
403 | return new JWKSet(key).toJSONObject();
404 | }
405 |
406 | }
407 |
408 | ```
409 |
410 | > 9、还需要配置Spring Security,允许获取公钥接口的访问
411 |
412 | ```java
413 | package cn.gathub.auth.config;
414 |
415 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
416 | import org.springframework.context.annotation.Bean;
417 | import org.springframework.context.annotation.Configuration;
418 | import org.springframework.security.authentication.AuthenticationManager;
419 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
420 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
421 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
422 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
423 | import org.springframework.security.crypto.password.PasswordEncoder;
424 |
425 | /**
426 | * SpringSecurity配置
427 | *
428 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
429 | */
430 | @Configuration
431 | @EnableWebSecurity
432 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
433 |
434 | @Override
435 | protected void configure(HttpSecurity http) throws Exception {
436 | http.authorizeRequests()
437 | .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
438 | .antMatchers("/rsa/publicKey").permitAll()
439 | .anyRequest().authenticated();
440 | }
441 |
442 | @Bean
443 | @Override
444 | public AuthenticationManager authenticationManagerBean() throws Exception {
445 | return super.authenticationManagerBean();
446 | }
447 |
448 | @Bean
449 | public PasswordEncoder passwordEncoder() {
450 | return new BCryptPasswordEncoder();
451 | }
452 |
453 | }
454 |
455 | ```
456 |
457 | > 10、创建一个资源服务ResourceServiceImpl,初始化的时候把资源与角色匹配关系缓存到Redis中,方便网关服务进行鉴权的时候获取
458 |
459 | ```java
460 | package cn.gathub.auth.service;
461 |
462 | import org.springframework.data.redis.core.RedisTemplate;
463 | import org.springframework.stereotype.Service;
464 |
465 | import java.util.List;
466 | import java.util.Map;
467 | import java.util.TreeMap;
468 |
469 | import javax.annotation.PostConstruct;
470 |
471 | import cn.gathub.auth.constant.RedisConstant;
472 | import cn.hutool.core.collection.CollUtil;
473 |
474 | /**
475 | * 资源与角色匹配关系管理业务类
476 | *
477 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
478 | */
479 | @Service
480 | public class ResourceServiceImpl {
481 |
482 | private final RedisTemplate redisTemplate;
483 |
484 | public ResourceServiceImpl(RedisTemplate redisTemplate) {
485 | this.redisTemplate = redisTemplate;
486 | }
487 |
488 | @PostConstruct
489 | public void initData() {
490 | Map> resourceRolesMap = new TreeMap<>();
491 | resourceRolesMap.put("/resource/hello", CollUtil.toList("ADMIN"));
492 | resourceRolesMap.put("/resource/user/currentUser", CollUtil.toList("ADMIN", "USER"));
493 | redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, resourceRolesMap);
494 | }
495 | }
496 |
497 | ```
498 |
499 | ### 二、网关服务`oauth2-gateway`
500 | 接下来搭建网关服务,它将作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行统一的校验认证和鉴权操作
501 |
502 | > 1、在pom.xml中添加相关依赖,主要是Gateway、Oauth2和JWT相关依赖
503 |
504 | ```java
505 |
506 |
507 | org.springframework.boot
508 | spring-boot-starter-webflux
509 |
510 |
511 | org.springframework.cloud
512 | spring-cloud-starter-gateway
513 |
514 |
515 | org.springframework.security
516 | spring-security-config
517 |
518 |
519 | org.springframework.security
520 | spring-security-oauth2-resource-server
521 |
522 |
523 | org.springframework.security
524 | spring-security-oauth2-client
525 |
526 |
527 | org.springframework.security
528 | spring-security-oauth2-jose
529 |
530 |
531 | com.nimbusds
532 | nimbus-jose-jwt
533 | 8.16
534 |
535 |
536 |
537 | ```
538 |
539 | > 2、在application.yml中添加相关配置,主要是路由规则的配置、Oauth2中RSA公钥的配置及路由白名单的配置
540 |
541 | ```yml
542 | server:
543 | port: 9201
544 | spring:
545 | profiles:
546 | active: dev
547 | application:
548 | name: oauth2-gateway
549 | cloud:
550 | nacos:
551 | discovery:
552 | server-addr: localhost:8848
553 | gateway:
554 | routes: # 配置路由路径
555 | - id: oauth2-resource-route
556 | uri: lb://oauth2-resource
557 | predicates:
558 | - Path=/resource/**
559 | filters:
560 | - StripPrefix=1
561 | - id: oauth2-auth-route
562 | uri: lb://oauth2-auth
563 | predicates:
564 | - Path=/auth/**
565 | filters:
566 | - StripPrefix=1
567 | - id: oauth2-auth-login
568 | uri: lb://oauth2-auth
569 | predicates:
570 | - Path=/login
571 | filters:
572 | - PreserveHostHeader
573 | - id: oauth2-auth-token
574 | uri: lb://oauth2-auth
575 | predicates:
576 | - Path=/oauth/token
577 | filters:
578 | - PreserveHostHeader
579 | - id: oauth2-auth-authorize
580 | uri: lb://oauth2-auth
581 | predicates:
582 | - Path=/oauth/authorize
583 | filters:
584 | - PreserveHostHeader
585 | discovery:
586 | locator:
587 | enabled: true # 开启从注册中心动态创建路由的功能
588 | lower-case-service-id: true # 使用小写服务名,默认是大写
589 | security:
590 | oauth2:
591 | resourceserver:
592 | jwt:
593 | jwk-set-uri: 'http://localhost:9401/rsa/publicKey' # 配置RSA的公钥访问地址
594 | redis:
595 | database: 0
596 | port: 6379
597 | host: localhost
598 | password:
599 | secure:
600 | ignore:
601 | urls: # 配置白名单路径
602 | - "/actuator/**"
603 | - "/oauth/token"
604 | - "/oauth/authorize"
605 | - "/login"
606 |
607 | ```
608 |
609 | > 3、对网关服务进行配置安全配置,由于Gateway使用的是WebFlux,所以需要使用@EnableWebFluxSecurity注解开启
610 |
611 | ```java
612 | package cn.gathub.gateway.config;
613 |
614 |
615 | import org.springframework.context.annotation.Bean;
616 | import org.springframework.context.annotation.Configuration;
617 | import org.springframework.core.convert.converter.Converter;
618 | import org.springframework.security.authentication.AbstractAuthenticationToken;
619 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
620 | import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
621 | import org.springframework.security.config.web.server.ServerHttpSecurity;
622 | import org.springframework.security.oauth2.jwt.Jwt;
623 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
624 | import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
625 | import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
626 | import org.springframework.security.web.server.SecurityWebFilterChain;
627 |
628 | import cn.gathub.gateway.authorization.AuthorizationManager;
629 | import cn.gathub.gateway.component.RestAuthenticationEntryPoint;
630 | import cn.gathub.gateway.component.RestfulAccessDeniedHandler;
631 | import cn.gathub.gateway.constant.AuthConstant;
632 | import cn.gathub.gateway.filter.IgnoreUrlsRemoveJwtFilter;
633 | import cn.hutool.core.util.ArrayUtil;
634 | import lombok.AllArgsConstructor;
635 | import reactor.core.publisher.Mono;
636 |
637 | /**
638 | * 资源服务器配置
639 | *
640 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
641 | */
642 | @AllArgsConstructor
643 | @Configuration
644 | @EnableWebFluxSecurity
645 | public class ResourceServerConfig {
646 | private final AuthorizationManager authorizationManager;
647 | private final IgnoreUrlsConfig ignoreUrlsConfig;
648 | private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
649 | private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
650 | private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
651 |
652 | @Bean
653 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
654 | http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
655 | // 1、自定义处理JWT请求头过期或签名错误的结果
656 | http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
657 | // 2、对白名单路径,直接移除JWT请求头
658 | http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
659 | http.authorizeExchange()
660 | .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(), String.class)).permitAll() // 白名单配置
661 | .anyExchange().access(authorizationManager) // 鉴权管理器配置
662 | .and().exceptionHandling()
663 | .accessDeniedHandler(restfulAccessDeniedHandler) // 处理未授权
664 | .authenticationEntryPoint(restAuthenticationEntryPoint) // 处理未认证
665 | .and().csrf().disable();
666 | return http.build();
667 | }
668 |
669 | @Bean
670 | public Converter> jwtAuthenticationConverter() {
671 | JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
672 | jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
673 | jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
674 | JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
675 | jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
676 | return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
677 | }
678 |
679 | }
680 |
681 | ```
682 | > 4、在WebFluxSecurity中自定义鉴权操作需要实现ReactiveAuthorizationManager接口
683 |
684 | ```java
685 | package cn.gathub.gateway.authorization;
686 |
687 |
688 | import org.springframework.data.redis.core.RedisTemplate;
689 | import org.springframework.security.authorization.AuthorizationDecision;
690 | import org.springframework.security.authorization.ReactiveAuthorizationManager;
691 | import org.springframework.security.core.Authentication;
692 | import org.springframework.security.core.GrantedAuthority;
693 | import org.springframework.security.web.server.authorization.AuthorizationContext;
694 | import org.springframework.stereotype.Component;
695 |
696 | import java.net.URI;
697 | import java.util.List;
698 | import java.util.stream.Collectors;
699 |
700 | import cn.gathub.gateway.constant.AuthConstant;
701 | import cn.gathub.gateway.constant.RedisConstant;
702 | import cn.hutool.core.convert.Convert;
703 | import reactor.core.publisher.Mono;
704 |
705 | /**
706 | * 鉴权管理器,用于判断是否有资源的访问权限
707 | *
708 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
709 | */
710 | @Component
711 | public class AuthorizationManager implements ReactiveAuthorizationManager {
712 | private final RedisTemplate redisTemplate;
713 |
714 | public AuthorizationManager(RedisTemplate redisTemplate) {
715 | this.redisTemplate = redisTemplate;
716 | }
717 |
718 | @Override
719 | public Mono check(Mono mono, AuthorizationContext authorizationContext) {
720 | // 1、从Redis中获取当前路径可访问角色列表
721 | URI uri = authorizationContext.getExchange().getRequest().getURI();
722 | Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());
723 | List authorities = Convert.toList(String.class, obj);
724 | authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
725 | // 2、认证通过且角色匹配的用户可访问当前路径
726 | return mono
727 | .filter(Authentication::isAuthenticated)
728 | .flatMapIterable(Authentication::getAuthorities)
729 | .map(GrantedAuthority::getAuthority)
730 | .any(authorities::contains)
731 | .map(AuthorizationDecision::new)
732 | .defaultIfEmpty(new AuthorizationDecision(false));
733 | }
734 |
735 | }
736 |
737 | ```
738 |
739 | > 5、这里我们还需要实现一个全局过滤器AuthGlobalFilter,当鉴权通过后将JWT令牌中的用户信息解析出来,然后存入请求的Header中,这样后续服务就不需要解析JWT令牌了,可以直接从请求的Header中获取到用户信息
740 |
741 | ```java
742 | package cn.gathub.gateway.filter;
743 |
744 | import com.nimbusds.jose.JWSObject;
745 |
746 | import org.slf4j.Logger;
747 | import org.slf4j.LoggerFactory;
748 | import org.springframework.cloud.gateway.filter.GatewayFilterChain;
749 | import org.springframework.cloud.gateway.filter.GlobalFilter;
750 | import org.springframework.core.Ordered;
751 | import org.springframework.http.server.reactive.ServerHttpRequest;
752 | import org.springframework.stereotype.Component;
753 | import org.springframework.web.server.ServerWebExchange;
754 |
755 | import java.text.ParseException;
756 |
757 | import cn.hutool.core.util.StrUtil;
758 | import reactor.core.publisher.Mono;
759 |
760 | /**
761 | * 将登录用户的JWT转化成用户信息的全局过滤器
762 | *
763 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
764 | */
765 | @Component
766 | public class AuthGlobalFilter implements GlobalFilter, Ordered {
767 |
768 | private final static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
769 |
770 | @Override
771 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
772 | String token = exchange.getRequest().getHeaders().getFirst("Authorization");
773 | if (StrUtil.isEmpty(token)) {
774 | return chain.filter(exchange);
775 | }
776 | try {
777 | // 从token中解析用户信息并设置到Header中去
778 | String realToken = token.replace("Bearer ", "");
779 | JWSObject jwsObject = JWSObject.parse(realToken);
780 | String userStr = jwsObject.getPayload().toString();
781 | LOGGER.info("AuthGlobalFilter.filter() user:{}", userStr);
782 | ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
783 | exchange = exchange.mutate().request(request).build();
784 | } catch (ParseException e) {
785 | e.printStackTrace();
786 | }
787 | return chain.filter(exchange);
788 | }
789 |
790 | @Override
791 | public int getOrder() {
792 | return 0;
793 | }
794 | }
795 |
796 | ```
797 |
798 | ### 三、资源服务(API服务)`oauth2-resource`
799 |
800 | 最后我们搭建一个API服务,它不会集成和实现任何安全相关逻辑,全靠网关来保护它
801 |
802 | > 1、在pom.xml中添加相关依赖,就添加了一个web依赖
803 |
804 | ```java
805 |
806 |
807 | org.springframework.boot
808 | spring-boot-starter-web
809 |
810 |
811 | ```
812 |
813 | > 2、在application.yml添加相关配置,很常规的配置
814 |
815 | ```yml
816 | server:
817 | port: 9501
818 | spring:
819 | profiles:
820 | active: dev
821 | application:
822 | name: oauth2-resource
823 | cloud:
824 | nacos:
825 | discovery:
826 | server-addr: localhost:8848
827 | management:
828 | endpoints:
829 | web:
830 | exposure:
831 | include: "*"
832 |
833 | ```
834 |
835 | > 3、创建一个测试接口,网关验证通过即可访问
836 |
837 | ```java
838 | package cn.gathub.resource.controller;
839 |
840 | import org.springframework.web.bind.annotation.GetMapping;
841 | import org.springframework.web.bind.annotation.RestController;
842 |
843 | /**
844 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
845 | */
846 | @RestController
847 | public class HelloController {
848 |
849 | @GetMapping("/hello")
850 | public String hello() {
851 | return "Hello World !";
852 | }
853 |
854 | }
855 |
856 | ```
857 |
858 | > 4、创建一个获取登录中的用户信息的接口,用于从请求的Header中直接获取登录用户信息
859 |
860 | ```java
861 | package cn.gathub.resource.controller;
862 |
863 |
864 | import org.springframework.web.bind.annotation.GetMapping;
865 | import org.springframework.web.bind.annotation.RequestMapping;
866 | import org.springframework.web.bind.annotation.RestController;
867 |
868 | import javax.servlet.http.HttpServletRequest;
869 |
870 | import cn.gathub.resource.domain.User;
871 | import cn.hutool.core.convert.Convert;
872 | import cn.hutool.json.JSONObject;
873 |
874 | /**
875 | * 获取登录用户信息接口
876 | *
877 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
878 | */
879 | @RestController
880 | @RequestMapping("/user")
881 | public class UserController {
882 |
883 | @GetMapping("/currentUser")
884 | public User currentUser(HttpServletRequest request) {
885 | // 从Header中获取用户信息
886 | String userStr = request.getHeader("user");
887 | JSONObject userJsonObject = new JSONObject(userStr);
888 | return User.builder()
889 | .username(userJsonObject.getStr("user_name"))
890 | .id(Convert.toLong(userJsonObject.get("id")))
891 | .roles(Convert.toList(String.class, userJsonObject.get("authorities"))).build();
892 | }
893 | }
894 |
895 | ```
896 |
897 | ## 功能演示
898 | 在此之前先启动我们的 Nacos 和 Redis 服务,然后依次启动`oauth2-auth`、`oauth2-gateway`及`oauth2-api`服务
899 |
900 | 我这里测试使用的 Docker 跑的单机版的 Nacos
901 | ```shell
902 | docker pull nacos/nacos-server
903 | docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server
904 | ```
905 | > 1、使用密码模式获取JWT令牌,访问地址:http://localhost:9201/oauth/token
906 |
907 | 
908 |
909 | > 2、使用获取到的JWT令牌访问需要权限的接口,访问地址:http://localhost:9201/resource/hello
910 |
911 | 
912 |
913 | > 3、使用获取到的JWT令牌访问获取当前登录用户信息的接口,访问地址:http://localhost:9201/resource/user/currentUser
914 |
915 | 
916 |
917 | > 4、当token不存在时,访问地址:http://localhost:9201/resource/user/currentUser
918 |
919 | 
920 |
921 | > 5、当JWT令牌过期时,使用refresh_token获取新的JWT令牌,访问地址:http://localhost:9201/oauth/token
922 |
923 | 
924 |
925 | > 6、使用授码模式登录时,先访问地址获取授权码:http://localhost:9201/oauth/authorize?response_type=code&client_id=client-app-2&redirect_uri=https://www.baidu.com
926 |
927 | > 7、访问地址,跳转登录页面
928 |
929 | 
930 |
931 | > 8、登录成功,进入授权页面
932 |
933 | 
934 |
935 | > 9、通过授权,拿到授权码
936 |
937 | 
938 |
939 | > 10、拿到授权码,访问地址登录:http://localhost:9201/oauth/token
940 |
941 | 
942 |
943 | > 11、使用没有访问权限的`user`账号登录,访问接口时会返回如下信息,访问地址:http://localhost:9201/resource/hello
944 |
945 | 
946 |
947 |
948 | ## 项目源码地址
949 | https://github.com/it-wwh/spring-cloud-gateway-oauth2
950 | ## 公众号
951 | 
952 |
953 |
954 |
955 |
956 |
957 |
958 |
--------------------------------------------------------------------------------
/oauth2-auth/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | gathub-oauth2
7 | cn.gathub
8 | 1.0.0
9 |
10 | 4.0.0
11 |
12 | oauth2-auth
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-security
22 |
23 |
24 | org.springframework.cloud
25 | spring-cloud-starter-oauth2
26 |
27 |
28 | com.nimbusds
29 | nimbus-jose-jwt
30 | 8.16
31 |
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-data-redis
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/Oauth2AuthApplication.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6 |
7 | /**
8 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
9 | */
10 | @EnableDiscoveryClient
11 | @SpringBootApplication
12 | public class Oauth2AuthApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(Oauth2AuthApplication.class, args);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/api/CommonResult.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.api;
2 |
3 | /**
4 | * 通用返回对象
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public class CommonResult {
9 | private long code;
10 | private String message;
11 | private T data;
12 |
13 | protected CommonResult() {
14 | }
15 |
16 | protected CommonResult(long code, String message, T data) {
17 | this.code = code;
18 | this.message = message;
19 | this.data = data;
20 | }
21 |
22 | /**
23 | * 成功返回结果
24 | *
25 | * @param data 获取的数据
26 | */
27 | public static CommonResult success(T data) {
28 | return new CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
29 | }
30 |
31 | /**
32 | * 成功返回结果
33 | *
34 | * @param data 获取的数据
35 | * @param message 提示信息
36 | */
37 | public static CommonResult success(T data, String message) {
38 | return new CommonResult(ResultCode.SUCCESS.getCode(), message, data);
39 | }
40 |
41 | /**
42 | * 失败返回结果
43 | *
44 | * @param errorCode 错误码
45 | */
46 | public static CommonResult failed(IErrorCode errorCode) {
47 | return new CommonResult(errorCode.getCode(), errorCode.getMessage(), null);
48 | }
49 |
50 | /**
51 | * 失败返回结果
52 | *
53 | * @param errorCode 错误码
54 | * @param message 错误信息
55 | */
56 | public static CommonResult failed(IErrorCode errorCode, String message) {
57 | return new CommonResult(errorCode.getCode(), message, null);
58 | }
59 |
60 | /**
61 | * 失败返回结果
62 | *
63 | * @param message 提示信息
64 | */
65 | public static CommonResult failed(String message) {
66 | return new CommonResult(ResultCode.FAILED.getCode(), message, null);
67 | }
68 |
69 | /**
70 | * 失败返回结果
71 | */
72 | public static CommonResult failed() {
73 | return failed(ResultCode.FAILED);
74 | }
75 |
76 | /**
77 | * 参数验证失败返回结果
78 | */
79 | public static CommonResult validateFailed() {
80 | return failed(ResultCode.VALIDATE_FAILED);
81 | }
82 |
83 | /**
84 | * 参数验证失败返回结果
85 | *
86 | * @param message 提示信息
87 | */
88 | public static CommonResult validateFailed(String message) {
89 | return new CommonResult(ResultCode.VALIDATE_FAILED.getCode(), message, null);
90 | }
91 |
92 | /**
93 | * 未登录返回结果
94 | */
95 | public static CommonResult unauthorized(T data) {
96 | return new CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
97 | }
98 |
99 | /**
100 | * 未授权返回结果
101 | */
102 | public static CommonResult forbidden(T data) {
103 | return new CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
104 | }
105 |
106 | public long getCode() {
107 | return code;
108 | }
109 |
110 | public void setCode(long code) {
111 | this.code = code;
112 | }
113 |
114 | public String getMessage() {
115 | return message;
116 | }
117 |
118 | public void setMessage(String message) {
119 | this.message = message;
120 | }
121 |
122 | public T getData() {
123 | return data;
124 | }
125 |
126 | public void setData(T data) {
127 | this.data = data;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/api/IErrorCode.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.api;
2 |
3 | /**
4 | * 封装API的错误码
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public interface IErrorCode {
9 | long getCode();
10 |
11 | String getMessage();
12 | }
13 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/api/ResultCode.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.api;
2 |
3 | /**
4 | * 枚举了一些常用API操作码
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public enum ResultCode implements IErrorCode {
9 | SUCCESS(200, "操作成功"),
10 | FAILED(500, "操作失败"),
11 | VALIDATE_FAILED(404, "参数检验失败"),
12 | UNAUTHORIZED(401, "暂未登录或token已经过期"),
13 | FORBIDDEN(403, "没有相关权限");
14 | private final long code;
15 | private final String message;
16 |
17 | ResultCode(long code, String message) {
18 | this.code = code;
19 | this.message = message;
20 | }
21 |
22 | public long getCode() {
23 | return code;
24 | }
25 |
26 | public String getMessage() {
27 | return message;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/component/JwtTokenEnhancer.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.component;
2 |
3 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
4 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
5 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
6 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | import cn.gathub.auth.service.principal.UserPrincipal;
13 |
14 |
15 | /**
16 | * JWT内容增强器
17 | *
18 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
19 | */
20 | @Component
21 | public class JwtTokenEnhancer implements TokenEnhancer {
22 | @Override
23 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
24 | UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
25 | Map info = new HashMap<>();
26 | // 把用户ID设置到JWT中
27 | info.put("id", userPrincipal.getId());
28 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
29 | return accessToken;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/config/Oauth2ServerConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.core.io.ClassPathResource;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
8 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
9 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
10 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
11 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
12 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
13 | import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
14 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
15 | import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
16 |
17 | import java.security.KeyPair;
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import cn.gathub.auth.component.JwtTokenEnhancer;
22 | import cn.gathub.auth.service.ClientService;
23 | import cn.gathub.auth.service.UserService;
24 | import lombok.AllArgsConstructor;
25 |
26 | /**
27 | * 认证服务器配置
28 | *
29 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
30 | */
31 | @AllArgsConstructor
32 | @Configuration
33 | @EnableAuthorizationServer
34 | public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
35 |
36 | private final UserService userService;
37 | private final ClientService clientService;
38 | private final AuthenticationManager authenticationManager;
39 | private final JwtTokenEnhancer jwtTokenEnhancer;
40 |
41 | @Override
42 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
43 | // clients.inMemory()
44 | // // 1、密码模式
45 | // .withClient("client-app")
46 | // .secret(passwordEncoder.encode("123456"))
47 | // .scopes("read,write")
48 | // .authorizedGrantTypes("password", "refresh_token")
49 | // .accessTokenValiditySeconds(3600)
50 | // .refreshTokenValiditySeconds(86400)
51 | // .and()
52 | // // 2、授权码授权
53 | // .withClient("client-app-2")
54 | // .secret(passwordEncoder.encode("123456"))
55 | // .scopes("read")
56 | // .authorizedGrantTypes("authorization_code", "refresh_token")
57 | // .accessTokenValiditySeconds(3600)
58 | // .refreshTokenValiditySeconds(86400)
59 | // .redirectUris("https://www.gathub.cn", "https://www.baidu.com");
60 | clients.withClientDetails(clientService);
61 | }
62 |
63 | @Override
64 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
65 | TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
66 | List delegates = new ArrayList<>();
67 | delegates.add(jwtTokenEnhancer);
68 | delegates.add(accessTokenConverter());
69 | enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
70 | endpoints.authenticationManager(authenticationManager)
71 | .userDetailsService(userService) //配置加载用户信息的服务
72 | .accessTokenConverter(accessTokenConverter())
73 | .tokenEnhancer(enhancerChain);
74 | }
75 |
76 | @Override
77 | public void configure(AuthorizationServerSecurityConfigurer security) {
78 | security.allowFormAuthenticationForClients();
79 | }
80 |
81 | @Bean
82 | public JwtAccessTokenConverter accessTokenConverter() {
83 | JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
84 | jwtAccessTokenConverter.setKeyPair(keyPair());
85 | return jwtAccessTokenConverter;
86 | }
87 |
88 | @Bean
89 | public KeyPair keyPair() {
90 | // 从classpath下的证书中获取秘钥对
91 | KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "654321".toCharArray());
92 | return keyStoreKeyFactory.getKeyPair("jwt", "654321".toCharArray());
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/config/RedisRepositoryConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.redis.connection.RedisConnectionFactory;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
8 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
9 | import org.springframework.data.redis.serializer.StringRedisSerializer;
10 |
11 | /**
12 | * Redis相关配置
13 | *
14 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
15 | */
16 | @Configuration
17 | @EnableRedisRepositories
18 | public class RedisRepositoryConfig {
19 |
20 | @Bean
21 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
22 | RedisTemplate redisTemplate = new RedisTemplate<>();
23 | redisTemplate.setConnectionFactory(connectionFactory);
24 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
25 | redisTemplate.setKeySerializer(stringRedisSerializer);
26 | redisTemplate.setHashKeySerializer(stringRedisSerializer);
27 | Jackson2JsonRedisSerializer> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
28 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
29 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
30 | redisTemplate.afterPropertiesSet();
31 | return redisTemplate;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/config/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.config;
2 |
3 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
11 | import org.springframework.security.crypto.password.PasswordEncoder;
12 |
13 | /**
14 | * SpringSecurity配置
15 | *
16 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
17 | */
18 | @Configuration
19 | @EnableWebSecurity
20 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
21 |
22 | @Override
23 | protected void configure(HttpSecurity http) throws Exception {
24 | http.authorizeRequests()
25 | .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
26 | .antMatchers("/rsa/publicKey").permitAll()
27 | .anyRequest().authenticated()
28 | .and().formLogin().permitAll();
29 | }
30 |
31 | @Bean
32 | @Override
33 | public AuthenticationManager authenticationManagerBean() throws Exception {
34 | return super.authenticationManagerBean();
35 | }
36 |
37 | @Bean
38 | public PasswordEncoder passwordEncoder() {
39 | return new BCryptPasswordEncoder();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/constant/MessageConstant.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.constant;
2 |
3 | /**
4 | * 消息常量
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public class MessageConstant {
9 |
10 | public static final String SPLIT_COMMA = ",";
11 | public static final String LOGIN_SUCCESS = "登录成功!";
12 | public static final String USERNAME_PASSWORD_ERROR = "用户名或密码错误!";
13 | public static final String CREDENTIALS_EXPIRED = "该账户的登录凭证已过期,请重新登录!";
14 | public static final String ACCOUNT_DISABLED = "该账户已被禁用,请联系管理员!";
15 | public static final String ACCOUNT_LOCKED = "该账号已被锁定,请联系管理员!";
16 | public static final String ACCOUNT_EXPIRED = "该账号已过期,请联系管理员!";
17 | public static final String PERMISSION_DENIED = "没有访问权限,请联系管理员!";
18 | public static final String NOT_FOUND_CLIENT = "未找到客户端!";
19 | }
20 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/constant/RedisConstant.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.constant;
2 |
3 | /**
4 | * Redis常量
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public class RedisConstant {
9 |
10 | public static final String RESOURCE_ROLES_MAP = "AUTH:RESOURCE_ROLES_MAP";
11 | }
12 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/controller/AuthController.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.controller;
2 |
3 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
4 | import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
5 | import org.springframework.web.HttpRequestMethodNotSupportedException;
6 | import org.springframework.web.bind.annotation.PostMapping;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import java.security.Principal;
12 | import java.util.Map;
13 | import java.util.Objects;
14 |
15 | import cn.gathub.auth.api.CommonResult;
16 | import cn.gathub.auth.domain.dto.Oauth2TokenDto;
17 |
18 | /**
19 | * 自定义Oauth2获取令牌接口
20 | *
21 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
22 | */
23 | @RestController
24 | @RequestMapping("/oauth")
25 | public class AuthController {
26 |
27 | private final TokenEndpoint tokenEndpoint;
28 |
29 | public AuthController(TokenEndpoint tokenEndpoint) {
30 | this.tokenEndpoint = tokenEndpoint;
31 | }
32 |
33 | /**
34 | * Oauth2登录认证
35 | */
36 | @PostMapping("/token")
37 | public CommonResult postAccessToken(Principal principal, @RequestParam Map parameters) throws HttpRequestMethodNotSupportedException {
38 | OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
39 | Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
40 | .token(Objects.requireNonNull(oAuth2AccessToken).getValue())
41 | .refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
42 | .expiresIn(oAuth2AccessToken.getExpiresIn())
43 | .tokenHead("Bearer ").build();
44 | return CommonResult.success(oauth2TokenDto);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/controller/KeyPairController.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.controller;
2 |
3 | import com.nimbusds.jose.jwk.JWKSet;
4 | import com.nimbusds.jose.jwk.RSAKey;
5 |
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | import java.security.KeyPair;
10 | import java.security.interfaces.RSAPublicKey;
11 | import java.util.Map;
12 |
13 | /**
14 | * 获取RSA公钥接口
15 | *
16 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
17 | */
18 | @RestController
19 | public class KeyPairController {
20 |
21 | private final KeyPair keyPair;
22 |
23 | public KeyPairController(KeyPair keyPair) {
24 | this.keyPair = keyPair;
25 | }
26 |
27 | @GetMapping("/rsa/publicKey")
28 | public Map getKey() {
29 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
30 | RSAKey key = new RSAKey.Builder(publicKey).build();
31 | return new JWKSet(key).toJSONObject();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/domain/dto/Oauth2TokenDto.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.domain.dto;
2 |
3 | import lombok.Builder;
4 | import lombok.Data;
5 | import lombok.EqualsAndHashCode;
6 |
7 | /**
8 | * Oauth2获取Token返回信息封装
9 | *
10 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
11 | */
12 | @Data
13 | @EqualsAndHashCode(callSuper = false)
14 | @Builder
15 | public class Oauth2TokenDto {
16 | /**
17 | * 访问令牌
18 | */
19 | private String token;
20 | /**
21 | * 刷新令牌
22 | */
23 | private String refreshToken;
24 | /**
25 | * 访问令牌头前缀
26 | */
27 | private String tokenHead;
28 | /**
29 | * 有效时间(秒)
30 | */
31 | private int expiresIn;
32 | }
33 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/domain/entity/Client.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.domain.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.EqualsAndHashCode;
7 |
8 | /**
9 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
10 | */
11 | @Data
12 | @EqualsAndHashCode(callSuper = false)
13 | @AllArgsConstructor
14 | @Builder(toBuilder = true)
15 | public class Client {
16 | private String clientId;
17 | private String resourceIds;
18 | private Boolean secretRequire;
19 | private String clientSecret;
20 | private Boolean scopeRequire;
21 | private String scope;
22 | private String authorizedGrantTypes;
23 | private String webServerRedirectUri;
24 | private String authorities;
25 | private Integer accessTokenValidity;
26 | private Integer refreshTokenValidity;
27 | }
28 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/domain/entity/User.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.domain.entity;
2 |
3 | import java.util.List;
4 |
5 | import lombok.AllArgsConstructor;
6 | import lombok.Builder;
7 | import lombok.Data;
8 | import lombok.EqualsAndHashCode;
9 | import lombok.NoArgsConstructor;
10 |
11 | /**
12 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
13 | */
14 | @Data
15 | @EqualsAndHashCode(callSuper = false)
16 | @AllArgsConstructor
17 | @NoArgsConstructor
18 | @Builder(toBuilder = true)
19 | public class User {
20 | private Long id;
21 | private String username;
22 | private String password;
23 | private Integer status;
24 | private List roles;
25 | }
26 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/exception/Oauth2ExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.exception;
2 |
3 | import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
4 | import org.springframework.web.bind.annotation.ControllerAdvice;
5 | import org.springframework.web.bind.annotation.ExceptionHandler;
6 | import org.springframework.web.bind.annotation.ResponseBody;
7 |
8 | import cn.gathub.auth.api.CommonResult;
9 |
10 |
11 | /**
12 | * 全局处理Oauth2抛出的异常
13 | *
14 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
15 | */
16 | @ControllerAdvice
17 | public class Oauth2ExceptionHandler {
18 | @ResponseBody
19 | @ExceptionHandler(value = OAuth2Exception.class)
20 | public CommonResult handleOauth2(OAuth2Exception e) {
21 | return CommonResult.failed(e.getMessage());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/ClientService.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service;
2 |
3 | import org.springframework.security.oauth2.provider.ClientDetailsService;
4 |
5 | /**
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/18
7 | */
8 | public interface ClientService extends ClientDetailsService {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/ResourceService.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service;
2 |
3 | /**
4 | * @author Honghui [wanghonghui_work@163.com] 2021/3/18
5 | */
6 | public interface ResourceService {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/UserService.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service;
2 |
3 | import org.springframework.security.core.userdetails.UserDetailsService;
4 |
5 | /**
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/18
7 | */
8 | public interface UserService extends UserDetailsService {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/impl/ClientServiceImpl.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service.impl;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.security.crypto.password.PasswordEncoder;
5 | import org.springframework.security.oauth2.provider.ClientDetails;
6 | import org.springframework.security.oauth2.provider.ClientRegistrationException;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.web.server.ResponseStatusException;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 |
14 | import javax.annotation.PostConstruct;
15 |
16 | import cn.gathub.auth.constant.MessageConstant;
17 | import cn.gathub.auth.domain.entity.Client;
18 | import cn.gathub.auth.service.ClientService;
19 | import cn.gathub.auth.service.principal.ClientPrincipal;
20 | import cn.hutool.core.collection.CollUtil;
21 |
22 | /**
23 | * 客户端管理业务类
24 | *
25 | * @author Honghui [wanghonghui_work@163.com] 2021/3/18
26 | */
27 | @Service
28 | public class ClientServiceImpl implements ClientService {
29 |
30 | private List clientList;
31 | private final PasswordEncoder passwordEncoder;
32 |
33 | public ClientServiceImpl(PasswordEncoder passwordEncoder) {
34 | this.passwordEncoder = passwordEncoder;
35 | }
36 |
37 | @PostConstruct
38 | public void initData() {
39 | String clientSecret = passwordEncoder.encode("123456");
40 | clientList = new ArrayList<>();
41 | // 1、密码模式
42 | clientList.add(Client.builder()
43 | .clientId("client-app")
44 | .resourceIds("oauth2-resource")
45 | .secretRequire(false)
46 | .clientSecret(clientSecret)
47 | .scopeRequire(false)
48 | .scope("all")
49 | .authorizedGrantTypes("password,refresh_token")
50 | .authorities("ADMIN,USER")
51 | .accessTokenValidity(3600)
52 | .refreshTokenValidity(86400).build());
53 | // 2、授权码模式
54 | clientList.add(Client.builder()
55 | .clientId("client-app-2")
56 | .resourceIds("oauth2-resource2")
57 | .secretRequire(false)
58 | .clientSecret(clientSecret)
59 | .scopeRequire(false)
60 | .scope("all")
61 | .authorizedGrantTypes("authorization_code,refresh_token")
62 | .webServerRedirectUri("https://www.gathub.cn,https://www.baidu.com")
63 | .authorities("USER")
64 | .accessTokenValidity(3600)
65 | .refreshTokenValidity(86400).build());
66 | }
67 |
68 | @Override
69 | public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
70 | List findClientList = clientList.stream().filter(item -> item.getClientId().equals(clientId)).collect(Collectors.toList());
71 | if (CollUtil.isEmpty(findClientList)) {
72 | throw new ResponseStatusException(HttpStatus.NOT_FOUND, MessageConstant.NOT_FOUND_CLIENT);
73 | }
74 | return new ClientPrincipal(findClientList.get(0));
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/impl/ResourceServiceImpl.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service.impl;
2 |
3 | import org.springframework.data.redis.core.RedisTemplate;
4 | import org.springframework.stereotype.Service;
5 |
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.TreeMap;
9 |
10 | import javax.annotation.PostConstruct;
11 |
12 | import cn.gathub.auth.constant.RedisConstant;
13 | import cn.gathub.auth.service.ResourceService;
14 | import cn.hutool.core.collection.CollUtil;
15 |
16 | /**
17 | * 资源与角色匹配关系管理业务类
18 | *
19 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
20 | */
21 | @Service
22 | public class ResourceServiceImpl implements ResourceService {
23 |
24 | private final RedisTemplate redisTemplate;
25 |
26 | public ResourceServiceImpl(RedisTemplate redisTemplate) {
27 | this.redisTemplate = redisTemplate;
28 | }
29 |
30 | @PostConstruct
31 | public void initData() {
32 | Map> resourceRolesMap = new TreeMap<>();
33 | resourceRolesMap.put("/resource/hello", CollUtil.toList("ADMIN"));
34 | resourceRolesMap.put("/resource/user/currentUser", CollUtil.toList("ADMIN", "USER"));
35 | redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, resourceRolesMap);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/impl/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service.impl;
2 |
3 | import org.springframework.security.authentication.AccountExpiredException;
4 | import org.springframework.security.authentication.CredentialsExpiredException;
5 | import org.springframework.security.authentication.DisabledException;
6 | import org.springframework.security.authentication.LockedException;
7 | import org.springframework.security.core.userdetails.UserDetails;
8 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
9 | import org.springframework.security.crypto.password.PasswordEncoder;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | import javax.annotation.PostConstruct;
17 |
18 | import cn.gathub.auth.constant.MessageConstant;
19 | import cn.gathub.auth.domain.entity.User;
20 | import cn.gathub.auth.service.UserService;
21 | import cn.gathub.auth.service.principal.UserPrincipal;
22 | import cn.hutool.core.collection.CollUtil;
23 |
24 | /**
25 | * 用户管理业务类
26 | *
27 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
28 | */
29 | @Service
30 | public class UserServiceImpl implements UserService {
31 |
32 | private List userList;
33 | private final PasswordEncoder passwordEncoder;
34 |
35 | public UserServiceImpl(PasswordEncoder passwordEncoder) {
36 | this.passwordEncoder = passwordEncoder;
37 | }
38 |
39 | @PostConstruct
40 | public void initData() {
41 | String password = passwordEncoder.encode("123456");
42 | userList = new ArrayList<>();
43 | userList.add(new User(1L, "admin", password, 1, CollUtil.toList("ADMIN")));
44 | userList.add(new User(2L, "user", password, 1, CollUtil.toList("USER")));
45 | }
46 |
47 | @Override
48 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
49 | List findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
50 | if (CollUtil.isEmpty(findUserList)) {
51 | throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
52 | }
53 | UserPrincipal userPrincipal = new UserPrincipal(findUserList.get(0));
54 | if (!userPrincipal.isEnabled()) {
55 | throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
56 | } else if (!userPrincipal.isAccountNonLocked()) {
57 | throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
58 | } else if (!userPrincipal.isAccountNonExpired()) {
59 | throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
60 | } else if (!userPrincipal.isCredentialsNonExpired()) {
61 | throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
62 | }
63 | return userPrincipal;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/principal/ClientPrincipal.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service.principal;
2 |
3 | import org.springframework.security.core.GrantedAuthority;
4 | import org.springframework.security.oauth2.provider.ClientDetails;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.HashSet;
10 | import java.util.Map;
11 | import java.util.Set;
12 |
13 | import cn.gathub.auth.constant.MessageConstant;
14 | import cn.gathub.auth.domain.entity.Client;
15 | import lombok.Data;
16 |
17 | /**
18 | * 登录用户信息
19 | *
20 | * @author Honghui [wanghonghui_work@163.com] 2021/3/19
21 | */
22 | @Data
23 | public class ClientPrincipal implements ClientDetails {
24 |
25 | private Client client;
26 |
27 | public ClientPrincipal(Client client) {
28 | this.client = client;
29 | }
30 |
31 | @Override
32 | public String getClientId() {
33 | return client.getClientId();
34 | }
35 |
36 | @Override
37 | public Set getResourceIds() {
38 | return new HashSet<>(Arrays.asList(client.getResourceIds().split(MessageConstant.SPLIT_COMMA)));
39 | }
40 |
41 | @Override
42 | public boolean isSecretRequired() {
43 | return client.getSecretRequire();
44 | }
45 |
46 | @Override
47 | public String getClientSecret() {
48 | return client.getClientSecret();
49 | }
50 |
51 | @Override
52 | public boolean isScoped() {
53 | return client.getScopeRequire();
54 | }
55 |
56 | @Override
57 | public Set getScope() {
58 | return new HashSet<>(Arrays.asList(client.getScope().split(MessageConstant.SPLIT_COMMA)));
59 | }
60 |
61 | @Override
62 | public Set getAuthorizedGrantTypes() {
63 | return new HashSet<>(Arrays.asList(client.getAuthorizedGrantTypes().split(MessageConstant.SPLIT_COMMA)));
64 | }
65 |
66 | @Override
67 | public Set getRegisteredRedirectUri() {
68 | return new HashSet<>(Arrays.asList(client.getWebServerRedirectUri().split(MessageConstant.SPLIT_COMMA)));
69 | }
70 |
71 | @Override
72 | public Collection getAuthorities() {
73 | Collection collection = new ArrayList<>();
74 | Arrays.asList(client.getAuthorities().split(MessageConstant.SPLIT_COMMA)).forEach(
75 | auth -> collection.add((GrantedAuthority) () -> auth)
76 | );
77 | return collection;
78 | }
79 |
80 | @Override
81 | public Integer getAccessTokenValiditySeconds() {
82 | return client.getAccessTokenValidity();
83 | }
84 |
85 | @Override
86 | public Integer getRefreshTokenValiditySeconds() {
87 | return client.getRefreshTokenValidity();
88 | }
89 |
90 | @Override
91 | public boolean isAutoApprove(String s) {
92 | return false;
93 | }
94 |
95 | @Override
96 | public Map getAdditionalInformation() {
97 | return null;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/java/cn/gathub/auth/service/principal/UserPrincipal.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.auth.service.principal;
2 |
3 | import org.springframework.security.core.GrantedAuthority;
4 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 |
10 | import cn.gathub.auth.domain.entity.User;
11 | import lombok.Data;
12 |
13 | /**
14 | * 登录用户信息
15 | *
16 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
17 | */
18 | @Data
19 | public class UserPrincipal implements UserDetails {
20 |
21 | /**
22 | * ID
23 | */
24 | private Long id;
25 | /**
26 | * 用户名
27 | */
28 | private String username;
29 | /**
30 | * 用户密码
31 | */
32 | private String password;
33 | /**
34 | * 用户状态
35 | */
36 | private Boolean enabled;
37 | /**
38 | * 权限数据
39 | */
40 | private Collection authorities;
41 |
42 | public UserPrincipal(User user) {
43 | this.setId(user.getId());
44 | this.setUsername(user.getUsername());
45 | this.setPassword(user.getPassword());
46 | this.setEnabled(user.getStatus() == 1);
47 | if (user.getRoles() != null) {
48 | authorities = new ArrayList<>();
49 | user.getRoles().forEach(item -> authorities.add(new SimpleGrantedAuthority(item)));
50 | }
51 | }
52 |
53 | @Override
54 | public Collection extends GrantedAuthority> getAuthorities() {
55 | return this.authorities;
56 | }
57 |
58 | @Override
59 | public String getPassword() {
60 | return this.password;
61 | }
62 |
63 | @Override
64 | public String getUsername() {
65 | return this.username;
66 | }
67 |
68 | @Override
69 | public boolean isAccountNonExpired() {
70 | return true;
71 | }
72 |
73 | @Override
74 | public boolean isAccountNonLocked() {
75 | return true;
76 | }
77 |
78 | @Override
79 | public boolean isCredentialsNonExpired() {
80 | return true;
81 | }
82 |
83 | @Override
84 | public boolean isEnabled() {
85 | return this.enabled;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 9401
3 | spring:
4 | profiles:
5 | active: dev
6 | application:
7 | name: oauth2-auth
8 | cloud:
9 | nacos:
10 | discovery:
11 | server-addr: localhost:8848
12 | jackson:
13 | date-format: yyyy-MM-dd HH:mm:ss
14 | redis:
15 | database: 0
16 | port: 6379
17 | host: localhost
18 | password:
19 | management:
20 | endpoints:
21 | web:
22 | exposure:
23 | include: "*"
24 |
--------------------------------------------------------------------------------
/oauth2-auth/src/main/resources/jwt.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/it-honghui/spring-cloud-gateway-oauth2/1afb5590df8b3726a22036ef39fbf7c87ac11fb6/oauth2-auth/src/main/resources/jwt.jks
--------------------------------------------------------------------------------
/oauth2-gateway/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | gathub-oauth2
7 | cn.gathub
8 | 1.0.0
9 |
10 | 4.0.0
11 |
12 | oauth2-gateway
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-webflux
18 |
19 |
20 | org.springframework.cloud
21 | spring-cloud-starter-gateway
22 |
23 |
24 | org.springframework.security
25 | spring-security-config
26 |
27 |
28 | org.springframework.security
29 | spring-security-oauth2-resource-server
30 |
31 |
32 | org.springframework.security
33 | spring-security-oauth2-client
34 |
35 |
36 | org.springframework.security
37 | spring-security-oauth2-jose
38 |
39 |
40 | com.nimbusds
41 | nimbus-jose-jwt
42 | 8.16
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-data-redis
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-configuration-processor
53 | true
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/Oauth2GatewayApplication.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6 |
7 | /**
8 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
9 | */
10 | @EnableDiscoveryClient
11 | @SpringBootApplication
12 | public class Oauth2GatewayApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(Oauth2GatewayApplication.class, args);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/api/CommonResult.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.api;
2 |
3 | /**
4 | * 通用返回对象
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public class CommonResult {
9 | private long code;
10 | private String message;
11 | private T data;
12 |
13 | protected CommonResult() {
14 | }
15 |
16 | protected CommonResult(long code, String message, T data) {
17 | this.code = code;
18 | this.message = message;
19 | this.data = data;
20 | }
21 |
22 | /**
23 | * 成功返回结果
24 | *
25 | * @param data 获取的数据
26 | */
27 | public static CommonResult success(T data) {
28 | return new CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
29 | }
30 |
31 | /**
32 | * 成功返回结果
33 | *
34 | * @param data 获取的数据
35 | * @param message 提示信息
36 | */
37 | public static CommonResult success(T data, String message) {
38 | return new CommonResult(ResultCode.SUCCESS.getCode(), message, data);
39 | }
40 |
41 | /**
42 | * 失败返回结果
43 | *
44 | * @param errorCode 错误码
45 | */
46 | public static CommonResult failed(IErrorCode errorCode) {
47 | return new CommonResult(errorCode.getCode(), errorCode.getMessage(), null);
48 | }
49 |
50 | /**
51 | * 失败返回结果
52 | *
53 | * @param errorCode 错误码
54 | * @param message 错误信息
55 | */
56 | public static CommonResult failed(IErrorCode errorCode, String message) {
57 | return new CommonResult(errorCode.getCode(), message, null);
58 | }
59 |
60 | /**
61 | * 失败返回结果
62 | *
63 | * @param message 提示信息
64 | */
65 | public static CommonResult failed(String message) {
66 | return new CommonResult(ResultCode.FAILED.getCode(), message, null);
67 | }
68 |
69 | /**
70 | * 失败返回结果
71 | */
72 | public static CommonResult failed() {
73 | return failed(ResultCode.FAILED);
74 | }
75 |
76 | /**
77 | * 参数验证失败返回结果
78 | */
79 | public static CommonResult validateFailed() {
80 | return failed(ResultCode.VALIDATE_FAILED);
81 | }
82 |
83 | /**
84 | * 参数验证失败返回结果
85 | *
86 | * @param message 提示信息
87 | */
88 | public static CommonResult validateFailed(String message) {
89 | return new CommonResult(ResultCode.VALIDATE_FAILED.getCode(), message, null);
90 | }
91 |
92 | /**
93 | * 未登录返回结果
94 | */
95 | public static CommonResult unauthorized(T data) {
96 | return new CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
97 | }
98 |
99 | /**
100 | * 未授权返回结果
101 | */
102 | public static CommonResult forbidden(T data) {
103 | return new CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
104 | }
105 |
106 | public long getCode() {
107 | return code;
108 | }
109 |
110 | public void setCode(long code) {
111 | this.code = code;
112 | }
113 |
114 | public String getMessage() {
115 | return message;
116 | }
117 |
118 | public void setMessage(String message) {
119 | this.message = message;
120 | }
121 |
122 | public T getData() {
123 | return data;
124 | }
125 |
126 | public void setData(T data) {
127 | this.data = data;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/api/IErrorCode.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.api;
2 |
3 | /**
4 | * 封装API的错误码
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public interface IErrorCode {
9 | long getCode();
10 |
11 | String getMessage();
12 | }
13 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/api/ResultCode.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.api;
2 |
3 | /**
4 | * 枚举了一些常用API操作码
5 | *
6 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
7 | */
8 | public enum ResultCode implements IErrorCode {
9 | SUCCESS(200, "操作成功"),
10 | FAILED(500, "操作失败"),
11 | VALIDATE_FAILED(404, "参数检验失败"),
12 | UNAUTHORIZED(401, "暂未登录或token已经过期"),
13 | FORBIDDEN(403, "没有相关权限");
14 | private final long code;
15 | private final String message;
16 |
17 | ResultCode(long code, String message) {
18 | this.code = code;
19 | this.message = message;
20 | }
21 |
22 | public long getCode() {
23 | return code;
24 | }
25 |
26 | public String getMessage() {
27 | return message;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/authorization/AuthorizationManager.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.authorization;
2 |
3 |
4 | import org.springframework.data.redis.core.RedisTemplate;
5 | import org.springframework.security.authorization.AuthorizationDecision;
6 | import org.springframework.security.authorization.ReactiveAuthorizationManager;
7 | import org.springframework.security.core.Authentication;
8 | import org.springframework.security.core.GrantedAuthority;
9 | import org.springframework.security.web.server.authorization.AuthorizationContext;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.net.URI;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | import cn.gathub.gateway.constant.AuthConstant;
17 | import cn.gathub.gateway.constant.RedisConstant;
18 | import cn.hutool.core.convert.Convert;
19 | import reactor.core.publisher.Mono;
20 |
21 | /**
22 | * 鉴权管理器,用于判断是否有资源的访问权限
23 | *
24 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
25 | */
26 | @Component
27 | public class AuthorizationManager implements ReactiveAuthorizationManager {
28 | private final RedisTemplate redisTemplate;
29 |
30 | public AuthorizationManager(RedisTemplate redisTemplate) {
31 | this.redisTemplate = redisTemplate;
32 | }
33 |
34 | @Override
35 | public Mono check(Mono mono, AuthorizationContext authorizationContext) {
36 | // 1、从Redis中获取当前路径可访问角色列表
37 | URI uri = authorizationContext.getExchange().getRequest().getURI();
38 | Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());
39 | List authorities = Convert.toList(String.class, obj);
40 | authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
41 | // 2、认证通过且角色匹配的用户可访问当前路径
42 | return mono
43 | .filter(Authentication::isAuthenticated)
44 | .flatMapIterable(Authentication::getAuthorities)
45 | .map(GrantedAuthority::getAuthority)
46 | .any(authorities::contains)
47 | .map(AuthorizationDecision::new)
48 | .defaultIfEmpty(new AuthorizationDecision(false));
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/component/RestAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.component;
2 |
3 | import org.springframework.core.io.buffer.DataBuffer;
4 | import org.springframework.http.HttpHeaders;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.http.server.reactive.ServerHttpResponse;
8 | import org.springframework.security.core.AuthenticationException;
9 | import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
10 | import org.springframework.stereotype.Component;
11 | import org.springframework.web.server.ServerWebExchange;
12 |
13 | import java.nio.charset.StandardCharsets;
14 |
15 | import cn.gathub.gateway.api.CommonResult;
16 | import cn.hutool.json.JSONUtil;
17 | import reactor.core.publisher.Mono;
18 |
19 | /**
20 | * 自定义返回结果:没有登录或token过期时
21 | *
22 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
23 | */
24 | @Component
25 | public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
26 | @Override
27 | public Mono commence(ServerWebExchange exchange, AuthenticationException e) {
28 | ServerHttpResponse response = exchange.getResponse();
29 | response.setStatusCode(HttpStatus.UNAUTHORIZED);
30 | response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
31 | String body = JSONUtil.toJsonStr(CommonResult.unauthorized(e.getMessage()));
32 | DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
33 | return response.writeWith(Mono.just(buffer));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/component/RestfulAccessDeniedHandler.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.component;
2 |
3 | import org.springframework.core.io.buffer.DataBuffer;
4 | import org.springframework.http.HttpHeaders;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.http.server.reactive.ServerHttpResponse;
8 | import org.springframework.security.access.AccessDeniedException;
9 | import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
10 | import org.springframework.stereotype.Component;
11 | import org.springframework.web.server.ServerWebExchange;
12 |
13 | import java.nio.charset.StandardCharsets;
14 |
15 | import cn.gathub.gateway.api.CommonResult;
16 | import cn.hutool.json.JSONUtil;
17 | import reactor.core.publisher.Mono;
18 |
19 | /**
20 | * 自定义返回结果:没有权限访问时
21 | *
22 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
23 | */
24 | @Component
25 | public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
26 | @Override
27 | public Mono handle(ServerWebExchange exchange, AccessDeniedException denied) {
28 | ServerHttpResponse response = exchange.getResponse();
29 | response.setStatusCode(HttpStatus.FORBIDDEN);
30 | response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
31 | String body = JSONUtil.toJsonStr(CommonResult.forbidden(denied.getMessage()));
32 | DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
33 | return response.writeWith(Mono.just(buffer));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/config/IgnoreUrlsConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | import java.util.List;
7 |
8 | import lombok.Data;
9 | import lombok.EqualsAndHashCode;
10 |
11 | /**
12 | * 网关白名单配置
13 | *
14 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
15 | */
16 | @Data
17 | @EqualsAndHashCode(callSuper = false)
18 | @Component
19 | @ConfigurationProperties(prefix = "secure.ignore")
20 | public class IgnoreUrlsConfig {
21 | private List urls;
22 | }
23 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/config/RedisRepositoryConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.redis.connection.RedisConnectionFactory;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
8 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
9 | import org.springframework.data.redis.serializer.StringRedisSerializer;
10 |
11 | /**
12 | * Redis相关配置
13 | *
14 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
15 | */
16 | @Configuration
17 | @EnableRedisRepositories
18 | public class RedisRepositoryConfig {
19 |
20 | @Bean
21 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
22 | RedisTemplate redisTemplate = new RedisTemplate<>();
23 | redisTemplate.setConnectionFactory(connectionFactory);
24 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
25 | redisTemplate.setKeySerializer(stringRedisSerializer);
26 | redisTemplate.setHashKeySerializer(stringRedisSerializer);
27 | Jackson2JsonRedisSerializer> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
28 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
29 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
30 | redisTemplate.afterPropertiesSet();
31 | return redisTemplate;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/config/ResourceServerConfig.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.config;
2 |
3 |
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.core.convert.converter.Converter;
7 | import org.springframework.security.authentication.AbstractAuthenticationToken;
8 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
9 | import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
10 | import org.springframework.security.config.web.server.ServerHttpSecurity;
11 | import org.springframework.security.oauth2.jwt.Jwt;
12 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
13 | import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
14 | import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
15 | import org.springframework.security.web.server.SecurityWebFilterChain;
16 |
17 | import cn.gathub.gateway.authorization.AuthorizationManager;
18 | import cn.gathub.gateway.component.RestAuthenticationEntryPoint;
19 | import cn.gathub.gateway.component.RestfulAccessDeniedHandler;
20 | import cn.gathub.gateway.constant.AuthConstant;
21 | import cn.gathub.gateway.filter.IgnoreUrlsRemoveJwtFilter;
22 | import cn.hutool.core.util.ArrayUtil;
23 | import lombok.AllArgsConstructor;
24 | import reactor.core.publisher.Mono;
25 |
26 | /**
27 | * 资源服务器配置
28 | *
29 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
30 | */
31 | @AllArgsConstructor
32 | @Configuration
33 | @EnableWebFluxSecurity
34 | public class ResourceServerConfig {
35 | private final AuthorizationManager authorizationManager;
36 | private final IgnoreUrlsConfig ignoreUrlsConfig;
37 | private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
38 | private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
39 | private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
40 |
41 | @Bean
42 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
43 | http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
44 | // 1、自定义处理JWT请求头过期或签名错误的结果
45 | http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
46 | // 2、对白名单路径,直接移除JWT请求头
47 | http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
48 | http.authorizeExchange()
49 | .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(), String.class)).permitAll() // 白名单配置
50 | .anyExchange().access(authorizationManager) // 鉴权管理器配置
51 | .and().exceptionHandling()
52 | .accessDeniedHandler(restfulAccessDeniedHandler) // 处理未授权
53 | .authenticationEntryPoint(restAuthenticationEntryPoint) // 处理未认证
54 | .and().csrf().disable();
55 | return http.build();
56 | }
57 |
58 | @Bean
59 | public Converter> jwtAuthenticationConverter() {
60 | JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
61 | jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
62 | jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
63 | JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
64 | jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
65 | return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/constant/AuthConstant.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.constant;
2 |
3 | /**
4 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
5 | */
6 | public class AuthConstant {
7 |
8 | public static final String AUTHORITY_PREFIX = "ROLE_";
9 | public static final String AUTHORITY_CLAIM_NAME = "authorities";
10 | }
11 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/constant/RedisConstant.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.constant;
2 |
3 | /**
4 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
5 | */
6 | public class RedisConstant {
7 |
8 | public static final String RESOURCE_ROLES_MAP = "AUTH:RESOURCE_ROLES_MAP";
9 | }
10 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/filter/AuthGlobalFilter.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.filter;
2 |
3 | import com.nimbusds.jose.JWSObject;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.cloud.gateway.filter.GatewayFilterChain;
8 | import org.springframework.cloud.gateway.filter.GlobalFilter;
9 | import org.springframework.core.Ordered;
10 | import org.springframework.http.server.reactive.ServerHttpRequest;
11 | import org.springframework.stereotype.Component;
12 | import org.springframework.web.server.ServerWebExchange;
13 |
14 | import java.text.ParseException;
15 |
16 | import cn.hutool.core.util.StrUtil;
17 | import reactor.core.publisher.Mono;
18 |
19 | /**
20 | * 将登录用户的JWT转化成用户信息的全局过滤器
21 | *
22 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
23 | */
24 | @Component
25 | public class AuthGlobalFilter implements GlobalFilter, Ordered {
26 |
27 | private final static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
28 |
29 | @Override
30 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
31 | String token = exchange.getRequest().getHeaders().getFirst("Authorization");
32 | if (StrUtil.isEmpty(token)) {
33 | return chain.filter(exchange);
34 | }
35 | try {
36 | // 从token中解析用户信息并设置到Header中去
37 | String realToken = token.replace("Bearer ", "");
38 | JWSObject jwsObject = JWSObject.parse(realToken);
39 | String userStr = jwsObject.getPayload().toString();
40 | LOGGER.info("AuthGlobalFilter.filter() user:{}", userStr);
41 | ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
42 | exchange = exchange.mutate().request(request).build();
43 | } catch (ParseException e) {
44 | e.printStackTrace();
45 | }
46 | return chain.filter(exchange);
47 | }
48 |
49 | @Override
50 | public int getOrder() {
51 | return 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/java/cn/gathub/gateway/filter/IgnoreUrlsRemoveJwtFilter.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.gateway.filter;
2 |
3 | import org.springframework.http.server.reactive.ServerHttpRequest;
4 | import org.springframework.stereotype.Component;
5 | import org.springframework.util.AntPathMatcher;
6 | import org.springframework.util.PathMatcher;
7 | import org.springframework.web.server.ServerWebExchange;
8 | import org.springframework.web.server.WebFilter;
9 | import org.springframework.web.server.WebFilterChain;
10 |
11 | import java.net.URI;
12 | import java.util.List;
13 |
14 | import cn.gathub.gateway.config.IgnoreUrlsConfig;
15 | import reactor.core.publisher.Mono;
16 |
17 | /**
18 | * 白名单路径访问时需要移除JWT请求头
19 | *
20 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
21 | */
22 | @Component
23 | public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
24 |
25 | private final IgnoreUrlsConfig ignoreUrlsConfig;
26 |
27 | public IgnoreUrlsRemoveJwtFilter(IgnoreUrlsConfig ignoreUrlsConfig) {
28 | this.ignoreUrlsConfig = ignoreUrlsConfig;
29 | }
30 |
31 | @Override
32 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
33 | ServerHttpRequest request = exchange.getRequest();
34 | URI uri = request.getURI();
35 | PathMatcher pathMatcher = new AntPathMatcher();
36 | // 白名单路径移除JWT请求头
37 | List ignoreUrls = ignoreUrlsConfig.getUrls();
38 | for (String ignoreUrl : ignoreUrls) {
39 | if (pathMatcher.match(ignoreUrl, uri.getPath())) {
40 | request = exchange.getRequest().mutate().header("Authorization", "").build();
41 | exchange = exchange.mutate().request(request).build();
42 | return chain.filter(exchange);
43 | }
44 | }
45 | return chain.filter(exchange);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/oauth2-gateway/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 9201
3 | spring:
4 | profiles:
5 | active: dev
6 | application:
7 | name: oauth2-gateway
8 | cloud:
9 | nacos:
10 | discovery:
11 | server-addr: localhost:8848
12 | gateway:
13 | routes: # 配置路由路径
14 | - id: oauth2-resource-route
15 | uri: lb://oauth2-resource
16 | predicates:
17 | - Path=/resource/**
18 | filters:
19 | - StripPrefix=1
20 | - id: oauth2-auth-route
21 | uri: lb://oauth2-auth
22 | predicates:
23 | - Path=/auth/**
24 | filters:
25 | - StripPrefix=1
26 | - id: oauth2-auth-login
27 | uri: lb://oauth2-auth
28 | predicates:
29 | - Path=/login
30 | filters:
31 | - PreserveHostHeader
32 | - id: oauth2-auth-token
33 | uri: lb://oauth2-auth
34 | predicates:
35 | - Path=/oauth/token
36 | filters:
37 | - PreserveHostHeader
38 | - id: oauth2-auth-authorize
39 | uri: lb://oauth2-auth
40 | predicates:
41 | - Path=/oauth/authorize
42 | filters:
43 | - PreserveHostHeader
44 | discovery:
45 | locator:
46 | enabled: true # 开启从注册中心动态创建路由的功能
47 | lower-case-service-id: true # 使用小写服务名,默认是大写
48 | security:
49 | oauth2:
50 | resourceserver:
51 | jwt:
52 | jwk-set-uri: 'http://localhost:9401/rsa/publicKey' # 配置RSA的公钥访问地址
53 | redis:
54 | database: 0
55 | port: 6379
56 | host: localhost
57 | password:
58 | secure:
59 | ignore:
60 | urls: # 配置白名单路径
61 | - "/actuator/**"
62 | - "/oauth/token"
63 | - "/oauth/authorize"
64 | - "/login"
65 |
--------------------------------------------------------------------------------
/oauth2-resource/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | gathub-oauth2
7 | cn.gathub
8 | 1.0.0
9 |
10 | 4.0.0
11 |
12 | oauth2-resource
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/oauth2-resource/src/main/java/cn/gathub/resource/Oauth2ResourceApplication.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.resource;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6 |
7 | /**
8 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
9 | */
10 | @EnableDiscoveryClient
11 | @SpringBootApplication
12 | public class Oauth2ResourceApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(Oauth2ResourceApplication.class, args);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/oauth2-resource/src/main/java/cn/gathub/resource/controller/HelloController.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.resource.controller;
2 |
3 | import org.springframework.web.bind.annotation.GetMapping;
4 | import org.springframework.web.bind.annotation.RestController;
5 |
6 | /**
7 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
8 | */
9 | @RestController
10 | public class HelloController {
11 |
12 | @GetMapping("/hello")
13 | public String hello() {
14 | return "Hello World !";
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/oauth2-resource/src/main/java/cn/gathub/resource/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.resource.controller;
2 |
3 |
4 | import org.springframework.web.bind.annotation.GetMapping;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RestController;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 |
10 | import cn.gathub.resource.domain.User;
11 | import cn.hutool.core.convert.Convert;
12 | import cn.hutool.core.lang.UUID;
13 | import cn.hutool.json.JSONObject;
14 |
15 | /**
16 | * 获取登录用户信息接口
17 | *
18 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
19 | */
20 | @RestController
21 | @RequestMapping("/user")
22 | public class UserController {
23 |
24 | @GetMapping("/currentUser")
25 | public User currentUser(HttpServletRequest request) {
26 | // 从Header中获取用户信息
27 | String userStr = request.getHeader("user");
28 | JSONObject userJsonObject = new JSONObject(userStr);
29 | return User.builder()
30 | .username(userJsonObject.getStr("user_name"))
31 | .id(Convert.toLong(userJsonObject.get("id")))
32 | .roles(Convert.toList(String.class, userJsonObject.get("authorities"))).build();
33 | }
34 |
35 | @GetMapping
36 | public JSONObject findUser(HttpServletRequest request) {
37 | // 从Header中获取用户信息
38 | String userStr = request.getHeader("user");
39 | return new JSONObject(userStr);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/oauth2-resource/src/main/java/cn/gathub/resource/domain/User.java:
--------------------------------------------------------------------------------
1 | package cn.gathub.resource.domain;
2 |
3 | import java.util.List;
4 |
5 | import lombok.Builder;
6 | import lombok.Data;
7 | import lombok.EqualsAndHashCode;
8 |
9 | /**
10 | * @author Honghui [wanghonghui_work@163.com] 2021/3/16
11 | */
12 | @Data
13 | @EqualsAndHashCode(callSuper = false)
14 | @Builder(toBuilder = true)
15 | public class User {
16 | private Long id;
17 | private String username;
18 | private String password;
19 | private List roles;
20 | }
21 |
--------------------------------------------------------------------------------
/oauth2-resource/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 9501
3 | spring:
4 | profiles:
5 | active: dev
6 | application:
7 | name: oauth2-resource
8 | cloud:
9 | nacos:
10 | discovery:
11 | server-addr: localhost:8848
12 | management:
13 | endpoints:
14 | web:
15 | exposure:
16 | include: "*"
17 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.gathub
8 | gathub-oauth2
9 | pom
10 | 1.0.0
11 |
12 |
13 | oauth2-gateway
14 | oauth2-auth
15 | oauth2-resource
16 |
17 |
18 |
19 | UTF-8
20 | 1.8
21 | 1.8
22 | 1.8
23 | 2.2.0.RELEASE
24 | Hoxton.SR5
25 | 2.3.0.RELEASE
26 | 5.3.5
27 |
28 |
29 |
30 |
31 |
32 | com.alibaba.cloud
33 | spring-cloud-alibaba-dependencies
34 | ${spring-cloud-alibaba.version}
35 | pom
36 | import
37 |
38 |
39 | org.springframework.cloud
40 | spring-cloud-dependencies
41 | ${spring-cloud.version}
42 | pom
43 | import
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-dependencies
48 | ${spring-boot.version}
49 | pom
50 | import
51 |
52 |
53 |
54 |
55 |
56 |
57 | com.alibaba.cloud
58 | spring-cloud-starter-alibaba-nacos-discovery
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-starter-actuator
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-starter-test
67 | test
68 |
69 |
70 | org.junit.vintage
71 | junit-vintage-engine
72 |
73 |
74 |
75 |
76 | org.projectlombok
77 | lombok
78 | true
79 |
80 |
81 | cn.hutool
82 | hutool-all
83 | ${hutool-version}
84 |
85 |
86 |
87 |
88 |
89 |
90 | org.springframework.boot
91 | spring-boot-maven-plugin
92 |
93 |
94 |
95 |
96 |
97 |
98 | spring-milestones
99 | Spring Milestones
100 | https://repo.spring.io/milestone
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------