| 知乎专栏 |
package cn.netkiller.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
@Order
@Slf4j
public class TokenWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
log.info(exchange.getRequest().getURI().toString());
if (!exchange.getRequest().getHeaders().containsKey("token")) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Mono.just(response.bufferFactory().wrap("{\"msg\":\"no token\"}".getBytes())));
} else {
exchange.getAttributes().put("auth", "true");
return chain.filter(exchange).doFinally(s -> {
log.info("request after, url:{}, statusCode:{}", exchange.getRequest().getURI(), exchange.getResponse().getStatusCode());
});
}
}
}
iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号
package cn.netkiller.config;
import cn.netkiller.component.JwtTokeComponent;
import cn.netkiller.utils.ResponseJson;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
@Order
@Slf4j
public class TokenWebFilter implements WebFilter {
private static final String[] patterns = {"/token", "/verifier", "/mock/*", "/swagger/*", "/badges/**"};
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@Autowired
private JwtTokeComponent jwtTokeComponent;
public TokenWebFilter() {
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getFormData().subscribe(System.out::println);
exchange.getFormData().doOnNext(n -> {
n.forEach((k, v) -> {
log.info("K: {}, V: {}", k, v);
});
});
log.info("No Token");
String path = exchange.getRequest().getPath().toString();
for (String pattern : patterns) {
if (pathMatcher.match(pattern, path)) {
log.info("Permit Pattern '" + pattern + "' matches path '" + path + "'");
return chain.filter(exchange);
}
}
if (!exchange.getRequest().getHeaders().containsKey("token")) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Mono<DataBuffer> message = Mono.just(response.bufferFactory().wrap(new ResponseJson(false, ResponseJson.Code.TokenException, "请提供 Token", null).toString().getBytes()));
return response.writeWith(message);
} else {
String token = exchange.getRequest().getHeaders().getFirst("token");
log.info("token: " + token);
ResponseJson jwt = jwtTokeComponent.verifier(token);
log.info("jwt: " + jwt.isStatus());
if (jwt.isStatus()) {
return chain.filter(exchange);
} else {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Gson gson = new Gson();
String jsonString = gson.toJson(jwt);
Mono<DataBuffer> message = Mono.just(response.bufferFactory().wrap(jsonString.getBytes()));
return response.writeWith(message);
}
}
}
}
package cn.netkiller.component;
import cn.netkiller.utils.ResponseJson;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@Slf4j
@Component
@NoArgsConstructor
public class JwtTokeComponent {
@Value("${app.expires}")
private int expires;
@Value("${app.audience}")
private String audience;
@Value("${app.id}")
private String appId;
@Value("${app.key}")
private String appKey;
@Value("${app.secret}")
private String secret;
@Value("${app.subject}")
private String subject;
@Value("#{'${app.role}'.split(',')}")
private List role;
public ResponseJson verifier(String token) {
ResponseJson response;
try {
DecodedJWT jwt = this.verify(token);
response = new ResponseJson(true, ResponseJson.Code.SUCCESS, "Token 校验成功", jwt);
} catch (SignatureVerificationException e) {
response = new ResponseJson(false, ResponseJson.Code.TokenException, e.getMessage(), "Token 签名失败");
} catch (TokenExpiredException e) {
response = new ResponseJson(false, ResponseJson.Code.TokenExpiredException, e.getMessage(), "Token 过期");
} catch (AlgorithmMismatchException e) {
response = new ResponseJson(false, ResponseJson.Code.AlgorithmMismatchException, e.getMessage(), "Token 签名算法异常");
} catch (JWTVerificationException e) {
response = new ResponseJson(false, ResponseJson.Code.TokenException, e.getMessage(), "Token 校验失败");
} catch (Exception e) {
response = new ResponseJson(false, ResponseJson.Code.Exception, e.getMessage(), "Token 异常");
}
log.error(response.toString());
return response;
}
public Mono<String> getToken(String appId, String appKey) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, expires);
// instance.add(Calendar.SECOND, 30);
try {
// Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, rsaPrivateKey);
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withJWTId(appKey)
.withIssuer(appId)
.withIssuedAt(new Date())
.withSubject(subject)
.withKeyId(appKey)
.withAudience(audience)
.withClaim("role", role)
.withExpiresAt(instance.getTime())
.sign(algorithm);
return Mono.just(token);
} catch (JWTCreationException exception) {
log.error(exception.getMessage());
}
return Mono.empty();
}
public DecodedJWT verify(String token) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
// specify an specific claim validations
.withIssuer(appId)
.withJWTId(appKey)
.withSubject(subject)
.withAudience(audience)
// reusable verifier instance
.build();
DecodedJWT decodedJWT = verifier.verify(token);
log.info(decodedJWT.getClaims().toString());
return decodedJWT;
}
}
通过注解跳过 Token 验证
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
默认用户名是 user,密码会打印到终端
2024-01-03T18:52:01.027+08:00 WARN 33537 --- [watch-development] [ main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: 823fcdcc-e2dd-4967-84b4-546db9175357 This generated password is for development use only. Your security configuration must be updated before running your application in production. 2024-01-03T18:52:01.081+08:00 INFO 33537 --- [watch-development] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 13 endpoint(s) beneath base path '/actuator'
访问方式
neo@MacBook-Pro-M2 ~> curl -X 'GET' 'http://user:823fcdcc-e2dd-4967-84b4-546db9175357@localhost:8080/mock/mono' hello webflux⏎
修改密码,在配置文件中增加
# spring.security.user.name=user spring.security.user.password=123456 #
package cn.netkiller.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
// @Autowired
// private TokenWebFilter tokenWebFilter;
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Order(Ordered.HIGHEST_PRECEDENCE)
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/").permitAll()
.pathMatchers("/token", "/verifier").permitAll()
.pathMatchers("/mock/*").permitAll()
.pathMatchers("/ping", "/version").permitAll()
.pathMatchers("/badges/**", "/chat/**", "/status/**", "/picture/**").permitAll()
.anyExchange().authenticated()
);
// .addFilterBefore(tokenWebFilter, SecurityWebFiltersOrder.FIRST);
// .httpBasic(withDefaults())
// .formLogin(withDefaults());
return httpSecurity.build();
}
}