基于注解和jwt实现权限管理

news2024/10/7 7:25:11

第一步:创建项目

添加Maven依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<!--        集成Thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>net.sourceforge.nekohtml</groupId>
    <artifactId>nekohtml</artifactId>
    <version>1.9.22</version>
</dependency>

在resources/static目录下创建js文件夹,然后在其中添加jquery3.3.1.js文件

在这里插入图片描述

编辑application.yml:

server:
    port: 80
    servlet:
        context-path: /jwt
spring:
    thymeleaf:
        #前缀,也就是模板存放的路径
        prefix: classpath:/templates/
        #编码格式
        encoding: UTF-8
        check-template-location: false
        #关闭缓存,不然无法看到实时页面
        cache: false
        #后缀
        suffix: .html
        #设置不严格的html
        mode: HTML
        servlet:
            content-type: text/html

第二步:创建表示用户的实体类:

@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -8390887042859558153L;
    private Integer id;
    private String username;
    private String password;
}

第三步:通用类:

ResultCodeEnum.java

public enum ResultCodeEnum {
    /* 成功状态码 */
    SUCCESS(200,"操作成功!"),
    /* 错误状态码 */
    FAIL(-1,"操作失败!"),
      /* 用户错误:20001-29999*/
    USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
    /* 权限错误:70001-79999 */
    PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
    PERMISSION_TOKEN_INVALID(70006, "无效token"),
    PERMISSION_SIGNATURE_ERROR(70007, "签名失败");

    int code;    //操作代码
    String msg;    //提示信息
    ResultCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

Result.java

@Getter
@AllArgsConstructor
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    public Result<T> setCode(int code) {
        this.code = code;
        return this;
    }
    public Result<T> setMsg(String msg) {
        this.msg = msg;
        return this;
    }
    public Result<T> setData(T data) {
        this.data = data;
        return this;
    }
    public Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Result(ResultCodeEnum resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }
}

ResultUtil.java

public class ResultUtil {
    // 操作成功,只返回结果码和提示信息
    public static Result success(ResultCodeEnum resultCodeEnum) {
        return new Result(ResultCodeEnum.SUCCESS);
    }
    // 操作成功,只返回结果码和具体的数据,但不返回提示信息
    public static Result success(int code, String msg) {
        return new Result(code, msg);
    }
    // 操作成功,返回具体的数据、结果码和提示信息
    public static Result success(Object data) {
        Result<Object> result = new Result(ResultCodeEnum.SUCCESS);
        result.setData(data);
        return result;
    }
    // 操作成功,返回具体的数据、结果码和提示信息
    public static Result success(Integer code, String msg, Object data) {
        return new Result<>(code,msg,data);
    }
    // 操作成功,只返回结果码和提示信息
    public static Result fail(ResultCodeEnum resultCodeEnum) {
        return new Result(ResultCodeEnum.FAIL);
    }
    // 操作失败,只返回指定的结果码和具体的数据,但不返回提示信息
    public static Result fail(int code, String msg) {
        return new Result(code, msg);
    }
    // 操作失败,返回具体的数据、结果码和提示信息
    public static Result fail(Object data) {
        Result<Object> result = new Result(ResultCodeEnum.FAIL);
        result.setData(data);
        return result;
    }
    // 操作失败,返回具体的数据、结果码和提示信息
    public static Result fail(Integer code, String msg, Object data) {
        return new Result(code,msg,data);
    }
}

第四步:创建常量类:

public interface JwtConst {
    //JWT签发者
    String JWT_ID = "098f6bcd4621d373cade4e832627b4f6";
    // 密钥, 经过Base64加密, 可自行替换
    String JWT_SECRECT = "MDk4ZjZiY2Q0NjIxZD";
    // 过期时间,单位毫秒
    int JWT_TTL = 60*60*1000;
}

第五步:Jwt工具类:

public class JwtUtil {
    private static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    // 创建JWT
    public static String createJWT(String id, String subject, int ttl) {
        Calendar calendar = Calendar.getInstance();
        JwtBuilder builder = Jwts.builder()
                .setId(id)  //JWT唯一标识
                .setIssuedAt(calendar.getTime()) //签发时间
                .setSubject(subject) //JWT的主题,比如JSON类型的User对象
                .signWith(key);
        calendar.add(Calendar.MINUTE, ttl);
        builder.setExpiration(calendar.getTime()); //过期时间
        return builder.compact();
    }
    public static Claims parseJwt(String jwt) {
        Claims claims = Jwts.parserBuilder().setSigningKey(key)
.build().parseClaimsJws(jwt).getBody();
        return claims;
    }
    public static Result<Claims> validateJwt(String jwt) {
        Result<Claims> result = null;
        try {
            Claims claims = Jwts.parserBuilder().setSigningKey(key)
.build().parseClaimsJws(jwt).getBody();
            result = ResultUtil.success(ResultCodeEnum.SUCCESS)
                    .setData(claims);
        } catch (ExpiredJwtException e) {
            result = ResultUtil.fail(ResultCodeEnum.PERMISSION_TOKEN_EXPIRED);
            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            result = ResultUtil.fail(ResultCodeEnum.PERMISSION_TOKEN_INVALID);
            e.printStackTrace();
        } catch (MalformedJwtException e) {
            result = ResultUtil.fail(ResultCodeEnum.PERMISSION_TOKEN_INVALID);
            e.printStackTrace();
        } catch (SignatureException e) {
            result = ResultUtil.fail(ResultCodeEnum.PERMISSION_SIGNATURE_ERROR);
            e.printStackTrace();
        }
        return result;
    }
    public static String createSubject(User user) {
        return JSON.toJSONString(user);
    }
}

第六步:自定义全局异常及处理类

全局异常

@Data
@NoArgsConstructor
public class GlobalException extends RuntimeException {
    private int code;
    private String msg;
    public GlobalException(int code,String msg) {
        super(msg);
        this.code =code;
        this.msg = msg;
    }
    public GlobalException(ResultCodeEnum resultCode){
        super(resultCode.getMsg());
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }
}

处理类

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    // 处理自定义全局异常
    @ExceptionHandler(GlobalException.class)
    public Result handleException(GlobalException e) {
        // 打印异常信息
        log.error("### 异常信息:{} ###", e.getMessage());
        return new Result(e.getCode(),e.getMsg());
    }
    // 处理所有不可知的异常
    @ExceptionHandler(Exception.class)
    public Result handleOtherException(Exception e) {
        //打印异常堆栈信息
        e.printStackTrace();
        // 打印异常信息
        log.error("### 未知的异常:{} ###", e.getMessage());
        return new Result(1,"系统未知异常");
    }
}

第七步:

@Log4j2
public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                log.info("当前所访问的资源不需要登录");
                return true;
            }
        }

        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken loginToken = method.getAnnotation(LoginToken.class);
            if (loginToken.required()) {
                // 从 http 请求头中取出 token
                String jwt = request.getHeader("Authorization");
                //如果header中没有则从Request中获取
                if (jwt == null || jwt.length() == 0) {
                    jwt = request.getParameter("Authorization");
                }
                //如果没有获取到token
                if (jwt == null || jwt.length() == 0) {
                    log.info("用户未登录,请先登录");
                    throw new GlobalException(ResultCodeEnum.USER_NOT_LOGGED_IN);
                }
                String token = JSON.parseObject(jwt).getString("token");

                Result<Claims> result = JwtUtil.validateJwt(token);

                if (result.getCode() == 200) {
                    Claims claims = result.getData();
                    String subject = claims.getSubject();
                    User user = JSON.parseObject(subject, User.class);
                    log.info("jwt 解析成功 {}", user);
                    再次验证,解析出来的用户在数据库/Redis中是否存在,
                    //User temp = userService.selectByPrimaryKey(Integer.parseInt(userId));
                    //if (temp == null) {
                    //    log.info("用户不存在,请重新登录");
                    //    throw new RuntimeException("用户不存在,请重新登录");
                    //}
                    log.info("用户存在,登录成功");
                    return true;
                } else {
                    throw new GlobalException(ResultCodeEnum.PERMISSION_SIGNATURE_ERROR);
                }
            }
        }
        return false;
    }
}

第八步:配置类:

@Configuration //@Configuration会将该类作为一个SpringBean添加到IOC容器中
public class InterceptorConfig implements WebMvcConfigurer {
    // 配置跨域和支持restoful接口
    @Override
    public void addCorsMappings(CorsRegistry registry) {//跨域请求
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
// addInterceptor()需要一个实现HandlerInterceptor接口的拦截器实例
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");//用于设置拦截器的过滤路径规则
    }
    @Bean
    public JwtInterceptor authenticationInterceptor() {
        return new JwtInterceptor();
    }
}

第九步:自定义注解:

Logintoken.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
    boolean required() default true;
}

Passtoken.java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

第十步:创建Controller:

生成、解析JWT的Controller

@Controller
@RequestMapping("/jwt")
public class JWTController {
    @PassToken
    @RequestMapping("/login")
    @ResponseBody
    public Result<String> login(String username, String password) {
        if (username == null || password == null) {
            return new Result<String>(789, "用户名或密码不正确", null);
        }
        User user = new User(1001, username, password);
        String jwt = JwtUtil.createJWT(JwtConst.JWT_ID, JwtUtil.createSubject(user), JwtConst.JWT_TTL);
        System.out.println(jwt);
        return new Result<>(200, "请求成功!", jwt);
    }
    @LoginToken
    @RequestMapping("/parseJwt")
    @ResponseBody
    public User parseJwt(HttpServletRequest request) {
        String jwt = request.getHeader("Authorization");
        String token = JSON.parseObject(jwt).getString("token");
        Claims claims = JwtUtil.parseJwt(token);
        String subject = claims.getSubject();
        User user = JSON.parseObject(subject, User.class);
        System.out.println(user);
        return user;
    }
}

页面分发的Controller:

@Controller
public class DispatchController {
    @PassToken
    @GetMapping("/login")
    public String showLogin() {
        return "/login";
    }
    @PassToken
    @GetMapping("/showJwt")
    public String showJwt() {
        return "showJwt";
    }
}

第十一步:前端页面:

login.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面</title>
        <script src="js/jquery3.5.1.js" type="text/javascript"></script>
    </head>
    <body>
        <form id="loginForm">
            <input type="text" name="username" value="zhangsan"><br>
            <input type="password" name="password" value="123456">
        </form>
        <button id="btn">登录</button>
        <script>
            $(function () {
                $("#btn").click(function () {
                    $.ajax({
                        type: "POST",//请求方式
                        url: "jwt/login",  //请求地址
                        dataType: "JSON", //预期服务器端返回的数据的类型
                        data: $("#loginForm").serialize(),//数据,json字符串
                        success: function (result) { //请求成功
                            console.log(result);
                            if (result.code == 200) {
                                //获取令牌并保存到本地
                                localStorage.setItem("token", '{"token":"' + result.data + '"}');
                            }
                            if (result.code == 789) {
                                window.location = "login";
                            }
                        },
                        error: function (e) {  //请求失败,包含具体的错误信息
                            console.log(e.status);
                            console.log(e.responseText);
                        }
                    });
                });
            });
        </script>
    </body>
</html>

注:第一次点登录按钮时,无法将token数据保存在Application中,原因未找到,发现原因的朋友麻烦告诉我一声。

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
        <script src="js/jquery3.3.1.js" type="text/javascript"></script>
    </head>
    <body>
        首页启动时请求后台的解析Jwt的的请求
        <div id="sh"></div>
        <script>
            $(function () {
                $.ajax({
                    url: "jwt/parseJwt",
                    headers: {Authorization: localStorage.getItem("token")},
                    success:function (res) {
                        console.info(res);
                        $("#sh").text(JSON.stringify(res));
                    }
                })
            });
        </script>
    </body>
</html>

第十二步:部署运行查看结果:

  1. 访问http://localhost/jwt/login,在打开的login.html页面中单击登录按钮:
    在这里插入图片描述

注:第一次点登录按钮时,无法将token数据保存在Application中,原因未找到。
2. 访问http://localhost/jwt/index,在页面上会看到:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1011487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023 Google 开发者大会|Mobile开发专题追踪

文章目录 前言大会介绍涉及内容MobileWebAICloud Mobile开发专题多终端应用的开发适配大屏视频流可穿戴设备电视新的设计中心 构建高质量的应用高级相机和媒体功能用户的安全和隐私更精细的视觉体验 小结 前言 哈喽大家好&#xff0c;我是阿Q。近期&#xff0c;【2023 Google …

TVS二极管的小知识

TVS二极管的小知识 文章目录 TVS二极管的小知识1、工作原理2、TVS二极管参数详解3、选型指南4、TVS二极管选型注意事项 1、工作原理 TVS&#xff08;Transient Voltage Suppressors&#xff09;二极管&#xff0c;即瞬态电压抑制器&#xff0c;又称雪崩击穿二极管&#xff0c;…

LabelEdit DLSample

LabelEdit改成true SelectedNode.Text"" 关于图像和数据集的信息在字典DLDataset中表示&#xff0c;DLDataset充当一个数据库。更准确地说&#xff0c;它存储关于数据集和在关键样本下收集的单个样本的字典的一般信息。当需要实际的图像数据时&#xff0c;将为每个…

ClickHouse进阶(十六):clickhouse优化-表优化

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_大数据OLAP体系技术栈,Apache Doris,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

AD20多层板设计中的平电层设计规则

一般情况下的多层板设计非常复杂&#xff0c;尤其层叠的次序以及平电层的电源层设计&#xff0c;Gnd层的设计比较简单&#xff0c;不需要过多的关注&#xff0c;但是电源层的设计非常关键&#xff0c;常常让人感到无法下手的感觉&#xff0c;这里介绍一个简单的防盲很快的让你上…

云流化:XR扩展现实应用发展的一个新方向!

扩展现实的发展已经改变了我们工作、生活和娱乐的方式&#xff0c;而且这才刚刚开始。扩展现实 (Extended reality, XR) 涵盖了沉浸式技术&#xff0c;包括虚拟现实、增强现实和混合现实。从游戏到虚拟制作再到产品设计&#xff0c;XR 使人们能够以前所未有的方式在计算机生成的…

解决方案 | 法大大电子签加速高校七大场景全面实现数字化

随着互联网技术的普及和发展&#xff0c;数字化校园建设在各高校正如火如荼地开展。无纸化办公是数字化校园建设中非常重要的一项内容&#xff0c;如何更好地在高校实现无纸化办公&#xff0c;为学校的教学、科研及管理服务提高工作效率、规范工作流程&#xff0c;是各高校都应…

window server事件ID说明

重启&#xff1a;1074 6013&#xff1a;系统运行时间 6008&#xff1a;非正常关机或者意外关机 WindowsServer2012R2事件id6008什么意思&#xff1f; 在Windows Server 2012 R2中&#xff0c;事件ID 6008是一个系统事件&#xff0c;它通常表示系统的非正常关机或意外关机。当系…

阿里镜像站下载Centos7

CentOS主流版本有7和8&#xff0c;我们这里选择centos7进行安装&#xff0c;因为现在8已经不更新了&#xff0c;CentOS 8 是一个介于实验性版本和稳定商业版本之间的产物&#xff0c;是最后一个版本&#xff0c;一般情况下&#xff0c;我们更追求稳定性&#xff0c;以我们选择 …

幸福里基于 Flink Paimon 的流式数仓实践

摘要&#xff1a;本文整理自字节跳动基础架构工程师李国君&#xff0c;在 Streaming Lakehouse Meetup 的分享。幸福里业务是一种典型的交易、事务类型的业务场景&#xff0c;这种业务场景在实时数仓建模中遇到了诸多挑战。本次分享主要介绍幸福里业务基于 Flink & Paimon …

常用数据库的 API - 开篇

API API 这个词在大多数人看来可能和 CNS 差不多&#xff0c;前者天天听说就是用不上&#xff0c;后者天天读就是发不了。 不过&#xff0c;通过今天的一个简短介绍&#xff0c;今后 API 这个东西你就用上了&#xff0c;因为在文章最后我将会展示一个最最基础且高频的 API 使…

小白如何选择阿里云服务器配置?CPU内存带宽系统盘选择

阿里云服务器配置选择_CPU内存/带宽/存储配置_小白指南&#xff0c;阿里云服务器配置选择方法包括云服务器类型、CPU内存、操作系统、公网带宽、系统盘存储、网络带宽选择、安全配置、监控等&#xff0c;阿小云分享阿里云服务器配置选择方法&#xff0c;选择适合自己的云服务器…

4.3 IAT Hook 挂钩技术

IAT&#xff08;Import Address Table&#xff09;Hook是一种针对Windows操作系统的API Hooking 技术&#xff0c;用于修改应用程序对动态链接库&#xff08;DLL&#xff09;中导入函数的调用。IAT是一个数据结构&#xff0c;其中包含了应用程序在运行时使用的导入函数的地址。…

左神高级提升班1 很重要的题目

【案例1】 【题目描述 难度非常高】 【思路解析】 因为要求额外空间复杂度为O(1)&#xff0c;所以我们只能使用有限几个变量&#xff0c;来得到整个数组所在的城市距离首都的距离。因为数组paths[i]表示&#xff0c;i城市指向paths[i]城市&#xff0c;我们可以利用这个指向关…

ElasticSearch系列-简介与安装详解

全文检索 讲ElasticSearch之前, 需要先提一下全文检索.全文检索是计算机程序通过扫描文章中的每一个词&#xff0c;对每一个词建立一个索引&#xff0c;指明该词在文章中出现的次数和位置。当用户查询时根据建立的索引查找&#xff0c;类似于通过字典的检索字表查字的过程。 …

内网隧道代理技术(二十六)之 搭建ICMP隧道上线CS

搭建ICMP隧道上线CS ICMP隧道原理 ICMP隧道简单实用,是一个比较特殊的协议。在一般的通信协议里,如果两台设备要进行通信,肯定需要开放端口,而在ICMP协议下就不需要。最常见的ping命令就是利用的ICMP协议,攻击者可以利用命令行得到比回复更多的ICMP请求。在通常情况下,…

Django系列:Django的项目结构与配置解析

Django系列 Django的项目结构与配置解析 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/132893616 【介…

参议员和科技巨头的私人人工智能峰会引发争议

周三&#xff0c;美国参议员查克舒默(D-NY)在参议院办公楼举办了一场关于潜在人工智能监管的“人工智能洞察论坛”。与会者包括亿万富翁和现代行业巨头&#xff0c;如埃隆马斯克、比尔盖茨、马克扎克伯格、OpenAI的萨姆奥特曼和英伟达的黄仁勋。但是这份公司客人名单22个中的14…

晨控CK-FR102系列与汇川AC800系列MODBUSTCP通讯手册

晨控CK-FR102系列与汇川AC800系列MODBUSTCP通讯手册 晨控CK-FR102AN系列是一款基于射频识别技术的高频双通道读写器&#xff0c;读写器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。高频双通道读写器支持标准工业通讯…

在PG或HGDB上启用块校验checksum

瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14,N/A 文档用途 用途 使用checksum&#xff0c;对数据库提供块校验&#xff0c;以发现隐藏的块损坏问题&#xff0c;注意仅适用于原生PG或…