在上一篇文章的基础上我们学习搭建JWT的鉴权系统
1.0.JWT交互流程
上一篇:JWT使用
1.4.JWT交互流程
流程图:
步骤翻译:
• 1、用户登录
• 2、服务的认证,通过后根据secret生成token
• 3、将生成的token返回给用户
• 4、用户每次请求携带token
• 5、服务端利解读jwt签名,判断签名有效后,从Payload中获取用户信息
• 6、处理请求,返回响应结果
因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,就能知道用户身份,完全符合了Rest的无状态规范。
我们逐步演进系统架构设计。需要注意的是:secret是签名的关键,因此一定要保密,我们放到鉴权中心保存,其它任何服务中都不能获取secret。
在微服务架构中,我们可以把服务的鉴权操作放到网关中,将未通过鉴权的请求直接拦截,如图:
• 1、用户请求登录
• 2、Zuul将请求转发到授权中心,请求授权
• 3、授权中心校验完成,颁发JWT凭证
• 4、客户端请求其它功能,携带JWT
• 5、Zuul将jwt交给授权中心校验,通过后放行
• 6、用户请求到达微服务
• 7、微服务将jwt交给鉴权中心,鉴权同时解析用户信息
• 8、鉴权中心返回用户数据给微服务
• 9、微服务处理请求,返回响应
以下项目要搭建出这几个模块,有的话直接搭建认证模块
结论:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<modules>
<module>jwt-eurekamodule>
<module>jwt-commonmodule>
<module>jwt-pojomodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.1.RELEASEversion>
<relativePath/>
parent>
<groupId>com.czxygroupId>
<artifactId>jwt-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>pompackaging>
<name>jwt-parentname>
<description>Demo project for Spring Bootdescription>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.M3spring-cloud.version>
<mybatis.starter.version>1.3.2mybatis.starter.version>
<mapper.starter.version>1.2.3mapper.starter.version>
<druid.starter.version>1.1.9druid.starter.version>
<mysql.version>5.1.32mysql.version>
<pageHelper.starter.version>1.2.3pageHelper.starter.version>
<jjwt.version>0.7.0jjwt.version>
<joda-time.version>2.9.6joda-time.version>
<lombok.version>1.16.22lombok.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.starter.version}version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
<version>${mapper.starter.version}version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>${pageHelper.starter.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>${druid.starter.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>${jjwt.version}version>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>${joda-time.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<scope>providedscope>
dependency>
dependencies>
dependencyManagement>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
添加一段配置,用来启动管理我们服务,写下文的时候突然发现没配,回来写上【捂脸】
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
set>
option>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.czxygroupId>
<artifactId>jwt-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.czxygroupId>
<artifactId>jwt-eurekaartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>jwt-eurekaname>
<description>Demo project for Spring Bootdescription>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 10086 #eureka端口号
spring:
application:
name: eureka-server #eureka服务的名字
eureka:
server:
enable-self-preservation: true # Eureka自我保护机制,true打开/false禁用,默认打开状态,建议生产环境打开此配置。
eviction-interval-timer-in-ms: 5000 # 修改检查失效服务的时间
client:
fetch-registry: true # 定期的更新客户端的服务清单,以保证服务访问的正确性
register-with-eureka: true # 是否将自己注册为服务
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka # eureka服务的开放地址
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jwt-parentartifactId>
<groupId>com.czxygroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>jwt-commonartifactId>
<dependencies>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
dependencies>
project>
jwt-util
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
/**
* jwt工具类
*/
public class JWTUtil {
/**
* 获取token中的参数
*
* @param token
* @return
*/
public static Claims parseToken(String token,String key) {
if ("".equals(token)) {
return null;
}
try {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(key))
.parseClaimsJws(token).getBody();
} catch (Exception ex) {
return null;
}
}
/**
* 生成token
*
* @param userId
* @return
*/
public static String createToken(Integer userId,String key, int expireMinutes) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder()
// .setHeaderParam("type", "JWT")
// .setSubject(userId.toString())
.claim("userId", userId) // 设置载荷信息
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())// 设置超时时间
.signWith(signatureAlgorithm, signingKey);
//生成JWT
return builder.compact();
}
}
baseutil 这个主要是浏览器请求服务器,服务器接收请求作出的回执信息
/**
* 使用了lombok提供的@Data注解可以免写getset toString方法
* @Author ZhangLe
* @Date 2018/12/14 10:04
*/
@Data
public class BaseResult {
private Integer errno;//0成功,1失败
private String errmsg;//提示内容
private Object data;//返回的内容
public BaseResult(Integer errno, String errmsg,Object object) {
this.errno = errno;
this.errmsg = errmsg;
this.data=object;
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jwt-parentartifactId>
<groupId>com.czxygroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>jwt-pojoartifactId>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
dependencies>
project>
Goods商品实体类
/**
* 测试对象
*/
@Data
public class Goods {
private Integer skuid;
private String goodsName;
private Double price;
public Goods() {
}
public Goods(Integer skuid, String goodsName, Double price) {
this.skuid = skuid;
this.goodsName = goodsName;
this.price = price;
}
}
User实体类
/**
* @Author ZhangLe
* @Date 2018/12/17 17:30
*/
@Data
public class User {
private Integer id;
private String username;
private String password;
public User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
}
1、用户未登陆状态,可以搜索商品信息
2、goods-search为商品搜索服务,接收用户页面搜索请求
实现步骤:
1、pojo
2、controller
4、service
5、dao
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.czxygroupId>
<artifactId>jwt-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>goods-searchartifactId>
<packaging>jarpackaging>
<name>goods-searchname>
<description>Demo project for Spring Bootdescription>
<dependencies>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-pojoartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8084
spring:
application:
name: goods-search
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: ${eureka.instance.ip-address}.${server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
/**
* @Author ZhangLe
* @Date 2018/12/17 17:51
*/
@RestController
@RequestMapping("/search")
public class GoodsSearchController {
/**
* 我们使用伪造数据 知道是查出来的就行了 这里为了模拟Token
* @return
*/
@GetMapping
public BaseResult GoodsSearch(){
ArrayList<Goods> goods = new ArrayList<>();
goods.add(new Goods(1,"huawei",1.0));
goods.add(new Goods(2,"xiaomi",1.0));
goods.add(new Goods(3,"meizu",1.0));
BaseResult baseResult = new BaseResult(0,null,goods);
return baseResult;
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.czxygroupId>
<artifactId>jwt-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>user-serviceartifactId>
<packaging>jarpackaging>
<name>user-servicename>
<description>Demo project for Spring Bootdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-pojoartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8086
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: ${eureka.instance.ip-address}.${server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
/**
* @Author ZhangLe
* @Date 2018/12/17 18:45
*/
@RestController
@RequestMapping("user")
public class UserController {
@PostMapping
public BaseResult saveUser(User user){
System.out.println("新增用户"+user);
return new BaseResult(0,"success",null);
}
}
授权中心的主要职责:
• 用户鉴权:
– 接收用户的登录请求,通过用户中心的接口进行校验,通过后生成JWT
– 使用私钥生成JWT并返回
• 服务鉴权:微服务间的调用不经过Zuul,会有风险,需要鉴权中心进行认证
– 原理与用户鉴权类似,但逻辑稍微复杂一些(此处我们不做实现)
因为生成jwt,解析jwt这样的行为以后在其它微服务中也会用到,因此我们会抽取成工具。我们把鉴权中心进行聚合,一个工具module,一个提供服务的module
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.czxygroupId>
<artifactId>jwt-parentartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>jwt-authartifactId>
<packaging>jarpackaging>
<name>jwt-authname>
<description>Demo project for Spring Bootdescription>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-pojoartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8082
spring:
application:
name: jwt-auth
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: ${eureka.instance.ip-address}.${server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
http://localhost:8082/login?username=admin&password=admin
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jwt-parentartifactId>
<groupId>com.czxygroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>jwt-zuulartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.czxygroupId>
<artifactId>jwt-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 10010
spring:
application:
name: jwt-zuul
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: ${eureka.instance.ip-address}.${server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
ribbon:
ConnectTimeout: 300
ReadTimeout: 1000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 2
MaxAutoRetries: 1
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
/**
* @Author ZhangLe
* @Date 2018/12/18 8:34
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class UserZuulApplication {
public static void main(String[] args) {
SpringApplication.run(UserZuulApplication.class,args);
}
}
这段代码建议仔细看看run()
方法
package com.czxy.config;
import com.czxy.util.JWTUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @Author ZhangLe
* @Date 2018/12/18 8:37
*/
@Component
public class JWTFilter extends ZuulFilter {
@Override
public String filterType() {
System.out.println("前置过滤器");
return "pre";
}
@Override
public int filterOrder() {
System.out.println("优先级别,数字越小,优先级越高");
return 0;
}
@Override
public boolean shouldFilter() {
System.out.println("是否执行过滤器,true,执行");
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
//获取header
HttpServletRequest request = currentContext.getRequest();
//获取请求的url
String url = request.getRequestURL().toString();
if (url.indexOf("/login")>0 || url.indexOf("/search") > 0){
System.out.println("登录");
return null;
}
String key = "";
if (url.indexOf("user-service") != -1){
key = "user";
String token = request.getHeader("authorizztion");
if (token != null){
Claims claims = JWTUtil.parseToken(token, key);
if (claims != null){
currentContext.addZuulRequestHeader("authorizztion",token);
return null;
}
}
}
currentContext.setSendZuulResponse(false);//终止运行
currentContext.setResponseStatusCode(401);
currentContext.setResponseBody("{'flag':false,'message':'未登录'}");
currentContext.getResponse().setContentType("text/html;charset=UTF-8");
return null;
}
}
http://localhost:10010/user-service/user
用户登录
http://localhost:10010/jwt-auth/login?username=admin&password=admin
携带token,进行用户新增
http://localhost:10010/user-service/user
注:必须先登录,获取到token才能用户新增
token
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImV4cCI6MTU0NTA5NzM2M30.ouywUb_0KG4H132AcjPArSHkLXos1ceafGiNMpgdSdo
写了token,Authorizztion
大小写无所谓
番外
网关执行流程
用户新增
祝你幸福
送你一首歌《直到遇见了你我只喜欢你》陈柯宇
我去商场偶然听见,记住了高潮部分歌词 然后搜了出来,第一次听
附图:中国地图(看看以后自己会去哪,下个月写点面试经验,给以后的自己看)