源码地址:看这里,看这里,看这里
gateway为微服务的网关,所有的访问服务的api都要通过网关转发到内网对应的服务。spring security、oauth2为认证和授权中心,负责整个微服务api的安全
版本说明:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.4.RELEASEversion>
parent>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR8version>
<type>pomtype>
<scope>importscope>
dependency>
认证中心,主要是用于用户的登陆认证,发放token
使用jdk的keytool生成jks,记住生成的时填入的密码
keytool -genkeypair
将生成的 jwt.jks 文件放到 resources 目录下
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>com.nimbusdsgroupId>
<artifactId>nimbus-jose-jwtartifactId>
<version>8.16version>
dependency>
下面展示一些 内联代码片
。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthProperties authProperties;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
// 配置的白名单
.antMatchers(ArrayUtil.toArray(authProperties.getWhiteList(), String.class)).permitAll()
.anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密
return new BCryptPasswordEncoder();
}
}
3.3 Oauth2ServerConfig 配置
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AuthProperties authProperties;
@Autowired
private UserDetailsService customUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 客户端访问方式配置数据在数据库中
clients.withClientDetails(customClientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService) //配置加载用户信息的服务
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
TokenProperties token = authProperties.getToken();
String jwtFileName = token.getJwtFileName();
String jwtPassword = token.getJwtPassword();
String alias = token.getAlias();
//从classpath下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtFileName), jwtPassword.toCharArray());
return keyStoreKeyFactory.getKeyPair(alias, jwtPassword.toCharArray());
}
@Bean
public ClientDetailsService customClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
}
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
//把用户ID设置到JWT中
info.put("id", securityUser.getId());
info.put("usercode",securityUser.getUsercode());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
public class CustomUserDetailsService implements UserDetailsService {
protected final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户的权限(我是写死的)
Set<GrantedAuthority> userAuthotities = new HashSet<>();
userAuthotities.add(new SimpleGrantedAuthority("query-demo"));
UserPo userPo = new UserPo("admin",passwordEncoder.encode("admin"));
// 注意如果在WebSecurityConfig.java 中使用的密文时,这个地方也要使用密文
return new SecurityUser(userPo , userAuthotities);
}
}
@RestController
public class KeyPairController {
@Autowired
private KeyPair keyPair;
/**
* 获取RSA公钥
*
* @return
*/
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
auth:
token:
alias: jwt
jwtFileName: jwt.jks
jwtPassword: 123456789
white-list:
- "/rsa/publicKey" # 获取公钥
资源中心主要于鉴定用户是否有权限访问该资源
@Slf4j
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
private final static String PERMISSION = RedisCst.ROLE_PERMISSION_KEY;
private final static String DICTIONARY = RedisCst.COMM_DICTIONARY_SYS_MODULE;
@Autowired
private RedisUtils redisUtils;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
//从Redis中获取当前路径可访问角色列表
URI uri = authorizationContext.getExchange().getRequest().getURI();
String targetURI = uri.getPath();
log.info("<<<=== targetURI:{}", targetURI);
// 获取 redis 中数据字典的数据
Map<String, String> dictionaryMap = redisUtils.getArray(DICTIONARY + DictionaryType.SYS_MC_SYSTEM_MODULE, DataDictionaryVo.class)
.stream()
.filter(f -> DictionaryType.SYS_FC_SYSTEM_MODULE.equals(f.getFieldType()))
.collect(Collectors.toMap(DataDictionaryVo::getFieldValue, DataDictionaryVo::getFieldDisplay));
//认证通过且角色匹配的用户可访问当前路径
return mono
.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority)
.any(role -> {
role = role.replace(AuthConstant.AUTHORITY_PREFIX, "");
List<PermissionVo> permissionVos = redisUtils.getArray(PERMISSION + role, PermissionVo.class);
if (permissionVos == null) {
return false;
}
for (PermissionVo permissionVo : permissionVos) {
if (ToolsUtils.isEmpty(permissionVo.getSystemModule())) {
String sourceURI = permissionVo.getUrl();
log.info("===>>> sourceURI:{}", sourceURI);
if (antPathMatcher.match(sourceURI, targetURI)) {
log.info("\n===>>> sourceURI:{},targetURI:{} <<<===\n", sourceURI, targetURI);
return true;
}
} else {
String[] split = permissionVo.getSystemModule().split(",");
for (String s : split) {
String sourceURI = "/" + dictionaryMap.get(s) + permissionVo.getUrl();
log.info("===>>> sourceURI:{}", sourceURI);
if (antPathMatcher.match(sourceURI, targetURI)) {
log.info("\n===>>> sourceURI:{},targetURI:{} <<<===\n", sourceURI, targetURI);
return true;
}
}
}
}
return false;
})
.map(AuthorizationDecision::new)
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
private final AuthorizationManager authorizationManager;
private final SecurityProperties securityProperties;
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
// 暂时不能用
// private final WhiteListRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// jwt 增加
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
//自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//对白名单路径,直接移除JWT请求头
// http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
// 白名单
List<String> whiteList = securityProperties.getWhiteList();
if (!CollectionUtils.isEmpty(whiteList)) {
//白名单配置
http.authorizeExchange().pathMatchers(ArrayUtil.toArray(whiteList, String.class)).permitAll();
}
// 要权限的
List<String> needCheck = securityProperties.getNeedCheck();
http.authorizeExchange()
.pathMatchers(ArrayUtil.toArray(needCheck, String.class))
.authenticated()
.anyExchange().access(authorizationManager)//鉴权管理器配置
.and().exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
.authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
.and().csrf().disable();
return http.build();
}
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
spring:
application:
name: learn-cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: public-auth
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://learn-shop-public-auth
predicates:
- Path=/public-auth/**
# 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
filters:
- StripPrefix=1
- id: admin-system
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://learn-shop-admin-system
predicates:
- Path=/admin-system/**
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 'http://localhost:8999/rsa/publicKey' # 获取公钥
# 自己定义安全配置
secure:
client: # 登陆客户端配置
client-id: webapp
client-secret: webapp
scope: webapp
grant-type: password
access-token-uri: "http://learn-shop-public-auth/oauth/token"
white-list:
- "/actuator/**" # 健康检查
- "/userApi/*" # 获取用户信息
- "/public-auth/oauth/token" # 获取token或者刷新token
need-check:
- "/**/*Api/**"
- "/*Api/**"
源码地址:看这里,看这里,看这里
测试请参考
Spring Cloud 之 Zuul、Spring Security、Oauth2 的整合