前面介绍单体的security使用,SpringBoot实践(二十六):Security实现Vue-Element-Admin登录拦截(适合单体应用)_A叶子叶的博客-CSDN博客微服务与单体不同在于所有服务都要过网关,若使用gateway则更要考虑异构问题(security是基于MVC的,而gateway是基于webFlux的),因此在实现上把登录/授权相关功能放在同个工程,而token校验要放在网关中(依然使用security),结合oauth2实现授权模式。假设使用security框架的登录/授权工程叫login,在gateway进行token的拦截后回调login工程中的token校验接口,也就是说进行功能拆分,本文提供一种参考实现。
login工程的security配置userDetailsService是必须重写用于用户的校验,其他不配置。
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/oauth
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
login工程的oauth2配置
使用oauth2的token增强,重写clientDetailsService实现password授权模式;
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List delegates = new ArrayList<>();
delegates.add(tokenEnhancer());
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService) //配置加载用户信息的服务
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
@Bean
public TokenEnhancer tokenEnhancer() {
TokenEnhancer tokenEnhancer = (accessToken, authentication) -> {
Map additionalInfo = new HashMap<>();
Object principal = authentication.getUserAuthentication().getPrincipal();
if (principal instanceof SecurityUser) {
SecurityUser sysUserDetails = (SecurityUser) principal;
additionalInfo.put("userId", sysUserDetails.getId());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
return tokenEnhancer;
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
//从classpath下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
}
网关的授权模式
gateway只进行token的拦截,通过即放行。
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
private final ResourceServerManager resourceServerManager;
private final WhiteListConfig whiteListConfig;
private final RequestAccessDeniedHandler restfulAccessDeniedHandler;
private final RequestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
//.jwkSetUri() // 远程获取公钥,默认读取的key是spring.security.oauth2.resourceserver.jwt.jwk-set-uri
//自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//对白名单路径,直接移除JWT请求头
http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
http.authorizeExchange()
//白名单配置
.pathMatchers(whiteListConfig.getUrls().stream().toArray(String[]::new)).permitAll()
//鉴权管理器配置
.anyExchange()
.access(resourceServerManager)
.and()
.exceptionHandling()
//处理未授权
.accessDeniedHandler(restfulAccessDeniedHandler)
//处理未认证
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable();
return http.build();
}
@Bean
public Converter> jwtAuthenticationConverter()
{
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
综上,微服务架构实现的功能拆分是网关只当做授权客户端,login模块所有的登录/授权功能,假如不适用oauth2,也可以根据之前的单体security设置+自定义的网关拦截验证完成password模式。



