首页 > 其他分享 >SpringSecurity + JWT实战(前后端分离)

SpringSecurity + JWT实战(前后端分离)

时间:2024-08-05 20:26:21浏览次数:7  
标签:实战 String writer JWT JSON SpringSecurity token 服务器

1. 聊聊SpringSecurity+JWT

2. 前后端完全分离认证问题

3. JWT的原理

4. JWT的数据结构

4.1 Header

4.2 Payload

4.3 Signature

5. JWT的实战

1.引入jar

2. 创建jwt的工具类

​3. 登录成功

4. 登录失败

5. 权限不足

6. 未登录返回json数据


1. 聊聊SpringSecurity+JWT

先来聊一聊什么是SpringSecurity ,在上一篇文章中SpringSecurity已经聊过了,大家可以去看看接下来我们聊聊什么是JWT

Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 官网: JSON Web Token Introduction - jwt.io

2. 前后端完全分离认证问题

互联网服务离不开用户认证。一般流程是下面这样:

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录 时间等等。
  3. 服务器向用户返回一个session_id,写入用户的Cookie。
  4. 用户随后的每一次请求,都会通过Cookie,将session_id传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是前后端分离的服务导向架构,就要求session 数据共享,每台服务器都能够读取session,。

举例来说,A网站和B网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现? 一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大[]。另外,持久层万一挂了,就会单点失败。 另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器,而JWT就是这种方案的一个代表。

3. JWT的原理

JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,就像下面这样。

{ "姓名":"张三", "角色":"管理员", "到期时间":"2022年8月1日0点0分"

} 以后,用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。 服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

4. JWT的数据结构

实际的 JWT大概就像下面这样:

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT内部是没有换行的,这里只是为了便于展示,将它写成了几行。 JWT的三个部分依次如下。 Header (头部)

Payload(负载 载荷)

Signature(签名)

写成一行,就是下面的样子。

Header.Payload.Signature

4.1 Header

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

{ "alg": "HS256",

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

4.2 Payload

Payload 部分也是一个JSON对象,==用来存放实际需要传递的数据==。JWT规定了7个官方字段,供选用。 iss (issuer):签发人 exp (expiration time):过期时间

sub (subject):主题 aud (audience):受众

nbf (Not Before):生效时间

iat (lssued At):签发时间

jti (JWT ID):编号 除了官方字段,==你还可以在这个部分定义自己的字段==,下面就是一个例子。

{

"sub": "1234567890", "name" : "John Doe",

“userid”:2

"admin": true } 注意,JWT 默认是不加密的,任何人都可以读到,所以不要把==秘密信息【密码】==放在这个部分。这个JSON 对象也要使用Base64URL 算法转成字符串。

4.3 Signature

Signature部分是对前两部分的签名,防止数据篡改。 首先,需要指定一个==密钥(secret)==。这个密钥只有==服务器才知道==,不能泄露给用户。然后,使用Header里面指定的==签名算法(默认是 HMAC SHA256)==,按照下面的公式产生签名。 HMACSHA256( base64UrlEncode(header) + ".”"+base64UrlEncode(payload), secret) 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

5. JWT的实战

客户端收到服务器返回的JWT,可以储存在Cookie里面,也可以储存在 localStorage。SessionStorage 此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP==请求的头==信息Authorization字段里面。

1.引入jar

 <!--引入jwt的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

2. 创建jwt的工具类

public class JWTUtil {
    private static String key="ykqLDH";
    //通过jwt创建token令牌
    public static String createToken(Map<String,Object> map){
        Map<String,Object> head=new HashMap<>();
        head.put("alg","HS256");
        head.put("typ","JWT");
​
        Date date=new Date();//发布日期
        Calendar instance = Calendar.getInstance();//获取当前时间
        instance.set(Calendar.SECOND,7200);//在当前时间的基础上添加7200秒
        Date time = instance.getTime();
​
        String token = JWT.create()
                .withHeader(head) //设置头
                .withIssuedAt(date) //设置发布日期
                .withExpiresAt(time) //设置过期时间
                .withClaim("userinfo", map) //设置个人信息
                .sign(Algorithm.HMAC256(key));//签名
​
        return token;
    }
​
    //校验token
    public static boolean verify(String token){
        Verification require = JWT.require(Algorithm.HMAC256(key));
        try {
            require.build().verify(token);
            return true;
        }catch (Exception e){
            System.out.println("token错误");
            return false;
        }
    }
​
    //根据token获取自定义的信息
    public static Map<String,Object> getInfo(String token,String mykey){
        JWTVerifier build = JWT.require(Algorithm.HMAC256(key)).build();
        Claim claim = build.verify(token).getClaim(mykey);
        return claim.asMap();
    }
}

​3. 登录成功

登录成功后会返回JSON数据

    private AuthenticationSuccessHandler successHandler(){
        return (httpServletRequest, httpServletResponse, authentication) -> {
            //设置响应的编码
            httpServletResponse.setContentType("application/json;charset=utf-8");
            //获取输出对象
            PrintWriter writer = httpServletResponse.getWriter();
            //返回json数据即可
            Map<String,Object> map=new HashMap<>();
            map.put("username",authentication.getName());
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            //获取权限
            List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());

            map.put("permissions",collect);
            String token = JWTUtil.createToken(map);

            //返回一个统一的json对象
            R r=new R(200,"登录成功",token);
            //转换为json字符串
            String jsonString = JSON.toJSONString(r);
            //servlet
            writer.println(jsonString);
            writer.flush();
            writer.close();
        };
    }

4. 登录失败

    private AuthenticationFailureHandler failureHandler(){

        return (httpServletRequest, httpServletResponse, e) -> {

            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            R r=new R(500,"登录失败",e.getMessage());
            String jsonString = JSON.toJSONString(r);
            writer.println(jsonString);
            writer.flush();
            writer.close();
        };

    }

5. 权限不足

    //权限不足时的处理函数
    private AccessDeniedHandler accessDeniedHandler(){
        return (httpServletRequest, httpServletResponse, e) -> {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            R r=new R(403,"权限不足",e.getMessage());
            String jsonString =JSON.toJSONString(r);
            writer.println(jsonString);
            writer.flush();
            writer.close();
        };
    }

6. 未登录返回json数据

这里需要自定义一个过滤器。

@Component //交于spring容器管理
public class LoginFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setContentType("application/json;charset=utf-8");
        String path = request.getRequestURI();
        String method = request.getMethod();
        if("/login".equals(path)&&"POST".equals(method)){
            //放行
            filterChain.doFilter(request,response);
            return;
        }

        //1. 从请求头中获取token令牌
        String token = request.getHeader("token");
        //2. 判断是否token为null
        if(StringUtils.isEmpty(token)){
            PrintWriter writer = response.getWriter();
            //3. 返回一个未登录的json数据.
            R r=new R(500,"未登录",null);
            String jsonString = JSON.toJSONString(r);
            writer.write(jsonString);
            writer.flush();
            writer.close();
            return;
        }

        //3. 验证token
        if(!JWTUtil.verify(token)){
            PrintWriter writer = response.getWriter();
            //3. 返回一个未登录的json数据.
            R r=new R(500,"token失效",null);
            String jsonString = JSON.toJSONString(r);
            writer.write(jsonString);
            writer.flush();
            writer.close();
            return;
        }
         //把当前用户的信息封装到Authentication对象中
        SecurityContext context = SecurityContextHolder.getContext();
        //Object principal,账号
        // Object credentials,密码 null
        //			Collection<? extends GrantedAuthority> authorities:权限
        Map<String, Object> userinfo = JWTUtil.getInfo(token, "userinfo");
        Object username = userinfo.get("username");
        List<String> permissions = (List<String>) userinfo.get("permissions");
        List<SimpleGrantedAuthority> collect = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,null,collect);
        context.setAuthentication(authenticationToken);
        //4. 放行
        filterChain.doFilter(request,response);

    }
}

修改配置类

标签:实战,String,writer,JWT,JSON,SpringSecurity,token,服务器
From: https://blog.csdn.net/As_Yua/article/details/140934005

相关文章

  • basic_pentesting_1靶场实战[超详细]
    下载地址:https://download.vulnhub.com/depth/DepthB2R.ova一、靶场配置网卡模式设置为nat二、主机探测与端口扫描nmap192.168.121.0/24 ip地址为192.168.121.186,开启了21、22、80端口扫描一下端口信息nmap192.168.121.186-sV-A-p- 访问一下80web服务看看......
  • DepthB2R靶场实战【超详细】
    下载地址:https://download.vulnhub.com/depth/DepthB2R.ova一、主机探测与端口扫描nmap192.168.121.0/24 发现开启了8080端口我们对该端口进行扫描nmap192.168.121.187-p--A-Pn-sV 访问其8080web服务二、getshell目录扫描dirsearch-uhttp://192.168.121......
  • 基于人工智能的代码分析与 Bug 检测实战
    简介在人工智能还未盛行的时候,检测Bug通常是通过以下几种方式完成:研发编写单元测试。代码扫描工具,SonarQube、findbugs。测试人员进行集成测试。而现在其中的一部分工作,都可以通过人工智能提升效率,辅助开发与测试发现更多的问题,降低成本和提高软件质量。对应流程相......
  • 创新舞台的幕后:一位项目负责人的实战经验与应对策略
    创新舞台的幕后:一位项目负责人的实战经验与应对策略前言技术故障:预防与应对时间管理:精确控制意外中断:保持冷静参与者问题:灵活应对技术故障与主题偏离:清晰传达评委反应:积极互动个人状况:自我调节结语前言  在创新的浪潮中,每一位项目负责人都是勇敢的航海家,驾驭着团队......
  • AI绘画进阶 ComfyUI 实战教程:轻松给图片添加文字,附工作流教程使用
    大家好,我是设计师阿威在AI绘画中书写文字一直是个老大难的问题,直到SDXL的出现,文字生成才迎来转机,可以在提示词中指定一些英文字符,不过也是经常出错,生成中文就更加不可求了。本文介绍一种在图片中稳定生成文字的方法,可以自定义字体、颜色、大小,以及文字书写方向,有兴趣的同......
  • 用go实现JWT
    JWTISONWebToken,通过数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全的传输信息。JWT最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求的之前,都要先进行JWT安全校验,通过之后再进行处理三部分组成:Header { 'type':'j......
  • Django5+Vue3:OA系统前后端分离项目实战-异步优化Ajax请求(12)
    Django5+Vue3系列文章前言本节开始,全文仅对会员开放。若点赞和收藏数量超过100,全文将免费开放。此项目采用Django框架的5.0.7版本进行开发。Django5.0支持的Python版本为3.10、3.11和3.12。OA系统系列文章将持续更新,直至项目的Docker部署阶段。专栏链接:......
  • Django5+Vue3:OA系统前后端分离项目实战-Pinia用户和token信息管理(11)
    Django5+Vue3系列文章前言本节开始,全文仅对会员开放。若点赞和收藏数量超过100,全文将免费开放。此项目采用Django框架的5.0.7版本进行开发。Django5.0支持的Python版本为3.10、3.11和3.12。OA系统系列文章将持续更新,直至项目的Docker部署阶段。专栏链接:......
  • 【包邮送书】LaTeX 从入门到实战
    欢迎关注博主Mindtechnist或加入【智能科技社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关注公粽号《机器和智能》回复关键词“python项目实战......
  • “命令行利器:sort、uniq、date、ntpdate详解与实战“
    当今操作系统中的命令行工具不仅是管理和调试系统的利器,也是程序员和系统管理员的重要工具。在Unix和类Unix系统中,sort、uniq、date和ntpdate是几个常用的命令,它们各自拥有独特的功能,可以在日常工作中极大地提高效率。本文将深入探讨这些命令的用法和实际应用。1. sort命令s......