采用 springcloud 微服务。
创建父工程。
// pom.xml 部分代码
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR5</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
创建 EurekaServer。采用 module 的方式创建。
// pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
// application.yml
server:
port: 8080
spring:
application:
name: openapi-eureka
# security:
# user:
# name:
# password:
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
fetch-registry: false
register-with-eureka: false
// EurekaApplication.java
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
// 查看注册信息
http://localhost:8080/
创建 auth 工程,在登录接口中生成 jjwt。
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
// application.yml
server:
port: 8083
spring:
application:
name: openapi-auth
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
// AuthApplication.java
@SpringBootApplication
@EnableEurekaClient
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
// User.java
@Data
public class User {
private String username;
private String password;
}
// BaseResultBean.java
@Data
public class BaseResultBean {
private String msg;
private String code;
}
// AuthController.java
@RestController
public class AuthController {
@RequestMapping(value = "/login", method = {RequestMethod.POST})
public BaseResultBean login(@RequestBody User user, HttpServletResponse response) {
BaseResultBean resultBean = new BaseResultBean();
if ("admin".equalsIgnoreCase(user.getUsername()) && "admin".equalsIgnoreCase(user.getPassword())) {
resultBean.setCode("200");
resultBean.setMsg("登录成功");
Instant now = Instant.now();
String jwt = Jwts.builder()
.setSubject(user.getUsername()) // 设置用户
.setIssuedAt(Date.from(now)) // 设置 token 有效期开始时间
.setExpiration(Date.from(now.plusSeconds(3600))) // 结束时间
.claim("id","1001") // token 携带参数
.claim("username",user.getUsername())
.signWith(SignatureAlgorithm.HS256, "jwtPassword")
.compact();
System.out.println("jwt: " + jwt);
// 在 token 中响应出去
response.setHeader("token", jwt);
// 存储最新的 token
} else {
resultBean.setCode("201");
resultBean.setMsg("登录失败");
}
return resultBean;
}
}
在 zuul 过滤器中验证请求是否携带了正确的 token。
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
// application.yml
server:
port: 8085
spring:
application:
name: openapi-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
# zuul actuator 监控,http://localhost/actuator/routes
management:
endpoints:
web:
exposure:
include: "*"
#zuul:
# ignored-services: "*"
// GatewayController.java
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class GatewayController {
public static void main(String[] args) {
SpringApplication.run(GatewayController.class, args);
}
}
// JwtFilter.java
@Component
public class JwtFilter extends ZuulFilter {
@Autowired
private ObjectMapper objectMapper;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().sendZuulResponse();
// return false;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
HttpServletResponse response = currentContext.getResponse();
String jwt = request.getHeader("token");
try {
Claims claims = Jwts.parser().setSigningKey("jwtPassword").parseClaimsJws(jwt).getBody();
String subject = claims.getSubject();
Object id = claims.get("id");
Object username = claims.get("username");
System.out.println("subject: " + subject);
System.out.println("id: " + id);
System.out.println("username: " + username);
// 比对是否最新的 token ,否则 throw
} catch (Exception e) {
e.printStackTrace();
// 不放行
currentContext.setSendZuulResponse(false);
BaseResultBean resultBean = new BaseResultBean();
resultBean.setCode("202");
resultBean.setMsg("登录认证失败,请重新登录。");
response.setContentType("application/json;charset=utf-8");
try {
String s = objectMapper.writeValueAsString(resultBean);
currentContext.setResponseBody(s);
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
}
return null;
}
}
Postman 验证。
先拿到 jjwt 字符串。
再发送携带 token 的请求。
注意,每次登录请求时都会得到一个新的 jjwt,但之前未过期的 jjwt 仍然能通过认证,所以在认证时还应该判断 jjwt 是否最新。