安全性,JWT和WebFlux的云
#云 #java #microservices #jwt

春季安全性是一件很酷的事情,在各个平台上有许多指南,文章。但是问题在于,其中许多视频仅限于整体建筑。在本文中,我想谈谈我将其用于微服务的个人经验。这是我想分享的一个异常的个人经历,也许对某人很有用。

Image description

本文将涵盖以下内容:

  • 向用户注册和发行JWT令牌的机制(简短)
  • 授权机制(简短)
  • 基于用户角色的安全应用程序

应用技术:

  • Spring Boot
  • 春天云
  • 春季安全
  • JWT
  • WebFlux
我认为,

查询的机制对许多人来说是清楚的。如果没有,下面的图片将简要解释所有内容。

Image description

从用户收到请求。它被重定向到已部署网关的端口,微服务的名称被替换,然后是指定的微服务GO的通常终点。例如:localhost:8888/microserviceName/用户。

*让我们继续前进!
*

我建议在注册的微服务中进行一些运行,将用户存储在数据库中并发出JWT令牌。假设有某个个人实体,其中包含ID,用户名,密码,角色。

从用户服务创建用户的方法:

public AuthResponse createPerson(PersonDto dto) {
        Person personEntity = mapper.dtoToPerson(dto);
        personEntity.setRole(Role.USER);
        personEntity.setPassword(BCrypt.hashpw(dto.getPassword(), BCrypt.gensalt()));

        repository.save(personEntity);
        return getAuthResponse(personEntity);
    }

private AuthResponse getAuthResponse(Person personEntity) {
        String accessToken = jwt.generate(personEntity, accessType);
        String refreshToken = jwt.generate(personEntity, refreshType);
        return new AuthResponse(accessToken, refreshToken, personEntity.getMRID());
    }

请注意,在第4行中,我们将密码放置并以加密形式存储在数据库中。关于在Internet上生成JWT令牌的主题有很多有用的文章和视频。本文主要致力于我们应用程序的安全性。

现在让我们进入最有趣的部分。 API网关!让我们更详细地研究它。

所需的依赖项:

    implementation 'org.springframework.boot:spring-boot-starter-security:2.6.8'
    implementation 'org.springframework.boot:spring-boot-starter-webflux:2.6.8'
    implementation 'org.springframework:spring-webmvc:5.3.22'

    implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.1'

安全配置:

@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final AuthenticationManager authenticationManager;
    private final SecurityContextRepository securityContextRepository;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
                .csrf()
                .disable()
                .authenticationManager(authenticationManager)
                .securityContextRepository(securityContextRepository)
                .authorizeExchange()
                .pathMatchers("/microservice1/users").permitAll()
                .pathMatchers("/microservice2/emails").authenticated()
                .pathMatchers("/microservice3/persons").hasAuthority("ADMIN")
                .anyExchange()
                .permitAll()
                .and()
                .httpBasic()
                .disable()
                .formLogin()
                .disable();
        return http.build();
    }
}

请注意,我们不再使用@enablewebsecurity,而是@enablewebfluxsecurity。此注释是必要的,它使我们能够在网关中实现安全性,并通过微服务反应地运行。

我们知道,弃用了WebsecurityConfigurerAdapter的继承。因此,我们实现了安全网络过滤器链并描述其中所需的功能。

5,6行中有两个重要的事情,即:身份验证管理器,安全上下文存储库。

首先,考虑安全上下文存储库:

@Component
@RequiredArgsConstructor
public class SecurityContextRepository implements ServerSecurityContextRepository {

    private final AuthenticationManager authenticationManager;

    @Override
    public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Mono<SecurityContext> load(ServerWebExchange swe) {
        Mono<String> stringMono = Mono.justOrEmpty(swe.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
        return stringMono.flatMap(this::getSecurityContext);
    }

    private Mono<? extends SecurityContext> getSecurityContext(String token) {
        Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
        return authenticationManager.authenticate(auth).map(SecurityContextImpl::new);
    }
}

要简要回答这里发生的事情,我们将授权标题从请求中获取,并将其从AuthenticationManager发送到身份验证方法。

这是身份验证管理器:

@Lazy
@Component
@RequiredArgsConstructor
public class AuthenticationManager implements ReactiveAuthenticationManager {

    private final Builder webClient;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        String jwtToken = authentication.getCredentials().toString();
        return tokenValidate(jwtToken)
                .bodyToMono(UserAuthorities.class)
                .map(this::getAuthorities);
    }

    private UsernamePasswordAuthenticationToken getAuthorities(UserAuthorities userAuthorities) {
        return new UsernamePasswordAuthenticationToken(
                userAuthorities.getUsername(), null,
                userAuthorities.getAuthorities().stream()
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList()));
    }

    private ResponseSpec tokenValidate(String token) {
        return webClient.build()
                .get()
                .uri(uriBuilder -> uriBuilder.host("registration").path("/token/auth").queryParam("token", token).build())
                .retrieve()
                .onStatus(HttpStatus.FORBIDDEN::equals, response -> Mono.error(new IllegalStateException("Token is not valid")));
    }
}

在Tokenvalidate方法中,我们转到注册微服务,转到端点令牌/auth。 JWT令牌验证功能应在其中实现。在其中,您必须从JWT令牌中获取所有主张,然后将其写入DTO。看起来像这样:

public UserAuthorizationInfo getUserInfoFromToken(String token) {
        // здесь должна быть валидация вашего токена

        Claims allClaimsFromToken = jwt.getAllClaimsFromToken(token);
        UserAuthorizationInfo userInfo = new UserAuthorizationInfo();

        userInfo.setPersonId(allClaimsFromToken.get("id").toString());
        userInfo.setUsername(allClaimsFromToken.getSubject());

        List<String> authorities = new ArrayList<>();
        authorities.add(allClaimsFromToken.get(ROLES).toString());
        userInfo.setAuthorities(authorities);
        return userInfo;
    }

接下来,我们将获得包含用户名和收集机构的用户当局。并在收到标题的请求后,将用户名和角色分配给授权。现在,我们只能指定网关API的安全配置中的哪些端点,并且一切都可以正常工作,顺便说一句,毕竟,相当快,反应性:)