前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security 系列(3) —— Spring Security & Webflux

Spring Security 系列(3) —— Spring Security & Webflux

作者头像
求和小熊猫
发布2022-09-08 11:52:52
2.2K0
发布2022-09-08 11:52:52
举报
文章被收录于专栏:∑小熊猫的博客

Spring Security & Webflux

文章目录

Webflux Spring Security

初始准备

引入 POM
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
修改配置文件

application.yml

代码语言:javascript
复制
server:
  port: 8089
spring:
  main:
    allow-bean-definition-overriding: true
编写主启动类
代码语言:javascript
复制
@EnableWebFlux
@SpringBootApplication
public class SpringSecurityOAuth2TestApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringSecurityOAuth2TestApplication.class);
        application.setWebApplicationType(WebApplicationType.REACTIVE);
        application.run(args);
    }
}

开启表单登陆

表单验证登陆时 Serverlet 与 Webflux 的相关核心类的对照情况

添加 Controller
代码语言:javascript
复制
@RestController
public class LoginController {

    @GetMapping("/") // 默认登陆成功后跳转
    public Mono<String> main(){
        return Mono.just("main");
    }

    @GetMapping("/test1")
    public Mono<String> test1(){
        return Mono.just("test1");
    }

    @GetMapping("/test2")
    public Mono<String> test2(){
        return Mono.just("test2");
    }

    @GetMapping("/test3")
    public Mono<Integer> test3(){
        return Mono.fromSupplier(() -> new Random().nextInt());
    }

    @GetMapping("/test4")
    public Mono<Double> test4(){
        return Mono.fromSupplier(() -> new Random().nextDouble());
    }

}
添加 WebSecurity 的配置类
代码语言:javascript
复制
@Configuration
@EnableWebFluxSecurity
public class WebfluxConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http){
        http.authorizeExchange(exchanges -> exchanges // 对于请求进行匹配
                .pathMatchers("/test1").permitAll()
                .pathMatchers("/test2").hasAnyRole("root")
                .pathMatchers("/test3").hasAnyRole("admin")
                .pathMatchers("/test4").hasAnyAuthority("root","user")
                .anyExchange().authenticated()
        );
        http.formLogin(Customizer.withDefaults());// 开启表单验证
        http.httpBasic(Customizer.withDefaults());// 开启 Basic 验证


        http.csrf(csrf -> csrf.disable().headers().disable()); // csrf 防护进行配置
        http.cors(cors -> cors.configurationSource( // 对跨域请求进行配置
            exchange -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(Collections.singletonList("*"));
                config.setAllowedHeaders(Collections.singletonList("*"));
                config.setAllowedMethods(Collections.singletonList("*"));
                config.setExposedHeaders(Collections.singletonList("Content-Disposition"));
                config.setAllowCredentials(true);
                config.applyPermitDefaultValues();
                return config;
            }
        ));

        return http.build();
    }

    @Bean // 密码加密器曝露,其也会被自动注入到 webflux security 中
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean // 开启表单验证时一定要曝露一个 ReactiveUserDetailService,他会被自动注入到 WebfluxSecurity 中
    public MapReactiveUserDetailsService userDetailsService(){
        PasswordEncoder passwordEncoder = passwordEncoder();
        UserDetails root = User.withUsername("root") // 创建两个 UserDetail
                .password(passwordEncoder.encode("123456"))
//                .authorities("root","ROLE_user","user")
                .roles("root","user") // role 与 authorities 会相互覆盖只能用一个
                .build();
        UserDetails user = User.withUsername("usr")
                .password(passwordEncoder.encode("password"))
                .authorities("ROLE_user","user")
                .build();
        UserDetails role = User.withUsername("test")
                .password(passwordEncoder.encode("admin"))
                .authorities("ROLE_admin") // role 为 admin 的权限就是 ROLE_admin
                .build();
        return new MapReactiveUserDetailsService(root,user,role);
        // 注意: MapReactiveUserDetailsService 在此段代码中只是用于模拟自我实现的 ReactiveUserDetailService
        //       在实际开发中可以自需要自己实现这个接口
    }
}
测试效果

进入登陆页面,输入 test 的用户名和密码,在登陆成功后请求 test3 可以看到被校验通过

Webflux Spring Security OAuth2

OAuth2 客户端

OAuth2 核心类

WebFlux 与 Servelet 的 OAuth2 核心类对照表

WebFlux

Servelet

ClientRegistration

ClientRegistration

ReactiveClientRegistrationRepository

ClientRegistrationRepository

OAuth2AuthorizedClient

ClientRegistrationOAuth2AuthorizedClient

ServerOAuth2AuthorizedClientRepository / ReactiveOAuth2AuthorizedClientService

OAuth2AuthorizedClientRepository / OAuth2AuthorizedClientService

ReactiveOAuth2AuthorizedClientManager / ReactiveOAuth2AuthorizedClientProvider

OAuth2AuthorizedClientManager / OAuth2AuthorizedClientProvider

密码模式实现
修改 yml 配置文件

application.yml

代码语言:javascript
复制
auth_server: http://localhost:8088/ # 指定授权服务器地址
spring:
  main:
    allow-bean-definition-overriding: true
  security:
    oauth2:
      client:
        registration:
          test: # registrationId
            clientId: client # clientId
            clientSecret: yourSecret # clientSecret
            authorizationGrantType:  password # authorization_code # 授权类型
            scope: all # 授权范围
        provider:
          test: # providerId
            authorizationUri: ${auth_server}/oauth/authorize # 验证授权的uri
            tokenUri: ${auth_server}/oauth/token # 获取 token 的 uri
修改 Webflux 的配置

代码语言:javascript
复制
@Configuration
@EnableWebFluxSecurity
public class WebfluxConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http){
        http.authorizeExchange(exchanges -> exchanges // 对于请求进行匹配
                .pathMatchers("/oauth/**").permitAll()
                .anyExchange().authenticated()
        );

        http.oauth2Client(Customizer.withDefaults());// 使用 OAuth2 Client

        http.csrf(csrf -> csrf.disable().headers().disable()); // csrf 防护进行配置
        http.cors(cors -> cors.configurationSource( // 对跨域请求进行配置
            exchange -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(Collections.singletonList("*"));
                config.setAllowedHeaders(Collections.singletonList("*"));
                config.setAllowedMethods(Collections.singletonList("*"));
                config.setExposedHeaders(Collections.singletonList("Content-Disposition"));
                config.setAllowCredentials(true);
                config.applyPermitDefaultValues();
                return config;
            }
        ));

        return http.build();
    }

}
添加登陆用的 DTO
代码语言:javascript
复制
@Data
public class UserDto {
    private String username;
    private String password;
}
添加 OAuth2 配置类

OAuth2Configuration

代码语言:javascript
复制
@Configuration
public class OAuth2Configuration {
    @Bean
    public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .password()
                        .refreshToken()
                        .build();

        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultReactiveOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        // Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
        // map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
        authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

        return authorizedClientManager;
    }

    private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
        return authorizeRequest -> {
            Map<String, Object> contextAttributes = Collections.emptyMap();
            UserDto exchange = authorizeRequest.getAttribute(UserDto.class.getName());
            if (StringUtils.hasText(exchange.getUsername()) && StringUtils.hasText(exchange.getPassword())) {
                contextAttributes = new HashMap<>();
                // `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
                contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, exchange.getUsername());
                contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, exchange.getPassword());
            }
            return Mono.just(contextAttributes);
        };
    }
添加 Controller

Oauth2Controller

代码语言:javascript
复制
@RestController
public class OAuth2Controller {

    @Autowired
    ReactiveOAuth2AuthorizedClientManager clientManager;

    @PostMapping("/oauth/login")
    public Mono<String> login(@RequestBody UserDto user){
        Authentication authentication = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());

        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("test")
                .principal(authentication)
                .attribute(UserDto.class.getName(),user)
                .build();

        return clientManager.authorize(authorizeRequest).map(oAuth2AuthorizedClient -> {
           OAuth2AccessToken accessToken =  oAuth2AuthorizedClient.getAccessToken();
           if(accessToken != null && StringUtils.hasLength(accessToken.getTokenValue())){
               System.out.println(accessToken.getTokenValue());
               return accessToken.getTokenValue();
           }
           return "no info";

        });
    }
}
授权码模式实现
注入一个 client 用于获取授权码返回的 token 信息

OAuth2Configuration

代码语言:javascript
复制
@Bean
public WebClientReactiveAuthorizationCodeTokenResponseClient tokenResponseClient(){    
    return new WebClientReactiveAuthorizationCodeTokenResponseClient();
}
修改 Controller

Oauth2Controller

代码语言:javascript
复制
@Autowired
WebClientReactiveAuthorizationCodeTokenResponseClient client;

@Autowired
ReactiveClientRegistrationRepository clientRegistrationRepository;

@GetMapping("/oauth/loginWithCode")
public Mono<String> test(@PathParam("code")String code){
    if(StringUtils.hasLength(code)){
        OAuth2AuthorizationRequest oAuth2AuthorizeRequest = OAuth2AuthorizationRequest.authorizationCode()
                .authorizationUri("http://localhost:8088/oauth/authorize")
                .clientId("client")
                .build();
        OAuth2AuthorizationResponse response = OAuth2AuthorizationResponse.success(code).
                redirectUri("http://www.baidu.com").build();
        OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(oAuth2AuthorizeRequest,response);
        OAuth2AuthorizationCodeGrantRequest request = new OAuth2AuthorizationCodeGrantRequest(
                clientRegistrationRepository.findByRegistrationId("test").block() ,exchange);
        return client.getTokenResponse(request).map(res -> res.getAccessToken().getTokenValue());
    }
    return Mono.just("false");
}

OAuth2 资源服务器

使用 OAuth2 资源服务器
配置 yaml
代码语言:javascript
复制
server:
  port: 8089
  
auth_server: http://localhost:8088/ # 指定授权服务器地址
spring:
  main:
    allow-bean-definition-overriding: true
  security:
    oauth2:
      client:
        registration:
          test: # registrationId
            clientId: client # clientId
            clientSecret: yourSecret # clientSecret
            redirectUri: http://localhost:${server.port}/test/2
            authorizationGrantType:  password # authorization_code # 授权类型
            scope: all # 授权范围
        provider:
          test: # providerId
            authorizationUri: ${auth_server}/oauth/authorize # 验证授权的uri
            tokenUri: ${auth_server}/oauth/token # 获取 token 的 uri
      resourceserver:
        jwt:
          public-key-location: classpath:public.cert # 指定公钥位置
添加资源服务器配置

WebfluxConfiguration

代码语言:javascript
复制
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http){
    http.authorizeExchange(exchanges -> exchanges // 对于请求进行匹配
            .pathMatchers("/oauth/**").permitAll()
            .anyExchange().authenticated()
    );
    http.oauth2Client(Customizer.withDefaults());// 使用 OAuth2 Client
    // 对资源服务器进行相关配置
    http.oauth2ResourceServer(resource ->{
        resource.jwt(); // 开启资源服务器的 Jwt
    });
    http.csrf(csrf -> csrf.disable().headers().disable()); // csrf 防护进行配置
    http.cors(cors -> cors.configurationSource( // 对跨域请求进行配置
        exchange -> {
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowedOrigins(Collections.singletonList("*"));
            config.setAllowedHeaders(Collections.singletonList("*"));
            config.setAllowedMethods(Collections.singletonList("*"));
            config.setExposedHeaders(Collections.singletonList("Content-Disposition"));
            config.setAllowCredentials(true);
            config.applyPermitDefaultValues();
            return config;
        }
    ));
    return http.build();
}
修改 Controller 并测试效果
代码语言:javascript
复制
@Autowired
public ReactiveJwtDecoder jwtDecoder;
@PostMapping("/oauth/login")
public Mono<String> login(@RequestBody UserDto user){
    Authentication authentication = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
    OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("test")
            .principal(authentication)
            .attribute(UserDto.class.getName(),user)
            .build();
    return clientManager.authorize(authorizeRequest).map(oAuth2AuthorizedClient -> {
       OAuth2AccessToken accessToken =  oAuth2AuthorizedClient.getAccessToken();
       if(accessToken != null && StringUtils.hasLength(accessToken.getTokenValue())){
           System.out.println(accessToken.getTokenValue());
           jwtDecoder.decode(accessToken.getTokenValue()).subscribe(jwt -> { // 解码
               System.out.println(jwt.getClaims()); // 打印信息
               System.out.println(jwt.getId()); // 打印 jwt
           });
           return accessToken.getTokenValue();
       }
       return "noinfo";
    });
}
最终测试效果
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Security & Webflux
    • 文章目录
      • Webflux Spring Security
        • 初始准备
        • 开启表单登陆
      • Webflux Spring Security OAuth2
        • OAuth2 客户端
        • OAuth2 资源服务器
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档