乐优商城项目---day17-授权中心(JWT令牌)

 

1.1.什么是有状态?

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力

  • 服务端保存用户状态,无法进行水平扩展

  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

1.2.什么是无状态

微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息

  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务

  • 服务端的集群和状态对客户端透明

  • 服务端可以任意的迁移和伸缩

  • 减小服务端存储压力

1.3.如何实现无状态

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证

  • 以后每次请求,客户端都携带认证的token

  • 服务的对token进行解密,判断是否有效。

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用JWT + RSA非对称加密

1.4.JWT

1.4.1.简介

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

1.4.2.数据格式

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT

    我们会对头部进行base64编码,得到第一部分数据

  • Payload:载荷,就是有效数据,一般包含下面信息:

    • 用户身份信息(注意,这里因为采用base64编码,可解码,因此不要存放敏感信息)

    • 注册声明:如token的签发时间,过期时间,签发人等

    这部分也会采用base64编码,得到第二部分数据

  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性

生成的数据格式:token==个人证件 jwt=个人身份证

1.5.1.没有RSA加密时

在微服务架构中,我们可以把服务的鉴权操作放到网关中,将未通过鉴权的请求直接拦截,如图:

  • 1、用户请求登录

  • 2、Zuul将请求转发到授权中心,请求授权

  • 3、授权中心校验完成,颁发JWT凭证

  • 4、客户端请求其它功能,携带JWT

  • 5、Zuul将jwt交给授权中心校验,通过后放行

  • 6、用户请求到达微服务

  • 7、微服务将jwt交给鉴权中心,鉴权同时解析用户信息

  • 8、鉴权中心返回用户数据给微服务

  • 9、微服务处理请求,返回响应

发现什么问题了?

每次鉴权都需要访问鉴权中心,系统间的网络请求频率过高,效率略差,鉴权中心的压力较大。

1.5.2.结合RSA的鉴权

直接看图:

  • 我们首先利用RSA生成公钥和私钥。私钥保存在授权中心,公钥保存在Zuul和各个信任的微服务

  • 用户请求登录

  • 授权中心校验,通过后用私钥对JWT进行签名加密

  • 返回jwt给用户

  • 用户携带JWT访问

  • Zuul直接通过公钥解密JWT,进行验证,验证通过则放行

  • 请求到达微服务,微服务直接用公钥解析JWT,获取用户信息,无需访问授权中心

2.授权中心

2.1.创建授权中心

授权中心的主要职责:

  • 用户鉴权:

    • 接收用户的登录请求,通过用户中心的接口进行校验,通过后生成JWT

    • 使用私钥生成JWT并返回

  • 服务鉴权:微服务间的调用不经过Zuul,会有风险,需要鉴权中心进行认证

    • 原理与用户鉴权类似,但逻辑稍微复杂一些(此处我们不做实现)

因为生成jwt,解析jwt这样的行为以后在其它微服务中也会用到,因此我们会抽取成工具。我们把鉴权中心进行聚合,一个工具module,一个提供服务的module

关于GWT得令牌得响应顺序和响应得响应流程

开始搭建授权微服务得得工程:leyou-auth ,leyou-auth-common,leyou-auth-service,第一个是父工程,剩下两个是子工程,其实基本得逻辑和前面得一样,配置启动类,引入依赖,添加配置文件,就可以启动,这里一以leyou-service这个微服务位例子

添加依赖


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.leyou.user</groupId>
            <artifactId>leyou-user-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

添加配置文件

server:
  port: 8087
spring:
  application:
    name: auth-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 10
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期

添加引导类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LeyouAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(LeyouAuthApplication.class, args);
    }
}

这个授权微服务搭建完成,下一个就是引入jwt得Utils 工具类,然后在测试用例里面生成令牌,首先引入工具类

接下来就是创建公钥和私钥,可以看出来对应得文件已经生成成功

接下来就是生成jwt令牌,可以看出封装了一个载荷进去,并且生成了一个jwt得令牌

解析:对封装过得jwt令牌,用公钥进行解密,可以得到里面得载荷得信息

下来开始编写授权得接口测试

Controller

    @PostMapping("accredit")
    public ResponseEntity<Void> accredt(@RequestParam("username")String username, @RequestParam("password")String password, HttpServletResponse response, HttpServletRequest request){
        String token=   authService.accredit(username,password);
        if (token==null){
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        CookieUtils.setCookie(request,response,token,jwtProperties.getCookieNme(),jwtProperties.getExpire()*60);
        return ResponseEntity.ok(null);
    }

Service:

 public String accredit(String username, String password) {
        User user = userClient.query(username, password);
        if (user==null){
            return null;
        }
        UserInfo userInfo=new UserInfo();
        userInfo.setId(user.getId());
        userInfo.setUsername(user.getUsername());
        try {
           return JwtUtils.generateToken(userInfo,jwtProperties.getPrivateKey(),jwtProperties.getExpire());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

接口进行测试:可以看到Cookie设值成功

domin域得问题:这个domin域设置得时候又一些问题,其中要在nginx中配置一定要注意这个是在api.leyou.com里面配置得,如果配置到起码得www.leyou.com或者mangage.leyou.com都是无法生效得然后就是在gateway网关中进行配置

 完成之后就可以获取cookie中得信息

延长首页得Cookie时间

Controller:

 @GetMapping("verify")
    public  ResponseEntity<UserInfo> verify(@CookieValue("LY_TOKEN")String token,HttpServletRequest request,HttpServletResponse response){
        try {
            UserInfo userInfo = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
            if (userInfo==null){
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            }
            token=JwtUtils.generateToken(userInfo,jwtProperties.getPrivateKey(),jwtProperties.getExpire());
            CookieUtils.setCookie(request,response,token,jwtProperties.getCookieNme(),jwtProperties.getExpire()*60);
            return ResponseEntity.ok(userInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }

出现错误

出现了一个非常大得BUG 我得CookieName 和CookieValue 值设置反了

这个里面还又一个问题就是又可能token得公钥和这个匹配不上,把原来得文件删除了就行,在生成一次,还有一个比较坑得地方,这个地方,变量名一要是user,因为响应得数据里面如果是出来user之外得熟悉,resp,在前端页面就渲染不出来

关于添拦截器,URL和URI是不一样得注意

public class LoginFilter extends ZuulFilter {
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private filterProperties filterProperties;

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 10;
    }

    @Override
    public boolean shouldFilter() {
        List<String> allowPaths = filterProperties.getAllowPaths();
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String getRequestURI = request.getRequestURI().toString();
        for (String allowPath : allowPaths) {
            if (StringUtils.contains(getRequestURI,allowPath))
            {
                return false;
            }
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = CookieUtils.getCookieValue(request, jwtProperties.getPubKeyPath());
        try {
            JwtUtils.getInfoFromToken(token,jwtProperties.getPublicKey());
        } catch (Exception e) {
            e.printStackTrace();
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }

 拦截成功

今日总结:

访问流程

1.无状态登陆
    在服务器端保存session
    无状态不需要session,把登陆状态保存在cookie中

2.jwt+rsa
    token:登陆时,jwt oath2 
    jwt:头信息(jwt) 载荷(用户信息,签发人 签发时间 有效时间) 签名(头信息+载荷:通过加密算法生成。作用:校验前两部分内容的合法性)

3.zuul网关访问的流程
    

4.搭建授权中心
    聚合工程:leyou-auth-common(jwt相关的工具类) leyou-auth-service(微服务)

5.jwtUtils
    根据载荷还有私钥生成jwt类型的token
    根据公钥解析jwt类型的token,获取载荷信息(userInfo)
    rsaUtils(生成公钥和私钥文件,并且读取公钥和私钥文件返回公钥和私钥对象)

6.登陆功能
    1.调用user-service中的queryUser接口,验证用户是否正确
    2.判断返回的用户信息是否为空
    3.生成jwt类型的token
    4.token信息设置到cookie中

7.首页用户名回显
    1.获取Cookie中jwt类型的token @CookieValue("LY_TOKEN")
    2.jwtUtils解析jwt,获取用户信息
        判断用户是否为空
    3.刷新jwt时间 cookie时间
    4.响应用户信息

8.在zuul网关添加过滤器
    获取zuul的上下文对象
    获取request
    通过cookieutils获取token
    解析token
    
    白名单;
    
    
    
    
 

发布了143 篇原创文章 · 获赞 16 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/zgz102928/article/details/104591646