JWT技术的解析即代码

在测试类中@Test

在测试类中关于@Test注解的说明

如果导入的是import org.junit.Test;这个包那么就要在使用@Test修饰的方法中加public 修饰符

如果导入的是import org.junit.jupiter.api.Test;这个包就要在使用@Test修饰的方法中可以不加public 修饰符


jwt

背景

在传统的有状态服务应用中,服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如Tomcat中的Session。例如登录:用户登录后,我们把用户的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session,然后下次请求,用户携带cookie值来(这一步有浏览器自动完成),我们就能识别到对应session,从而找到用户的信息。这种方式目前来看最方便,但在分布式应用中,由服务端保存用户状态不是一种很好的选择,因此JWT诞生

具体的流程图

请添加图片描述

JWT概述

JWT(JSON WEB Token)是一个标准,借助JSON格式数据作为WEB应用请求中的令牌,进行数据的自包含设计,实现各方安全的信息传输,在数据传输过程中还可以对数据进行加密,签名等相关处理。同时JWT也是目前最流行的跨域身份验证解决方案(其官方网址为:https://jwt.io/)。可以非常方便的在分布式系统中实现用户身份认证。

JWT数据结构

JWT通常由三部分构成,分别为Header(头部),Payload(负载),Signature(签名),其格式如下:

xxxxx.yyyyy.zzzzz

令牌的例子


eyJhbGciOiJIUzI1NiJ9.eyJwZXJtaXNzaW9ucyI6InN5czpyZXM6Y3JlYXRlLHN5czpyZXM6cmV0cmlldmUiLCJleHAiOjE2MjY5MzIyNTksImlhdCI6MTYyNjkzMDQ1OSwidXNlcm5hbWUiOiJqYWNrIn0.SQrRS5nuID1Xv5GMvUgnr7xrVzB7GcRFrkNak-x16Mw



上面的令牌会解析为下面的三部分

Header部分

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。


{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(简写HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后,将这个 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

Payload部分

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT规范中规定了7个官方字段,供选用。
在输出态中
 iss (issuer):签发人
 exp (expiration time):过期时间
 sub (subject):主题
 aud (audience):受众
 nbf (Not Before):生效时间
 iat (Issued At):签发时间
 jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。


{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}


注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

Signature部分

Signature 部分是对前两部分的签名,其目的是防止数据被篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)


算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

JWT快速入门的案例

jwt的依赖

请添加图片描述

pom.xml文件


<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.2.RELEASE</version>
    </parent>

    <groupId>com.cy</groupId>
    <artifactId>03-jt-sourty-jwt</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--添加jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

</project>




在测试类的部分代码


package com.cy.jt;

import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest
public class SecurityJwtTest {
    //定义一个秘钥
    private String secret="AAABBBCCCEEE";
 //测试创建和解析token(令牌)的过程
    @Test
    void testCeat(){
       //获取用登录信息
        Map<String ,Object> map=new HashMap<>();//注意这里不能存密码
        map.put("username", "jack");//用户名
        map.put("permission", "sys:create,sys:res:retrieve");//权限信息


        //创建token(包含三部分信息:头信息,负载信息,签名信息)
       String token= Jwts.builder()
                .setSubject("jet")//设置主题里面的字符串可以随写
                .setClaims(map)//负载信息(存储用户登录信息)
                 .setExpiration(new Date(System.currentTimeMillis()+30*1000))//设置失效时间这里是30秒
                 .setIssuedAt(new Date())//签发时间
                 .signWith(SignatureAlgorithm.HS256,secret)//密码的加密的算法和加密盐
                .compact();//创建令牌即生成token
        System.out.println("token为"+token);
        //解析token的内容

    }

}





根据上面生成 的token在官网里进行解析

请添加图片描述

编写单元测试,实践Token对象的创建与解析,例如:


package com.cy.jt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest
public class SecurityJwtTest {
    //定义一个秘钥
    private String secret="AAABBBCCCEEE";
 //测试创建和解析token(令牌)的过程
    @Test
    void testCeat(){
       //获取用登录信息
        Map<String ,Object> map=new HashMap<>();//注意这里不能存密码
        map.put("username", "jack");//用户名
        map.put("permission", "sys:create,sys:res:retrieve");//权限信息

       //在登录时服务器会创建一个token
        //创建token(包含三部分信息:头信息,负载信息,签名信息)
       String token= Jwts.builder()
                .setSubject("jet")//设置主题里面的字符串可以随写
                .setClaims(map)//负载信息(存储用户登录信息)
                 .setExpiration(new Date(System.currentTimeMillis()+30*1000))//设置失效时间这里是30秒
                 .setIssuedAt(new Date())//签发时间
                 .signWith(SignatureAlgorithm.HS256,secret)//密码的加密的算法和加密盐
                .compact();//创建令牌即生成token
        System.out.println("token为"+token);



        //解析token的内容
        //在登录成功之后再进行访问是需要解析token,看看是否有权限
        Claims body= Jwts.parser()//获取解析器对象
                //设置解析时使用的密钥
                        .setSigningKey(secret)
                          //获取token中的负载
                        .parseClaimsJws(token)
                         //获取具体负载内容
                         .getBody();
        System.out.println(body);





    }

}


创建JWT工具类

为了简化JWT在项目中的应用,我们通常会构建一个工具类,对token的创建和解析进行封装,例如:
由于项目上线之后测试类就会没有所以上一个测试类的代码还可以写为下面的情况,但是在测试的时候还需要测试类的内容和下面的内容一起都需要写



package com.cy.jt.security.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.Map;
/**基于Jwt规范创建和解析token的工具类*/
public class JwtUtils {
    private static String secret="AAABBBCCCDDDEEE";
    /**基于负载和算法创建token信息*/
    public static String generatorToken(Map<String,Object> map){
       return Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis()+30*60*1000))
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();//签约,创建token
    }
    /**解析token获取数据*/
    public static Claims getClaimsFromToken(String token){
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
    /**判定token是否失效*/
    public static boolean isTokenExpired(String token){
       Date expiration=getClaimsFromToken(token).getExpiration();
       return expiration.before(new Date());
    }
    //.....

}



JWT在项目中的应用

AuthController 认证服务,主要功能是用户登录之后会将token在客户端进行存储,需要的时候在将token传到服务端 ,然后服务端将token进行校验

在controller层的代码,定义AuthController用于处理登录认证业务,代码如下:


package com.cy.jt.security.controller;

import com.cy.jt.security.util.JwtUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class AuthController {
    @RequestMapping("/login")
    public Map<String,Object> doLogin(String username,
                          String password){
        Map<String,Object> map=new HashMap<>();
        if("jack".equals(username)&&"123456".equals(password)){
            map.put("state","200");
            map.put("message","login ok");
            
//用户登录之后会将token在客户端进行存储,需要的时候在将token传到服务端 ,然后服务端将token进行校验
            Map<String,Object> claims=new HashMap<>();//负载信息
            claims.put("username",username);
            map.put("Authentication", JwtUtils.generatorToken(claims));
            return map;
        }else{
            map.put("state","500");
            map.put("message","login failure");
            return map;
        }
    }
}


在postman这个软件里执行的结果

在浏览器中只能get请求,在post请求中可以有多种请求

请添加图片描述

以上步骤完成的是下图中单点登录的 1,2,3,4

请添加图片描述

认证成功之后,进行之后的操作(比如查询资源等)

在controller层的代码


package com.cy.jt.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResourceController {

    @RequestMapping("/retrieve")
    public String doRetrieve(){
        //检查用户有没有登录
        //执行业务查询操作
        return "do retrieve resource success";
    }
    @RequestMapping("/update")
    public String doUpdate(){
        //检查用户有没有登录
        //执行业务查询操作
        return "do update resource success";
    }
}



请添加图片描述

在上一个代码中方法执行之前,检查是否登录,即就是拦截器

具体代码执行


package com.cy.jt.security.interceptor;

import com.cy.jt.security.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 令牌(token:ticker-通票)拦截器
 * 其中,HandlerInterceptor为Spring MVC中的拦截器,
 * 可以在Controller方法执行之前之后执行一些动作.
 * 1)Handler 处理器(Spring MVC中将@RestController描述的类看成是处理器)
 * 2)Interceptor 拦截器
 */
public class TokenInterceptor implements HandlerInterceptor {
    /**
     * preHandle在目标Controller方法执行之前执行的方法
     * @param handler 目标Controller对象
     * postHandle方法执行之后执行的方法
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        System.out.println("==preHandler==");
        //http://localhost:8080/retrieve?Authentication=ASDASDASFASF
        
/**
http://localhost:8080/update?Authentication=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjczNTgwNDIsImlhdCI6MTYyNzM1NjI0MiwidXNlcm5hbWUiOiJqYWNrIn0.I2KoyBqglYLZ_C0sLMdUFzDqceCwII7nzC6mTBipo28
*/
       String token= request.getParameter("Authentication");//这个方法使用请求URL进行访问   
       
       // String token=request.getHeader("Authentication");//这个方法使用请求头(header)进行访问
        //判定请求中是否有令牌    即就是图中的五步和六步
        if(token==null||"".equals(token))
            throw new RuntimeException("please login");
        //判定令牌是否已经过期
        boolean flag=JwtUtils.isTokenExpired(token);
        if(flag)
        throw new RuntimeException("login timeout,please login");
        return true;//true表示放行,false表示拦截到请求以后,不再继续传递
    }
}

在上一个代码添加到执行联中即写一个配置类, 即一个执行链,因为需要将拦截器加到执行链


package com.cy.jt.security.config;

import com.cy.jt.security.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 定义Spring Web MVC 配置类
 */
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
    /**将拦截器添加到spring mvc的执行链中
     * @param registry 此对象提供了一个list集合,可以将拦截器添加到集合中
     * */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器()里面的内容是自己写的拦截器类
         registry.addInterceptor(new TokenInterceptor())
                 //配置要拦截的url 即拦截controller层的url
                 .addPathPatterns("/retrieve","/update");
    }
}





资源访问测试
第一步:启动认证服务器,通过postman进行登陆认证,例如:
在这里插入图片描述

请求头的方式进行访问

在这里插入图片描述

总结(Summary)

本章节重点讲解了JWT产生的背景,它的构成和项目中的基本应用,需要在实践中进行分析和理解.

重难点分析

JWT 诞生的背景?(分布式架构应用平台下无状态会话时,规范令牌(通票)数据格式)
JWT 规范定义的数据格式?(头,负载-详细内容,签名,思考一篇文章的构成,)
JWT 规范下JAVA相关API的应用?(jjwt依赖-Jwts)
基于JWT规范下JAVA API 创建令牌,解析令牌

FAQ分析

JWT 是什么?(一种规范的数据格式)

JWT规范中的数据格式有几部分构成?(3部分,前两部分会进行Base64编码,最会基于签名算法加密)
JWT的负载(Payload-存储实际用户信息的部分)部分可以自定义吗?(Claims)
JWT令牌对象一般是在哪里创建?(服务端,可以创建令牌以后,响应到客户端)
JWT令牌假如要存储在客户端你会存储在哪里?(Cookie,localStorage(持久性存储 ,即无论在哪个窗口拿到这个数据),sessionStorage(回话窗口存储,只在当前窗口进行存储))
JWT令牌以怎样的方式有客户端传递到服务端?(请求参数,请求头)
Bug分析
创建token和解析token时一定要相同的密钥
Token过期了

Base64解密操作



package com.cy.jt.security;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Base64;

@SpringBootTest
public class Base64Tests {
    @Test
    void testEncodeDecode(){
        //1.定义目标字符串(对这个字符串进行编解码)
        String header="www.tedu.cn";
        //2.构建Base64编码对象Encoder
        Base64.Encoder encoder
                = Base64.getEncoder();//Encoder编码对象
        //3.对目标字符串进行编码
        String encodeStr =
                encoder.encodeToString(header.getBytes());
        System.out.println(encodeStr);//d3d3LnRlZHUuY24=
        //4.对已编码的对象进行解码
        //4.1获取一个解码对象(Decoder 解码对象)
        Base64.Decoder decoder = Base64.getDecoder();
        //4.2执行解码操作
        byte[] bytes=decoder.decode(encodeStr);
        //4.3构建字符串
        System.out.println(new String(bytes));
    }
}


创建通用工程

背景分析

当多个项目都有一部分公共资源需要重复编写时,我们可以创建一个公共工程,在这个工程中创建共性对象和依赖.其它工程需要时直接引用即可

初始化工程
第一步:在pom.xml文件中添加项目依赖.


 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>



第二步:拷贝工具类

将sso-auth工程中的WebUtils,JwtUtils拷贝到sso-common工程的sso.util包中

创建跨域配置类

在前后端分离工程中,当通过前端工程访问认证服务和资源服务时,需要进行跨域配置,例如:


package sso.config;

@Configuration
public class CorsFilterConfig {
    /**服务端过滤器层面的跨域设计*/
    @Bean
    public FilterRegistrationBean<CorsFilter> filterFilterRegistrationBean(){
        //1.对此过滤器进行配置(跨域设置-url,method)
        UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
        CorsConfiguration config=new CorsConfiguration();
        config.addAllowedHeader("*");//所有请求头信息
        config.addAllowedMethod("*");//所有请求方式,post,delete,get,put,....
        config.addAllowedOrigin("*");//所有请求参数
        config.setAllowCredentials(true);//所有认证信息,例如cookie
        //2.注册过滤器并设置其优先级
        configSource.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> fBean=
                new FilterRegistrationBean(
                        new CorsFilter(configSource));
        //设置此过滤器的优先级最高
        fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return fBean;
    }
}




引用通用工程

第一步:删除sso-auth,sso-resource 工程下的util包
第二步:删除sso-auth,sso-resource 工程中的公共依赖
第三步:在sso-auth,sso-resource工程中添加通用工程依赖


<dependency>
        <groupId>com.cy.jt</groupId>
        <artifactId>sso-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

启动工程测试

打开postman进行重新登录,访问资源测试.

创建前端工程

背景分析

我们做后端,一般在测试时直接基于postman进行访问就可以,为了更好理解前后端通讯过程,我们暂且基于springboot工程构建一个前端工程.

创建前端工程

在这里插入图片描述

初始化工程

第一步:添加web依赖,代码如下:


<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


第二步:创建application.yml配置文件,代码如下:


server:
  port: 80


第三步:创建启动类,代码如下:


package sso;
@SpringBootApplication
public class UIApplication {
    public static void main(String[] args) {
        SpringApplication.run(UIApplication.class,args);
    }
}


创建静态页面

将课前资料中的static目录直接拷贝到项目中的resource目录下.

登录页面login.html内容如下:

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>login</title>
</head>
<body>
<div class="container"id="app">
    <h3>Please Login</h3>
    <form>
        <div class="mb-3">
            <label for="usernameId" class="form-label">Username</label>
            <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="passwordId" class="form-label">Password</label>
            <input type="password" v-model="password" class="form-control" id="passwordId">
        </div>
        <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
    </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm=new Vue({
        el:"#app",//定义监控点,vue底层会基于此监控点在内存中构建dom树
        data:{ //此对象中定义页面上要操作的数据
            username:"",
            password:""
        },
        methods: {//此位置定义所有业务事件处理函数
            doLogin() {
                //1.定义url
                let url = "http://localhost:8081/login"
                //2.定义参数

                var params = new URLSearchParams()
                params.append('username',this.username);
                params.append('password',this.password);
                //3.发送异步请求
                axios.post(url, params).then((response) => {
                   debugger
                   var data=response.data;
                   console.log(data);
                    if (data.state == 200) {
                        alert("login ok");
                        window.localStorage.setItem("token",data.token);
                        location.href="/index.html"
                    } else {
                        alert(response.message);
                    }
                })
            }
        }
    });
</script>
</body>
</html>


登录成功页面index.html,代码如下:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="appIndex">
<h1>Index Page <a href="#"  @click="doLogout()">Logout</a></h1>
<h2>CRUD(Create,Retrieve,Update,Delete) Operation</h2>
<ul>
    <li><a href="#" @click="doCreate()">Create(添加-insert)</a></li>
    <li><a href="#">Retrieve(查询-select)</a></li>
    <li><a href="#" @click="doUpdate()">Update(更新-update)</a></li>
    <li><a href="#">Delete(删除-delete)</a></li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    let vm=new Vue({
        el:"#appIndex",//定义监控点,vue底层会基于此监控点在内存中构建dom树
        methods: {//此位置定义所有业务事件处理函数
            doCreate() {
                //1.定义url
                let url = "http://localhost:8091/doCreate"
                //3.发送异步请求
                let token=localStorage.getItem("token");
                axios.get(url,{headers:{"token":token==null?"":token}}).then((response) => {
                   alert(response.data)
                })
            },
            doUpdate() {
                //1.定义url
                let url = "http://localhost:8091/doUpdate"
                //3.发送异步请求
                axios.get(url,{headers:{"token":localStorage.getItem("token")}}).then((response) => {
                    debugger
                    alert(response.data.message);
                })
            },
            doLogout() {
                //移除token
                localStorage.removeItem('token');
                //跳转到登录页面
                location.href="/login.html";
            },
        }
    });
</script>
</body>
</html>



工程访问测试

启动sso-auth,sso-resource,sso-ui 工程,然后访问http://localhost/login.html进行登录

在这里插入图片描述

输入正确的账号,密码执行登录,登录成功以后跳转到如下页面.

在这里插入图片描述

然后,对create,update选项进行访问,检测输出结果.
资源访问过程分析

从登录认证,到资源访问,其过程如下:

在这里插入图片描述

总结(summary)

重难点分析

单体架构中的登录设计
分布式架构中的单点登录设计
SpringSecutiry在认证服务器和资源服务器中的配置
JWT在认证授权系统中的应用

常见FAQ

传统单体架构方式的会话是是如何实现的?(Cookie+Session)
传统单体架构方式的登录在分布式架构中有什么缺陷?(cookie的跨域,session的共享)
分布式架构中的认证方式如何实现?(方式1:Session数据持久化,方式2:认证服务器创建令牌,客户端
存储令牌,资源服务端解析令牌)
认证服务器用来做什么?(创建并响应令牌,设置认证机制-登录成功,失败,没有认证)
认证服务器的令牌基于什么规范进行创建?(JWT-JSON Web Token)
资源服务器你要做什么?(解析令牌,存储用户认证和权限信息,提供有条件的资源访问)

BUG分析

400
401
403

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一个基于Spring Boot、Spring Security、JWT和OAuth2的示例项目,实现了用户注册、登录、注销、刷新令牌、访问受保护资源等功能。 ## 技术栈 - Spring Boot 2.5.4 - Spring Security 5.5.1 - Spring Data JPA 2.5.4 - MySQL 8.0.26 - JWT 0.11.2 - OAuth2 2.5.4 - Lombok 1.18.20 ## 数据库配置 在MySQL数据库中新建一个名为`springboot_security_jwt_oauth2`的数据库,执行以下SQL语句创建用户表: ```sql CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(255) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', `create_time` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; ``` ## 项目结构 ``` ├── src/main/java │ └── com │ └── example │ └── demo │ ├── DemoApplication.java │ ├── config │ │ ├── JwtConfig.java │ │ ├── MyPasswordEncoder.java │ │ └── SecurityConfig.java │ ├── controller │ │ ├── LoginController.java │ │ └── UserController.java │ ├── dao │ │ ├── UserRepository.java │ │ └── UserRoleRepository.java │ ├── entity │ │ ├── User.java │ │ └── UserRole.java │ ├── exception │ │ ├── JwtAuthenticationException.java │ │ └── UserNotFoundException.java │ ├── service │ │ ├── AuthService.java │ │ ├── UserService.java │ │ └── impl │ │ ├── AuthServiceImpl.java │ │ └── UserServiceImpl.java │ ├── util │ │ ├── JwtTokenUtil.java │ │ └── JwtUserDetailsService.java │ └── web │ ├── JwtAuthenticationEntryPoint.java │ ├── JwtAuthenticationFilter.java │ ├── JwtAuthorizationFilter.java │ ├── RestResponse.java │ └── UserNotFoundExceptionHandler.java └── src/main/resources ├── application.properties ├── static └── templates ``` - `config`:Spring Security和JWT的配置类 - `controller`:控制器类,处理请求和响应 - `dao`:数据访问层,使用Spring Data JPA实现 - `entity`:实体类 - `exception`:异常类 - `service`:服务层接口和实现类 - `util`:工具类,包括JWT生成和解析、用户认证等 - `web`:Web相关类,包括异常处理、JWT过滤器等 ## API文档 ### 用户注册 - URL:`/api/register` - Method:POST - Request: ```json { "username": "test", "password": "123456" } ``` - Response: ```json { "code": 200, "message": "注册成功", "data": { "id": 1, "username": "test", "password": "$2a$10$8uFJ3zZB.Sd7K3YB2K3Y/OfVhF4oJXeS3j0R2A3RG1c2UJWuXkSdC", "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00" } } ``` ### 用户登录 - URL:`/api/login` - Method:POST - Request: ```json { "username": "test", "password": "123456" } ``` - Response: ```json { "code": 200, "message": "登录成功", "data": { "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA", "tokenType": "Bearer", "expiresIn": 3600 } } ``` ### 用户注销 - URL:`/api/logout` - Method:POST - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA ``` - Response: ```json { "code": 200, "message": "注销成功" } ``` ### 刷新令牌 - URL:`/api/refresh` - Method:POST - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA ``` - Response: ```json { "code": 200, "message": "刷新令牌成功", "data": { "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQxMzQsImlhdCI6MTYzMjk2MDUzNH0.2hWq8dLJ7s9G6MqQ8Gg7kNvGzeOaJQFb4eBZ9RcB6N8lP3kglz8W_KXMh8r4oJZkzy5HOVZrB5YSEKNxZyY5lg", "tokenType": "Bearer", "expiresIn": 3600 } } ``` ### 获取当前用户信息 - URL:`/api/user/info` - Method:GET - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQxMzQsImlhdCI6MTYzMjk2MDUzNH0.2hWq8dLJ7s9G6MqQ8Gg7kNvGzeOaJQFb4eBZ9RcB6N8lP3kglz8W_KXMh8r4oJZkzy5HOVZrB5YSEKNxZyY5lg ``` - Response: ```json { "code": 200, "message": "获取用户信息成功", "data": { "id": 1, "username": "test", "password": null, "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00", "authorities": [ { "authority": "ROLE_USER" } ] } } ``` ### 获取所有用户信息 - URL:`/api/user/all` - Method:GET - Response: ```json { "code": 200, "message": "获取所有用户信息成功", "data": [ { "id": 1, "username": "test", "password": null, "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00", "authorities": [ { "authority": "ROLE_USER" } ] } ] } ``` ## 完整代码 完整代码请参考[GitHub](https://github.com/zhongshijun/springboot-security-jwt-oauth2)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值