Spring Security集成JWT实现权限认证 您所在的位置:网站首页 jwt授权认证 Spring Security集成JWT实现权限认证

Spring Security集成JWT实现权限认证

2024-03-12 23:33| 来源: 网络整理| 查看: 265

Spring Security集成JWT实现权限认证 框架介绍 Spring Security

我们先来看看Spring Security官网对其的介绍:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。 它是保护基于 Spring 的应用程序的事实标准。

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。 像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求

通俗点来讲,Spring Security 就是一个权限安全框架,可以为我们的项目提供安全的访问控制。又因为它是Spring 家族的一员,所以对集成Spring 有着天然的优势。它提供了一组可以在Spring 应用上下文中配置的Bean ,充分利用了Spring IoC ,DI 和AOP 功能,为我们的项目提供安全的声明式的安全访问控制,减少我们的代码量。

但是Spring Security是一个重量级的安全框架,并且有一定的上手难度。Spring Security依赖于Spring 的IOC等功能,对于非Spring项目Spring Security的支持显然也没有Shrio那样好。所以,在技术选型的时候大家一定要根据项目实际出发,切记千万不能一昧的追求新的技术,从而给项目带来不必要的时间成本

JWT(Json Web Tokens)

我们再来看看JWT官网的介绍:

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDS

JSON Web Token (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。 此信息可以验证和信任,因为它是数字签名的。 JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

JWT的组成:JWT的token格式:header.payload.signature

header中用于存放签名的生成算法 payload中用于存放用户名 token的生成时间和过期时间

JWT的认证流程:

img

可能会有小伙伴有疑问:为什么有了session我们还需要使用token呢?各自有什么优缺点?

这里我就简单的说一下吧,我们先来看看传统的session运行机制:

当用户第一次通过浏览器使用用户名和密码访问服务器时,服务器会验证用户数据,验证成功后在服务器端写入session数据,向客户端浏览器返回sessionid,浏览器将sessionid保存在cookie中,当用户再次访问服务器时,会携带sessionid,服务器会拿着sessionid从数据库获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持。

因为session是保存在服务端的,所以当用户量增大时,服务器的压力会增大 CSRF跨站伪造请求攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击 扩展性不强,即在分布式的情况下因为各个微服务不在同一台服务器上,如何共享session这会是一个需要解决的难题,这里不再展开

而使用token就可以让我们避免上面的两个问题:

token是保存在客户端的,这样就减轻了服务端的压力 token不是保存在cookie中,通常是放在header里面,并且在签发时服务器会进行加密和签名,在随后的访问中服务器会对签发的token进行校验,而在用户登录时我们可以禁用掉session从而避免CSRF攻击 RBAC模型介绍

基于角色的访问控制(RBAC)是实施面向企业安全策略的一种有效的访问控制方式。

其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限,它们之间的关系如下图所示:

img

User(用户):每个用户都有唯一的UID识别,并被授予不同的角色 Role(角色):不同角色具有不同的权限 Permission(权限):访问权限(可以访问的资源列表) 用户-角色映射:用户和角色之间的映射关系 角色-权限映射:角色和权限之间的映射

RBAC还可以细分为好几种,这里不再展开,感兴趣的小伙伴可以自行查阅

准备数据库

我们集成Spring Security需要实现最基本的RBAC模型,所以我们先创建一个名为security 的数据库,然后创建如下的表:

用户表:

DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱',  `phone` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '电话',  `create_time` datetime(0) NOT NULL COMMENT '创建时间',  `update_time` datetime(0) NOT NULL COMMENT '更新时间',  PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

角色表:

DROP TABLE IF EXISTS `t_roles`; CREATE TABLE `t_roles`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名',  `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色的英文名',  `user_count` int(11) NOT NULL COMMENT '角色数量',  `create_time` datetime(0) NOT NULL,  `update_time` datetime(0) NOT NULL,  PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

资源表(权限表):

DROP TABLE IF EXISTS `t_resource`; CREATE TABLE `t_resource`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '权限字段',  `uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '资源路径',  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '资源描述',  `create_time` datetime(0) NOT NULL,  `update_time` datetime(0) NOT NULL,  PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

用户角色关系表:

DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `user_id` bigint(20) NOT NULL,  `role_id` bigint(20) NOT NULL,  `create_time` datetime(0) NOT NULL,  `update_time` datetime(0) NOT NULL,  PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

角色权限关系表:

DROP TABLE IF EXISTS `t_role_resource`; CREATE TABLE `t_role_resource`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `role_id` bigint(20) NOT NULL,  `resource_id` bigint(20) NOT NULL,  `create_time` datetime(0) NOT NULL,  `update_time` datetime(0) NOT NULL,  PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 整合Spring Security 引入依赖            org.springframework.boot            spring-boot-starter-security                            org.springframework.security            spring-security-test            test                            io.jsonwebtoken            jjwt-api            0.11.0                            io.jsonwebtoken            jjwt-impl            0.11.0            runtime                            io.jsonwebtoken            jjwt-jackson            0.11.0            runtime                                    com.google.guava            guava            31.0.1-jre                            com.baomidou            mybatis-plus-boot-starter            3.5.1                                    com.baomidou            mybatis-plus-generator            3.5.1                                    org.apache.velocity            velocity-engine-core            2.3                                    org.projectlombok            lombok            true            1.18.20         ​ 添加生成JWT的工具类 package cuit.epoch.pymjl.security.common.utils; ​ import com.google.common.io.BaseEncoding; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; ​ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; ​ /** * @author Pymjl * @date 2022/1/19 21:49 */ @Component public class JwtUtils {    private static long expiration; ​    private static String jwtId; ​    private static String jwtSecret;    private static final int TIME_UNIT = 1000; ​    /**     * 创建JWT     */    public static String createJWT(Map claims, Long time) {        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;        Date now = new Date(System.currentTimeMillis()); ​        SecretKey secretKey = generalKey();        //生成JWT的时间        long nowMillis = System.currentTimeMillis();        //下面就是在为payload添加各种标准声明和私有声明了        //这里其实就是new一个JwtBuilder,设置jwt的body        JwtBuilder builder = Jwts.builder()                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的               .setClaims(claims)                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。               .setId(jwtId)                //iat: jwt的签发时间               .setIssuedAt(now)                //设置签名使用的签名算法和签名使用的秘钥               .signWith(signatureAlgorithm, secretKey);        if (time >= 0) {            long expMillis = nowMillis + time;            Date exp = new Date(expMillis);            //设置过期时间            builder.setExpiration(exp);       }        return builder.compact();   } ​ ​    /**     * 验证jwt     */    public static Claims verifyJwt(String token) {        //签名秘钥,和生成的签名的秘钥一模一样        SecretKey key = generalKey();        Claims claims;        try {            //得到DefaultJwtParser            claims = Jwts.parser()                    //设置签名的秘钥                   .setSigningKey(key)                   .parseClaimsJws(token).getBody();       } catch (Exception e) {            e.printStackTrace();            claims = null;       }//设置需要解析的jwt        return claims;   } ​    /**     * 由字符串生成加密key     *     * @return SecretKey     */    public static SecretKey generalKey() {        String stringKey = jwtSecret;        byte[] encodedKey = BaseEncoding.base64().decode(stringKey);        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "HmacSHA256");        return key;   } ​    /**     * 根据userId和userName生成token     */    public static String generateToken(Long userId, String username) {        Map map = new HashMap();        map.put("userId", userId);        map.put("nickName", username);        return createJWT(map, expiration * TIME_UNIT);   } ​    /**     * 根据userName生成token     */    public static String generateToken(String username) {        Map map = new HashMap();        map.put("username", username);        return createJWT(map, expiration * TIME_UNIT);   } ​    @Value("${jwt.expiration}")    public void setTokenExpiredTime(long tokenExpiredTime) {        JwtUtils.expiration = tokenExpiredTime;   } ​    @Value("${jwt.id}")    public void setJwtId(String jwtId) {        JwtUtils.jwtId = jwtId;   } ​    @Value("${jwt.secret}")    public void setJwtSecret(String jwtSecret) {        JwtUtils.jwtSecret = jwtSecret;   } ​    public static long getExpiration() {        return expiration;   } ​    public static String getJwtId() {        return jwtId;   } ​    public static String getJwtSecret() {        return jwtSecret;   } } ​ 添加代码生成器的主类 package cuit.pymjl.util; ​ ​ import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.fill.Column; ​ import java.util.Arrays; import java.util.Collections; import java.util.List; ​ /** * @author Pymjl * @version 1.0 * @date 2022/4/28 19:16 **/ public class CodeGenerator {    // 数据库URL,注意将URL改成你自己对应的数据库名称    private final static String URL = "jdbc:mysql://127.0.0.1:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true";    //数据库用户名    private final static String USER_NAME = "root";    //数据库密码    private final static String PASSWORD = "xxxxxx";    //注意将这个参数改成你自己项目的目录    private final static String OUT_PUT_DIR = "C:\Users\Admin\JavaProjects\blog-code-demo\security\src\main\java"; ​    public static void main(String[] args) {        FastAutoGenerator.create(URL, USER_NAME, PASSWORD)                // 全局配置               .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride()                       .outputDir(OUT_PUT_DIR)                       .fileOverride())                // 包配置               .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))                // 策略配置               .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))                       .addTablePrefix("t_")                       .controllerBuilder().enableRestStyle().enableHyphenStyle()                       .entityBuilder().enableLombok().addTableFills(                                new Column("create_time", FieldFill.INSERT)                       ).build())                /*                    模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker                   .templateEngine(new BeetlTemplateEngine())                   .templateEngine(new FreemarkerTemplateEngine())                 */               .execute(); ​   } ​    // 处理 all 情况    protected static List getTables(String tables) {        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));   } } ​

然后运行代码生成器,生成项目基本结构

开始配置Spring Security

因为篇幅有限,我直接介绍如何集成Spring Security,至于Spring Security的一些组件原理,运行机制我不再赘述,如果对此还不了解的小伙伴可自行上网百度,或者参考以下文章:

Spring Security 工作原理概览

一文带你了解强大的 Spring Security 架构原理!

深入了解Spring Security的实现原理

添加RestfulAccessDeniedHandler /** * 当访问接口没有权限时,自定义的返回结果 * * @author Pymjl * @version 1.0 * @date 2022/8/20 16:38 **/ @Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.toJsonPrettyStr(ResultUtils.fail(ResultEnum.PERMISSION_DENIED))); response.getWriter().flush(); } } 添加RestAuthenticationEntryPoint /** * 当未登录或者token失效访问接口时,自定义的返回结果 * * @author Pymjl * @version 1.0 * @date 2022/8/20 16:42 **/ @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(ResultUtils.fail(ResultEnum.AUTHENTICATION_FAILED))); response.getWriter().flush(); } } 添加MyUserDetails /** * Spring Security需要的用户详情 * * @author Pymjl * @version 1.0 * @date 2022/8/20 17:19 **/ public class MyUserDetails implements UserDetails { private User user; private List permissions; public MyUserDetails(User user, List permissions) { this.user = user; this.permissions = permissions; } @Override public Collection


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有