春季安全性是一件很酷的事情,在各个平台上有许多指南,文章。但是问题在于,其中许多视频仅限于整体建筑。在本文中,我想谈谈我将其用于微服务的个人经验。这是我想分享的一个异常的个人经历,也许对某人很有用。
本文将涵盖以下内容:
- 向用户注册和发行JWT令牌的机制(简短)
- 授权机制(简短)
- 基于用户角色的安全应用程序
应用技术:
- Spring Boot
- 春天云
- 春季安全
- JWT
- WebFlux
查询的机制对许多人来说是清楚的。如果没有,下面的图片将简要解释所有内容。
从用户收到请求。它被重定向到已部署网关的端口,微服务的名称被替换,然后是指定的微服务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的安全配置中的哪些端点,并且一切都可以正常工作,顺便说一句,毕竟,相当快,反应性:)