pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
TokenAuthentication
@Data
@EqualsAndHashCode(callSuper = true)
public class TokenAuthentication extends UsernamePasswordAuthenticationToken {
private final String token;
public TokenAuthentication(String token, Object principal, Object credentials) {
super(principal, credentials);
this.token = token;
}
public TokenAuthentication(String token, Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.token = token;
}
}
Config Class
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
//可换为redis存储
private final Map<String, SecurityContext> tokenCache = new ConcurrentHashMap<>();
private static final String BEARER = "Bearer ";
private static final String[] AUTH_WHITELIST = new String[]{"/login", "/actuator/**"};
@Bean
ReactiveAuthenticationManager reactiveAuthenticationManager() {
final ReactiveUserDetailsService detailsService = userDetailsService();
LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
managers.add(authentication -> {
//其他登陆方式(比如手机号验证码登陆)可在此设置不得抛出异常或者Mono.error
return Mono.empty();
});
//必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此AuthenticationManager会调用Mono.error造成后面的AuthenticationManager不生效
managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(detailsService));
return new DelegatingReactiveAuthenticationManager(managers);
}
@Bean
ServerSecurityContextRepository serverSecurityContextRepository() {
return new ServerSecurityContextRepository() {
@Override
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
if (context.getAuthentication() instanceof TokenAuthentication) {
TokenAuthentication authentication = (TokenAuthentication) context.getAuthentication();
tokenCache.put(authentication.getToken(), context);
}
return Mono.empty();
}
@Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isEmpty(authorization) || !tokenCache.containsKey(authorization)) {
return Mono.empty();
}
return Mono.just(tokenCache.get(authorization));
}
};
}
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers(AUTH_WHITELIST).permitAll()
.anyExchange().authenticated()
.and().build();
}
//修改为访问数据库的UserDetailsService即可
@Bean
ReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
@Bean
ServerAuthenticationConverter serverAuthenticationConverter() {
final AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
return exchange -> {
String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isEmpty(token)) {
return Mono.just(anonymous);
}
if (!token.startsWith(BEARER) || token.length() <= BEARER.length() || !tokenCache.containsKey(token.substring(BEARER.length()))) {
return Mono.just(anonymous);
}
return Mono.just(tokenCache.get(token.substring(BEARER.length())).getAuthentication());
};
}
@Bean
AuthenticationWebFilter authenticationWebFilter() {
AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(reactiveAuthenticationManager());
NegatedServerWebExchangeMatcher negateWhiteList = new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(AUTH_WHITELIST));
authenticationWebFilter.setRequiresAuthenticationMatcher(negateWhiteList);
authenticationWebFilter.setServerAuthenticationConverter(serverAuthenticationConverter());
authenticationWebFilter.setSecurityContextRepository(serverSecurityContextRepository());
authenticationWebFilter.setAuthenticationFailureHandler((webFilterExchange, exception) -> Mono.error(new BadCredentialsException("权限不足")));
return authenticationWebFilter;
}
}
login api
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LoginController {
private final ReactiveAuthenticationManager authenticationManager;
private final ServerSecurityContextRepository contextRepository;
@PostMapping("/login")
public Mono login(@RequestBody Login login, ServerWebExchange exchange) {
final String token = UUID.randomUUID().toString().replaceAll("-", "");
TokenAuthentication authenticationToken = new TokenAuthentication(token, login.getUsername(), login.getPassword());
return authenticationManager.authenticate(authenticationToken).doOnSuccess(auth -> contextRepository.save(exchange, new SecurityContextImpl(authenticationToken))).then(Mono.just(token));
}
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于