Spring WebFlux REST API Token 登陆

本贴最后更新于 2104 天前,其中的信息可能已经时移世异

pom

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

TokenAuthentication

  • 方便授权时传递 token 并放入缓存
@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));
    }
}
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...