• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Spring boot+Spring security+JWT实现前后端分离登录认证和权限控制

武飞扬头像
程序员李哈
帮助17

 借鉴文章:

最近一段时间,公司给我安排了一个公司子系统的后台管理系统,让我实现权限管理。此时我就考虑到Spring全家桶的Spring security来权限管理。Spring security大致分为认证和授权两个功能,底层也是通过JavaWeb的Filter过滤器来实现,在Spring security中维护了一个过滤器链用来一层的一层的做过滤实现认证和授权,这里就不过多的介绍了,下面用案例来介绍。

项目前的准备:

整体架构为Spring boot Spring security Mybatis-plus jwt。整体项目的结构如下:

学新通

整个项目依赖如下:

  1.  
    <parent>
  2.  
    <artifactId>spring-boot-starter-parent</artifactId>
  3.  
    <groupId>org.springframework.boot</groupId>
  4.  
    <version>2.2.1.RELEASE</version>
  5.  
    </parent>
  6.  
     
  7.  
     
  8.  
    <dependencies>
  9.  
    <dependency>
  10.  
    <groupId>org.springframework.boot</groupId>
  11.  
    <artifactId>spring-boot-starter-web</artifactId>
  12.  
    </dependency>
  13.  
     
  14.  
    <dependency>
  15.  
    <groupId>org.springframework.boot</groupId>
  16.  
    <artifactId>spring-boot-starter-test</artifactId>
  17.  
    <scope>test</scope>
  18.  
    </dependency>
  19.  
     
  20.  
    <dependency>
  21.  
    <groupId>org.springframework.boot</groupId>
  22.  
    <artifactId>spring-boot-starter-security</artifactId>
  23.  
    </dependency>
  24.  
     
  25.  
    <dependency>
  26.  
    <groupId>com.baomidou</groupId>
  27.  
    <artifactId>mybatis-plus-boot-starter</artifactId>
  28.  
    <version>3.0.5</version>
  29.  
    </dependency>
  30.  
     
  31.  
    <dependency>
  32.  
    <groupId>mysql</groupId>
  33.  
    <artifactId>mysql-connector-java</artifactId>
  34.  
    </dependency>
  35.  
     
  36.  
    <dependency>
  37.  
    <groupId>com.alibaba</groupId>
  38.  
    <artifactId>druid-spring-boot-starter</artifactId>
  39.  
    <version>1.1.13</version>
  40.  
    </dependency>
  41.  
     
  42.  
    <dependency>
  43.  
    <groupId>org.projectlombok</groupId>
  44.  
    <artifactId>lombok</artifactId>
  45.  
    </dependency>
  46.  
     
  47.  
    <!--JSON-->
  48.  
    <dependency>
  49.  
    <groupId>com.alibaba</groupId>
  50.  
    <artifactId>fastjson</artifactId>
  51.  
    <version>1.2.76</version>
  52.  
    </dependency>
  53.  
    <dependency>
  54.  
    <groupId>org.apache.commons</groupId>
  55.  
    <artifactId>commons-lang3</artifactId>
  56.  
    <version>3.8.1</version>
  57.  
    </dependency>
  58.  
     
  59.  
    <dependency>
  60.  
    <groupId>com.baomidou</groupId>
  61.  
    <artifactId>mybatis-plus-generator</artifactId>
  62.  
    <version>3.4.1</version>
  63.  
    </dependency>
  64.  
     
  65.  
    <dependency>
  66.  
    <groupId>org.apache.velocity</groupId>
  67.  
    <artifactId>velocity-engine-core</artifactId>
  68.  
    <version>2.1</version>
  69.  
    </dependency>
  70.  
     
  71.  
    <!-- JWT-->
  72.  
    <dependency>
  73.  
    <groupId>io.jsonwebtoken</groupId>
  74.  
    <artifactId>jjwt</artifactId>
  75.  
    <version>0.9.0</version>
  76.  
    </dependency>
  77.  
    </dependencies>
  78.  
     
  79.  
     
  80.  
    <build>
  81.  
    <resources>
  82.  
    <resource>
  83.  
    <directory>src/main/java</directory>
  84.  
    <includes>
  85.  
    <include>**/*.xml</include>
  86.  
    </includes>
  87.  
    </resource>
  88.  
    </resources>
  89.  
    </build>
学新通

数据库的脚本如下:

大家可以去Mybatis-Plus官网copy到逆向生成的代码,链接如下

代码生成器(新) | MyBatis-Plus

记得要加上逆向生成的依赖哦,笔者提供的maven依赖中没有写。

学新通

  1.  
    SET NAMES utf8mb4;
  2.  
    SET FOREIGN_KEY_CHECKS = 0;
  3.  
     
  4.  
    -- ----------------------------
  5.  
    -- Table structure for sys_permission
  6.  
    -- ----------------------------
  7.  
    DROP TABLE IF EXISTS `sys_permission`;
  8.  
    CREATE TABLE `sys_permission`  (
  9.  
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  10.  
      `permission_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限code',
  11.  
      `permission_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
  12.  
      PRIMARY KEY (`id`) USING BTREE
  13.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Compact;
  14.  
     
  15.  
    -- ----------------------------
  16.  
    -- Records of sys_permission
  17.  
    -- ----------------------------
  18.  
    INSERT INTO `sys_permission` VALUES (1, 'create_user', '创建用户');
  19.  
    INSERT INTO `sys_permission` VALUES (2, 'query_user', '查看用户');
  20.  
    INSERT INTO `sys_permission` VALUES (3, 'delete_user', '删除用户');
  21.  
    INSERT INTO `sys_permission` VALUES (4, 'modify_user', '修改用户');
  22.  
     
  23.  
    -- ----------------------------
  24.  
    -- Table structure for sys_request_path
  25.  
    -- ----------------------------
  26.  
    DROP TABLE IF EXISTS `sys_request_path`;
  27.  
    CREATE TABLE `sys_request_path`  (
  28.  
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  29.  
      `url` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '请求路径',
  30.  
      `description` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路径描述',
  31.  
      PRIMARY KEY (`id`) USING BTREE
  32.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '请求路径' ROW_FORMAT = Compact;
  33.  
     
  34.  
    -- ----------------------------
  35.  
    -- Records of sys_request_path
  36.  
    -- ----------------------------
  37.  
    INSERT INTO `sys_request_path` VALUES (1, '/getUser', '查询用户');
  38.  
    INSERT INTO `sys_request_path` VALUES (2, '/deleteUser', '删除用户');
  39.  
     
  40.  
    -- ----------------------------
  41.  
    -- Table structure for sys_request_path_permission_relation
  42.  
    -- ----------------------------
  43.  
    DROP TABLE IF EXISTS `sys_request_path_permission_relation`;
  44.  
    CREATE TABLE `sys_request_path_permission_relation`  (
  45.  
      `id` int(11) NULL DEFAULT NULL COMMENT '主键id',
  46.  
      `url_id` int(11) NULL DEFAULT NULL COMMENT '请求路径id',
  47.  
      `permission_id` int(11) NULL DEFAULT NULL COMMENT '权限id'
  48.  
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '路径权限关联表' ROW_FORMAT = Compact;
  49.  
     
  50.  
    -- ----------------------------
  51.  
    -- Records of sys_request_path_permission_relation
  52.  
    -- ----------------------------
  53.  
    INSERT INTO `sys_request_path_permission_relation` VALUES (1, 1, 2);
  54.  
    INSERT INTO `sys_request_path_permission_relation` VALUES (2, 2, 3);
  55.  
     
  56.  
    -- ----------------------------
  57.  
    -- Table structure for sys_role
  58.  
    -- ----------------------------
  59.  
    DROP TABLE IF EXISTS `sys_role`;
  60.  
    CREATE TABLE `sys_role`  (
  61.  
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  62.  
      `role_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色值',
  63.  
      `role_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
  64.  
      `role_description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色说明',
  65.  
      PRIMARY KEY (`id`) USING BTREE
  66.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色表' ROW_FORMAT = Compact;
  67.  
     
  68.  
    -- ----------------------------
  69.  
    -- Records of sys_role
  70.  
    -- ----------------------------
  71.  
    INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '管理员,拥有所有权限');
  72.  
    INSERT INTO `sys_role` VALUES (2, 'user', '普通用户', '普通用户,拥有部分权限');
  73.  
     
  74.  
    -- ----------------------------
  75.  
    -- Table structure for sys_role_permission_relation
  76.  
    -- ----------------------------
  77.  
    DROP TABLE IF EXISTS `sys_role_permission_relation`;
  78.  
    CREATE TABLE `sys_role_permission_relation`  (
  79.  
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  80.  
      `role_id` int(11) NULL DEFAULT NULL COMMENT '角色id',
  81.  
      `permission_id` int(11) NULL DEFAULT NULL COMMENT '权限id',
  82.  
      PRIMARY KEY (`id`) USING BTREE
  83.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色-权限关联关系表' ROW_FORMAT = Compact;
  84.  
     
  85.  
    -- ----------------------------
  86.  
    -- Records of sys_role_permission_relation
  87.  
    -- ----------------------------
  88.  
    INSERT INTO `sys_role_permission_relation` VALUES (1, 1, 1);
  89.  
    INSERT INTO `sys_role_permission_relation` VALUES (2, 1, 2);
  90.  
    INSERT INTO `sys_role_permission_relation` VALUES (3, 1, 3);
  91.  
    INSERT INTO `sys_role_permission_relation` VALUES (4, 1, 4);
  92.  
    INSERT INTO `sys_role_permission_relation` VALUES (5, 2, 1);
  93.  
    INSERT INTO `sys_role_permission_relation` VALUES (6, 2, 2);
  94.  
     
  95.  
    -- ----------------------------
  96.  
    -- Table structure for sys_user
  97.  
    -- ----------------------------
  98.  
    DROP TABLE IF EXISTS `sys_user`;
  99.  
    CREATE TABLE `sys_user`  (
  100.  
      `id` int(11) NOT NULL AUTO_INCREMENT,
  101.  
      `account` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',
  102.  
      `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  103.  
      `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户密码',
  104.  
      `last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',
  105.  
      `enabled` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否可用。默认为1(可用)',
  106.  
      `not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '是否过期。默认为1(没有过期)',
  107.  
      `account_not_locked` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否锁定。默认为1(没有锁定)',
  108.  
      `credentials_not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '证书(密码)是否过期。默认为1(没有过期)',
  109.  
      `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  110.  
      `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  111.  
      `create_user` int(11) NULL DEFAULT NULL COMMENT '创建人',
  112.  
      `update_user` int(11) NULL DEFAULT NULL COMMENT '修改人',
  113.  
      PRIMARY KEY (`id`) USING BTREE
  114.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Compact;
  115.  
     
  116.  
    -- ----------------------------
  117.  
    -- Records of sys_user
  118.  
    -- ----------------------------
  119.  
    INSERT INTO `sys_user` VALUES (1, 'admin', '李哈1', '$2a$10$/fs2JL/Dypng7pYZP8tZ6ufZZ9ZqtnhCSQAtCian2w5ND.kn/a4fK', '2022-03-30 16:10:17', 1, 1, 1, 1, '2019-08-29 06:29:24', '2022-03-30 16:10:17', 1, 1);
  120.  
    INSERT INTO `sys_user` VALUES (2, 'liha', '李哈2', '$2a$10$YSaBhUZyXotbndbhzVM/Y.kZlTeLKZDkBI/Afe5oLQ7jHXfjSml3m', '2022-03-30 11:06:00', 1, 1, 1, 1, '2019-08-29 06:29:24', '2022-03-30 11:06:00', 1, 2);
  121.  
     
  122.  
    -- ----------------------------
  123.  
    -- Table structure for sys_user_role_relation
  124.  
    -- ----------------------------
  125.  
    DROP TABLE IF EXISTS `sys_user_role_relation`;
  126.  
    CREATE TABLE `sys_user_role_relation`  (
  127.  
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  128.  
      `user_id` int(11) NULL DEFAULT NULL COMMENT '用户id',
  129.  
      `role_id` int(11) NULL DEFAULT NULL COMMENT '角色id',
  130.  
      PRIMARY KEY (`id`) USING BTREE
  131.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关联关系表' ROW_FORMAT = Compact;
  132.  
     
  133.  
    -- ----------------------------
  134.  
    -- Records of sys_user_role_relation
  135.  
    -- ----------------------------
  136.  
    INSERT INTO `sys_user_role_relation` VALUES (1, 1, 1);
  137.  
    INSERT INTO `sys_user_role_relation` VALUES (2, 2, 2);
  138.  
     
  139.  
    SET FOREIGN_KEY_CHECKS = 1;
学新通

返回结果的枚举:

  1.  
    public enum ResultCode {
  2.  
    /* 成功 */
  3.  
    SUCCESS(200, "请求成功"),
  4.  
    SUCCESS_login(200, "用户登录成功"),
  5.  
    SUCCESS_logout(200, "用户退出成功"),
  6.  
     
  7.  
    /* 默认失败 */
  8.  
    COMMON_FAIL(999, "失败"),
  9.  
     
  10.  
    /* 参数错误:1000~1999 */
  11.  
    PARAM_NOT_VALID(1001, "参数无效"),
  12.  
    PARAM_IS_BLANK(1002, "参数为空"),
  13.  
    PARAM_TYPE_ERROR(1003, "参数类型错误"),
  14.  
    PARAM_NOT_COMPLETE(1004, "参数缺失"),
  15.  
     
  16.  
    /* 用户错误 */
  17.  
    USER_NOT_LOGIN(2001, "用户未登录"),
  18.  
    USER_ACCOUNT_EXPIRED(2002, "账号已过期"),
  19.  
    USER_CREDENTIALS_ERROR(2003, "密码错误"),
  20.  
    USER_CREDENTIALS_EXPIRED(2004, "密码过期"),
  21.  
    USER_ACCOUNT_DISABLE(2005, "账号不可用"),
  22.  
    USER_ACCOUNT_LOCKED(2006, "账号被锁定"),
  23.  
    USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"),
  24.  
    USER_ACCOUNT_ALREADY_EXIST(2008, "账号已存在"),
  25.  
    USER_ACCOUNT_USE_BY_OTHERS(2009, "账号下线"),
  26.  
     
  27.  
    /* 业务错误 */
  28.  
    NO_PERMISSION(3001, "当前账号没有此权限");
  29.  
    private Integer code;
  30.  
    private String message;
  31.  
     
  32.  
    ResultCode(Integer code, String message) {
  33.  
    this.code = code;
  34.  
    this.message = message;
  35.  
    }
  36.  
     
  37.  
    public Integer getCode() {
  38.  
    return code;
  39.  
    }
  40.  
     
  41.  
    public void setCode(Integer code) {
  42.  
    this.code = code;
  43.  
    }
  44.  
     
  45.  
    public String getMessage() {
  46.  
    return message;
  47.  
    }
  48.  
     
  49.  
    public void setMessage(String message) {
  50.  
    this.message = message;
  51.  
    }
  52.  
     
  53.  
    /**
  54.  
    * 根据code获取message
  55.  
    *
  56.  
    * @param code
  57.  
    * @return
  58.  
    */
  59.  
    public static String getMessageByCode(Integer code) {
  60.  
    for (ResultCode ele : values()) {
  61.  
    if (ele.getCode().equals(code)) {
  62.  
    return ele.getMessage();
  63.  
    }
  64.  
    }
  65.  
    return null;
  66.  
    }
  67.  
    }
学新通

返回结果的实体类如下:

  1.  
    @Data
  2.  
    public class JsonResult<T> implements Serializable {
  3.  
    private Boolean success;
  4.  
    private Integer errorCode;
  5.  
    private String errorMsg;
  6.  
    private T data;
  7.  
     
  8.  
    public JsonResult() {
  9.  
    }
  10.  
     
  11.  
    // 成功或者失败都能走这个
  12.  
    public JsonResult(boolean success) {
  13.  
    this.success = success;
  14.  
    this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage();
  15.  
    this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode();
  16.  
    }
  17.  
     
  18.  
    // 成功或者失败都能走这个,并且可以传一个枚举来改变默认枚举的值
  19.  
    public JsonResult(boolean success, ResultCode resultEnum) {
  20.  
    this.success = success;
  21.  
    // 传来的枚举为null就用默认的,不为null就用传来的枚举
  22.  
    this.errorCode = success ? (resultEnum==null?ResultCode.SUCCESS.getCode():resultEnum.getCode()) : (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode());
  23.  
    this.errorMsg = success ? (resultEnum==null?ResultCode.SUCCESS.getMessage():resultEnum.getMessage()): (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage());
  24.  
    }
  25.  
     
  26.  
    // 成功或者失败都能用
  27.  
    // 用户可以传一个任意对象过来,用默认的成功或者失败的枚举
  28.  
    public JsonResult(boolean success, T data) {
  29.  
    this.success = success;
  30.  
    this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode();
  31.  
    this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage();
  32.  
    this.data = data;
  33.  
    }
  34.  
     
  35.  
    // 成功或者失败都能用
  36.  
    // 用户可以传一个任意对象和自定义枚举过来
  37.  
    public JsonResult(boolean success, ResultCode resultEnum, T data) {
  38.  
    this.success = success;
  39.  
    this.errorCode = success ? (resultEnum==null ? ResultCode.SUCCESS.getCode() : resultEnum.getCode()): (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode());
  40.  
    this.errorMsg = success ? (resultEnum==null ? ResultCode.SUCCESS.getMessage() : resultEnum.getMessage()) : (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage());
  41.  
    this.data = data;
  42.  
    }
  43.  
    }
学新通

返回结果的包装类:

  1.  
    /**
  2.  
    * @Author: liha
  3.  
    * @Description:
  4.  
    */
  5.  
    public class ResultTool {
  6.  
    public static JsonResult success() {
  7.  
    return new JsonResult(true);
  8.  
    }
  9.  
     
  10.  
    public static JsonResult success(ResultCode resultEnum) {
  11.  
    return new JsonResult(true,resultEnum);
  12.  
    }
  13.  
     
  14.  
    public static <T> JsonResult<T> success(T data) {
  15.  
    return new JsonResult(true, data);
  16.  
    }
  17.  
     
  18.  
    public static <T> JsonResult<T> success(ResultCode resultEnum,T data){
  19.  
    return new JsonResult<>(true,resultEnum,data);
  20.  
    }
  21.  
     
  22.  
    public static JsonResult fail() {
  23.  
    return new JsonResult(false);
  24.  
    }
  25.  
     
  26.  
    public static JsonResult fail(ResultCode resultEnum) {
  27.  
    return new JsonResult(false, resultEnum);
  28.  
    }
  29.  
    }
学新通

JWT的工具类:

过期时间和秘钥大家可以自行设置/需要加密的字段也可以根据自己需要做处理

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @since 2021/10/16
  4.  
    */
  5.  
    public class JwtUtils {
  6.  
     
  7.  
    public static final long EXPIRE = 1000 * 60 * 60 * 24;//token过期时间 24小时
  8.  
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//密钥
  9.  
     
  10.  
     
  11.  
    public static String getJwtToken(String id, String account){
  12.  
     
  13.  
    String JwtToken = Jwts.builder()
  14.  
    .setHeaderParam("typ", "JWT")
  15.  
    .setHeaderParam("alg", "HS256")
  16.  
    .setSubject("jacob-user")
  17.  
    .setIssuedAt(new Date())
  18.  
    .setExpiration(new Date(System.currentTimeMillis() EXPIRE))
  19.  
    .claim("id", id)
  20.  
    .claim("account", account)
  21.  
    .signWith(SignatureAlgorithm.HS256, APP_SECRET)
  22.  
    .compact();
  23.  
     
  24.  
    return JwtToken;
  25.  
    }
  26.  
     
  27.  
    /**
  28.  
    * 根据token,判断token是否存在与有效
  29.  
    * @param jwtToken
  30.  
    * @return
  31.  
    */
  32.  
    public static boolean checkToken(String jwtToken) {
  33.  
    if(StringUtils.isEmpty(jwtToken)) return false;
  34.  
    try {
  35.  
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  36.  
    } catch (Exception e) {
  37.  
    e.printStackTrace();
  38.  
    return false;
  39.  
    }
  40.  
    return true;
  41.  
    }
  42.  
     
  43.  
    /**
  44.  
    * 根据request判断token是否存在与有效(也就是把token取出来罢了)
  45.  
    * @param request
  46.  
    * @return
  47.  
    */
  48.  
    public static boolean checkToken(HttpServletRequest request) {
  49.  
    try {
  50.  
    String jwtToken = request.getHeader("UserToken");
  51.  
    if(StringUtils.isEmpty(jwtToken)) return false;
  52.  
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  53.  
    } catch (Exception e) {
  54.  
    e.printStackTrace();
  55.  
    return false;
  56.  
    }
  57.  
    return true;
  58.  
    }
  59.  
     
  60.  
    /**
  61.  
    * 根据token获取会员id
  62.  
    * @param request
  63.  
    * @return
  64.  
    */
  65.  
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
  66.  
    String jwtToken = request.getHeader("UserToken");
  67.  
    if(StringUtils.isEmpty(jwtToken)) return "";
  68.  
    try {
  69.  
    // 这里解析可能会抛异常,所以try catch来捕捉
  70.  
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  71.  
    Claims claims = claimsJws.getBody();
  72.  
    return (String)claims.get("id");
  73.  
    }catch (Exception e){
  74.  
    e.printStackTrace();
  75.  
    return "";
  76.  
    }
  77.  
    }
  78.  
     
  79.  
     
  80.  
    /**
  81.  
    * 根据token获取用户的account
  82.  
    * @param request
  83.  
    * @return
  84.  
    */
  85.  
    public static String getMemberAccountByJwtToken(HttpServletRequest request) {
  86.  
    String jwtToken = request.getHeader("UserToken");
  87.  
    if(StringUtils.isEmpty(jwtToken)) return "";
  88.  
    try{
  89.  
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
  90.  
    Claims claims = claimsJws.getBody();
  91.  
    return (String)claims.get("account");
  92.  
    }catch (Exception e){
  93.  
    e.printStackTrace();
  94.  
    return "";
  95.  
    }
  96.  
    }
  97.  
    }
学新通

Spring Security层面:

首先,我们要明白,Spring security默认是帮你实现了一个登录页面的,我们要实现前后端分离肯定是要抛弃掉默认的登录页面,但是我们的登录判断还是交给Spring security来完成,所以需要实现UserDetailsService接口,这个接口来帮你完成登录的判断。并且我们知道用户的用户名和密码是要保存到DB中,所以这里需要注入Mapper的接口,来实现一个查询,并且把当前用户的权限和保存起来,其实这里也可以抽出一个User的实体类继承UserDetails接口,然后在下面的接口的loadUserByUsername实现方法中返回我们的User实体类。

学新通

  1.  
    @Service
  2.  
    public class SecurityUserServiceImpl implements SecurityUserService {
  3.  
     
  4.  
    @Autowired
  5.  
    private SysUserMapper sysUserMapper;
  6.  
     
  7.  
    @Autowired
  8.  
    private SysPermissionMapper sysPermissionMapper;
  9.  
     
  10.  
    /**
  11.  
    * 根据用户名查找数据库,判断是否存在这个用户
  12.  
    * */
  13.  
    @Override
  14.  
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  15.  
     
  16.  
    // 用户名必须是唯一的,不允许重复
  17.  
    SysUser sysUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().eq("account",username));
  18.  
     
  19.  
    if(StringUtils.isEmpty(sysUser)){
  20.  
    throw new UsernameNotFoundException("根据用户名找不到该用户的信息!");
  21.  
    }
  22.  
     
  23.  
    List<SysPermission> sysPermissions = sysPermissionMapper.getUserRolesByUserId(sysUser.getId());
  24.  
    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
  25.  
    sysPermissions.stream().forEach(sysPermission -> {
  26.  
    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysPermission.getPermissionCode());
  27.  
    grantedAuthorities.add(grantedAuthority);
  28.  
    });
  29.  
     
  30.  
    return new User(sysUser.getAccount(), sysUser.getPassword(), sysUser.getEnabled(), sysUser.getNotExpired(), sysUser.getCredentialsNotExpired(), sysUser.getAccountNotLocked(), grantedAuthorities);
  31.  
    }
  32.  
    }
学新通

Mybatis语句如下:

  1.  
    <select id="getUserRolesByUserId" resultType="com.entontech.entiry.SysPermission">
  2.  
    SELECT
  3.  
    p.*
  4.  
    FROM
  5.  
    sys_user AS u
  6.  
    LEFT JOIN sys_user_role_relation AS ur
  7.  
    ON u.id = ur.user_id
  8.  
    LEFT JOIN sys_role AS r
  9.  
    ON r.id = ur.role_id
  10.  
    LEFT JOIN sys_role_permission_relation AS rp
  11.  
    ON r.id = rp.role_id
  12.  
    LEFT JOIN sys_permission AS p
  13.  
    ON p.id = rp.permission_id
  14.  
    WHERE u.id = #{userId}
  15.  
    </select>
学新通

我们要明白,前后端分离的项目,只能返回Json数据给前段的拦截器来对JSON做处理,实现一些页面跳转的功能,而前后端不分离的项目可以实现内部的请求转发或者重定向之类的实现页面跳转,并且我们知道,对于一个系统来说有很多功能是需要登录了才能访问的,所以下面就是未登录的一个处理。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 15:31
  5.  
    * @description 用户未登录
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
  9.  
     
  10.  
    // 返回的是Json数据
  11.  
    @Override
  12.  
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  13.  
    JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
  14.  
    httpServletResponse.setContentType("text/json;charset=utf-8");
  15.  
    httpServletResponse.getWriter().write(JSON.toJSONString(result));
  16.  
    }
  17.  
    }
学新通

当用户登录以后,有一些接口是只能时管理员身份来访问的,有一些接口是对外开放的,所以前后端分离的项目中,对于一个权限的处理,也只能后端给前段返回JSON数据,前段拦截器来做出来,后端的权限控制如下:

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/29 9:52
  5.  
    * @description 没有权限
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
  9.  
    @Override
  10.  
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
  11.  
    JsonResult noPermission = ResultTool.fail(ResultCode.NO_PERMISSION);
  12.  
    //处理编码方式,防止中文乱码的情况
  13.  
    httpServletResponse.setContentType("text/json;charset=utf-8");
  14.  
    // 把Json数据放到HttpServletResponse中返回给前台
  15.  
    httpServletResponse.getWriter().write(JSON.toJSONString(noPermission));
  16.  
    }
  17.  
    }
学新通

对于用户登录时候,可能会出现成功或者失败2中情况,所以2中情况也是需要处理的。2中情况也是只能返回Json数据,前段拦截器做处理。

下面是失败的处理。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 15:37
  5.  
    * @description 登录账户时失败的处理
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
  9.  
     
  10.  
    @Override
  11.  
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  12.  
    //返回json数据
  13.  
    JsonResult result = null;
  14.  
    if (e instanceof AccountExpiredException) {
  15.  
    //账号过期
  16.  
    result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);
  17.  
    } else if (e instanceof BadCredentialsException) {
  18.  
    //密码错误
  19.  
    result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);
  20.  
    } else if (e instanceof CredentialsExpiredException) {
  21.  
    //密码过期
  22.  
    result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
  23.  
    } else if (e instanceof DisabledException) {
  24.  
    //账号不可用
  25.  
    result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);
  26.  
    } else if (e instanceof LockedException) {
  27.  
    //账号锁定
  28.  
    result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);
  29.  
    } else if (e instanceof InternalAuthenticationServiceException) {
  30.  
    //用户不存在
  31.  
    result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
  32.  
    }else{
  33.  
    //其他错误
  34.  
    result = ResultTool.fail(ResultCode.COMMON_FAIL);
  35.  
    }
  36.  
    //处理编码方式,防止中文乱码的情况
  37.  
    httpServletResponse.setContentType("text/json;charset=utf-8");
  38.  
    // 把Json数据放入到HttpServletResponse中返回给前台
  39.  
    httpServletResponse.getWriter().write(JSON.toJSONString(result));
  40.  
    }
  41.  
    }
学新通

下面是成功的处理,而我们知道,整合JWT以后,登录成功后,肯定是需要根据某些字段生成Token给前段返回的,所以也就是这里来做处理,并且根据自己的业务可以在登录成功的处理中加上自己业务的逻辑。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 15:39
  5.  
    * @description 登录成功
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
  9.  
    @Autowired
  10.  
    SysUserMapper mapper;
  11.  
     
  12.  
    @Override
  13.  
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  14.  
    //更新用户表上次登录时间、更新人、更新时间等字段
  15.  
    User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  16.  
    SysUser sysUser = mapper.selectOne(new QueryWrapper<SysUser>().eq("account",userDetails.getUsername()));
  17.  
    sysUser.setLastLoginTime(new Date());
  18.  
    sysUser.setUpdateTime(new Date());
  19.  
    sysUser.setUpdateUser(sysUser.getId());
  20.  
    mapper.update(sysUser,new QueryWrapper<SysUser>().eq("id",sysUser.getId()));
  21.  
     
  22.  
    // 根据用户的id和account生成token并返回
  23.  
    String jwtToken = JwtUtils.getJwtToken(sysUser.getId().toString(), sysUser.getAccount());
  24.  
    Map<String,String> results = new HashMap<>();
  25.  
    results.put("token",jwtToken);
  26.  
     
  27.  
    //返回json数据
  28.  
    JsonResult result = ResultTool.success(ResultCode.SUCCESS_login,results);
  29.  
    //处理编码方式,防止中文乱码的情况
  30.  
    httpServletResponse.setContentType("text/json;charset=utf-8");
  31.  
    // 把Json数据放入HttpServletResponse中返回给前台
  32.  
    httpServletResponse.getWriter().write(JSON.toJSONString(result));
  33.  
    }
  34.  
    }
学新通

退出登录的处理,对于退出登录,也就是后端返回Json,前段删除Token。退出的大部分功能还是需要前段来处理。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 15:38
  5.  
    * @description 退出登录
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
  9.  
    @Override
  10.  
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  11.  
    JsonResult result = ResultTool.success(ResultCode.SUCCESS_logout);
  12.  
    httpServletResponse.setContentType("text/json;charset=utf-8");
  13.  
    httpServletResponse.getWriter().write(JSON.toJSONString(result));
  14.  
    }
  15.  
    }
学新通

我们把这么多功能的处理写完了,但是我们项目毕竟是基于JWT的前后端分离,所以我们需要自己实现一个Filter接口,来实现自定义的JWT过滤器功能,来实现对Token的处理。所以我们定义一个JWT过滤器类实现OncePerRequestFilter接口。

下面也就是对Token的一个判断,并且通过Token获取到用户的信息,然后再通过用户的信息获取到权限添加到SecurityContext。我的理解就是这样就可以实现只需要判断一次获取到一个令牌,然后把用户的信息做一个存储。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/30 10:28
  5.  
    * @description 这个过滤器用来判断JWT是否有效
  6.  
    */
  7.  
    @Component
  8.  
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  9.  
    /**
  10.  
    * 直接将我们前面写好的service注入进来,通过service获取到当前用户的权限
  11.  
    * */
  12.  
    @Autowired
  13.  
    private SecurityUserService userDetailsService;
  14.  
     
  15.  
    @Override
  16.  
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
  17.  
    // 获取当请求头中的token,其实这里多余,完全可以使用HttpServletRequest来获取
  18.  
    String authToken = httpServletRequest.getHeader("UserToken");
  19.  
     
  20.  
    // 获取到当前用户的account
  21.  
    String account = JwtUtils.getMemberAccountByJwtToken(httpServletRequest);
  22.  
     
  23.  
    System.out.println("自定义JWT过滤器获得用户名为" account);
  24.  
     
  25.  
    // 当token中的username不为空时进行验证token是否是有效的token
  26.  
    if (!account.equals("") && SecurityContextHolder.getContext().getAuthentication() == null) {
  27.  
    // token中username不为空,并且Context中的认证为空,进行token验证
  28.  
     
  29.  
    // 获取到用户的信息,也就是获取到用户的权限
  30.  
    UserDetails userDetails = this.userDetailsService.loadUserByUsername(account);
  31.  
     
  32.  
    if (JwtUtils.checkToken(authToken)) { // 验证当前token是否有效
  33.  
     
  34.  
    UsernamePasswordAuthenticationToken authentication =
  35.  
    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  36.  
     
  37.  
    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
  38.  
     
  39.  
    //将authentication放入SecurityContextHolder中
  40.  
    SecurityContextHolder.getContext().setAuthentication(authentication);
  41.  
    }
  42.  
    }
  43.  
    // 放行给下个过滤器
  44.  
    filterChain.doFilter(httpServletRequest, httpServletResponse);
  45.  
    }
  46.  
    }
学新通

此时,我们还需要根据不同的请求来做权限的判断,这里我们总不能把所有请求都写在配置文件中把,我们肯定是需要放在DB中,然后请求一过来获取到请求路径,然后根据请求路径查询数据库获取到具体的权限,然后根据权限再判断当前的角色是否有权利。

这里是获取到请求,然后查询数据库获取到权限。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 16:48
  5.  
    * @description 根据请求,查询数据库,看看这个请求是那些角色能访问
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
  9.  
    AntPathMatcher antPathMatcher = new AntPathMatcher();
  10.  
     
  11.  
    @Autowired
  12.  
    SysPermissionMapper sysPermissionMapper;
  13.  
     
  14.  
    @Override
  15.  
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
  16.  
    // 获取请求地址
  17.  
    String requestUrl = ((FilterInvocation) o).getRequestUrl();
  18.  
    // 查询具体某个接口的权限
  19.  
    List<SysPermission> permissionList = sysPermissionMapper.selectListByPath(requestUrl);
  20.  
    if(permissionList == null || permissionList.size() == 0){
  21.  
    // 请求路径没有配置权限,表明该请求接口可以任意访问
  22.  
    return null;
  23.  
    }
  24.  
    String[] attributes = new String[permissionList.size()];
  25.  
    for(int i = 0;i<permissionList.size();i ){
  26.  
    attributes[i] = permissionList.get(i).getPermissionCode();
  27.  
    }
  28.  
    return SecurityConfig.createList(attributes);
  29.  
    }
  30.  
     
  31.  
    @Override
  32.  
    public Collection<ConfigAttribute> getAllConfigAttributes() {
  33.  
    return null;
  34.  
    }
  35.  
     
  36.  
    @Override
  37.  
    public boolean supports(Class<?> aClass) {
  38.  
    return true;
  39.  
    }
  40.  
    }
学新通

Mybatis语句如下:

  1.  
    <select id="selectListByPath" resultType="com.entontech.entiry.SysPermission">
  2.  
    select p.*
  3.  
    from sys_permission as p
  4.  
    left join sys_request_path_permission_relation as srp
  5.  
    on p.id = srp.permission_id
  6.  
    left join sys_request_path as sr
  7.  
    on srp.url_id = sr.id
  8.  
    where sr.url = #{requestUrl}
  9.  
    </select>

上一步根据请求获取到当前请求的权限,所以我们还需要获取到之前UserDetailsService接口的实现方法loadUserByUsername存入的当前用户的权限做一个匹对。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 16:48
  5.  
    * @description 匹对权限
  6.  
    */
  7.  
    @Component
  8.  
    public class CustomizeAccessDecisionManager implements AccessDecisionManager {
  9.  
    @Override
  10.  
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
  11.  
    Iterator<ConfigAttribute> iterator = collection.iterator();
  12.  
    while (iterator.hasNext()) {
  13.  
    ConfigAttribute ca = iterator.next();
  14.  
    // 当前请求需要的权限
  15.  
    String needRole = ca.getAttribute();
  16.  
    // 当前用户所具有的权限
  17.  
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  18.  
    for (GrantedAuthority authority : authorities) {
  19.  
    if (authority.getAuthority().equals(needRole)) {
  20.  
    return;
  21.  
    }
  22.  
    }
  23.  
    }
  24.  
    throw new AccessDeniedException("权限不足!");
  25.  
    }
  26.  
     
  27.  
    @Override
  28.  
    public boolean supports(ConfigAttribute configAttribute) {
  29.  
    return true;
  30.  
    }
  31.  
     
  32.  
    @Override
  33.  
    public boolean supports(Class<?> aClass) {
  34.  
    return true;
  35.  
    }
  36.  
    }
学新通

此时我们还需要一步,就是把前面2者的操作给串通起来,因为根据路径获取到权限,和登录时存的权限需要一个桥梁来判断。

  1.  
    **
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/28 16:47
  5.  
    * @description 我的理解是这个过滤器能把前2步的操作给连接起来
  6.  
    */
  7.  
    @Service
  8.  
    public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
  9.  
     
  10.  
    @Autowired
  11.  
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
  12.  
     
  13.  
    @Autowired
  14.  
    public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
  15.  
    super.setAccessDecisionManager(accessDecisionManager);
  16.  
    }
  17.  
     
  18.  
    @Override
  19.  
    public Class<?> getSecureObjectClass() {
  20.  
    return FilterInvocation.class;
  21.  
    }
  22.  
     
  23.  
    @Override
  24.  
    public SecurityMetadataSource obtainSecurityMetadataSource() {
  25.  
    return this.securityMetadataSource;
  26.  
    }
  27.  
     
  28.  
    @Override
  29.  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  30.  
    FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
  31.  
    invoke(fi);
  32.  
    }
  33.  
     
  34.  
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
  35.  
    //fi里面有一个被拦截的url
  36.  
    //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
  37.  
    //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
  38.  
    InterceptorStatusToken token = super.beforeInvocation(fi);
  39.  
    try {
  40.  
    //执行下一个拦截器
  41.  
    fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  42.  
    } finally {
  43.  
    super.afterInvocation(token, null);
  44.  
    }
  45.  
    }
  46.  
    }
学新通

小科普:

如果@Autowirte注解写在方法上,就会把当前方法的参数从IoC容器中获取到,并且执行当前方法。

我们最早是写的SpringSecurity的一些处理逻辑,后面是写的自定义的一些过滤逻辑。 此时我们还需要把这些处理逻辑和自定义的过滤逻辑通过配置的情况放入到SpringSecurity中。

可以清楚的看到,也就是把我们写的处理逻辑可以自定义过滤器给注入,然后配置。

可以说这个配置是一个整体的架子,把不同的内容给填充进来。

  1.  
    /**
  2.  
    * @author com.liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/25 10:27
  5.  
    * @description SpringSecurity的配置类
  6.  
    */
  7.  
    @Configuration
  8.  
    @EnableWebSecurity
  9.  
    public class MyConfig extends WebSecurityConfigurerAdapter {
  10.  
     
  11.  
    @Autowired
  12.  
    private SecurityUserService securityUserService;
  13.  
     
  14.  
    @Autowired
  15.  
    private AuthenticationEntryPoint authenticationEntryPoint;
  16.  
     
  17.  
    @Autowired
  18.  
    private AuthenticationFailureHandler authenticationFailureHandler;
  19.  
     
  20.  
    @Autowired
  21.  
    private LogoutSuccessHandler logoutSuccessHandler;
  22.  
     
  23.  
    @Autowired
  24.  
    private AuthenticationSuccessHandler authenticationSuccessHandler;
  25.  
     
  26.  
    @Autowired
  27.  
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
  28.  
     
  29.  
    @Autowired
  30.  
    private CustomizeAbstractSecurityInterceptor customizeAbstractSecurityInterceptor;
  31.  
     
  32.  
    @Autowired
  33.  
    private CustomizeAccessDecisionManager customizeAccessDecisionManager;
  34.  
     
  35.  
    @Autowired
  36.  
    private CustomizeFilterInvocationSecurityMetadataSource customizeFilterInvocationSecurityMetadataSource;
  37.  
     
  38.  
    @Autowired
  39.  
    private CustomizeAccessDeniedHandler customizeAccessDeniedHandler;
  40.  
     
  41.  
    @Autowired
  42.  
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
  43.  
     
  44.  
    /**
  45.  
    * 对请求进行鉴权的配置
  46.  
    *
  47.  
    * @param http
  48.  
    * @throws Exception
  49.  
    */
  50.  
    @Override
  51.  
    protected void configure(HttpSecurity http) throws Exception {
  52.  
     
  53.  
    http.cors()
  54.  
    .and().csrf().disable();
  55.  
     
  56.  
    http.authorizeRequests().
  57.  
    withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
  58.  
    @Override
  59.  
    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
  60.  
    o.setAccessDecisionManager(customizeAccessDecisionManager);//访问决策管理器
  61.  
    o.setSecurityMetadataSource(customizeFilterInvocationSecurityMetadataSource);//安全元数据源
  62.  
    return o;
  63.  
    }
  64.  
    });
  65.  
     
  66.  
    http.authorizeRequests()
  67.  
    .and()
  68.  
    .exceptionHandling()
  69.  
    .authenticationEntryPoint(authenticationEntryPoint)
  70.  
    .accessDeniedHandler(customizeAccessDeniedHandler)
  71.  
    .and()
  72.  
    .formLogin() // 登录
  73.  
    .permitAll() //允许所有用户
  74.  
    .successHandler(authenticationSuccessHandler) //登录成功处理逻辑
  75.  
    .failureHandler(authenticationFailureHandler) //登录失败处理逻辑
  76.  
    .and()
  77.  
    .logout() // 退出
  78.  
    .permitAll() //允许所有用户
  79.  
    .logoutSuccessHandler(logoutSuccessHandler) //退出成功处理逻辑
  80.  
    .deleteCookies("JSESSIONID") //登出之后删除cookie
  81.  
    .and()
  82.  
    .sessionManagement() //会话管理
  83.  
    .maximumSessions(1) //同一账号同时登录最大用户数
  84.  
    .expiredSessionStrategy(sessionInformationExpiredStrategy);
  85.  
     
  86.  
    http.addFilterBefore(customizeAbstractSecurityInterceptor, FilterSecurityInterceptor.class);
  87.  
    http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  88.  
    http.headers().cacheControl();
  89.  
    }
  90.  
     
  91.  
    @Override
  92.  
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  93.  
    auth.userDetailsService(securityUserService);
  94.  
    }
  95.  
     
  96.  
    /**
  97.  
    * 默认开启密码加密,前端传入的密码Security会在加密后和数据库中的密文进行比对,一致的话就登录成功
  98.  
    * 所以必须提供一个加密对象,供security加密前端明文密码使用
  99.  
    * @return
  100.  
    */
  101.  
    @Bean
  102.  
    PasswordEncoder passwordEncoder() {
  103.  
    return new BCryptPasswordEncoder();
  104.  
    }
  105.  
    }
学新通

密码的处理,由于security是要加密的密码,以BCryptPasswordEncoder类型生成的密码,所以我们写一个测试类来生成我们的需要的密码。

这里有一点要注意,生成的密码放入数据库的时候,记得ctrl A全选数据库字段,双击的话可能不等于全线(别问我怎么知道的,被这东西困扰几个小时)

跨域处理,也是一个标有@Configuration的配置类。

  1.  
    /**
  2.  
    * @author liha
  3.  
    * @version 1.0
  4.  
    * @date 2022/3/29 11:18
  5.  
    * @description
  6.  
    */
  7.  
    @Configuration
  8.  
    public class CronConfig {
  9.  
     
  10.  
    private CorsConfiguration buildConfig() {
  11.  
    CorsConfiguration corsConfiguration = new CorsConfiguration();
  12.  
    // 你需要跨域的地址 注意这里的 127.0.0.1 != localhost
  13.  
    // * 表示对所有的地址都可以访问
  14.  
    corsConfiguration.addAllowedOrigin("*"); // 1
  15.  
    // 跨域的请求头
  16.  
    corsConfiguration.addAllowedHeader("*"); // 2
  17.  
    // 跨域的请求方法
  18.  
    corsConfiguration.addAllowedMethod("*"); // 3
  19.  
    //加上了这一句,大致意思是可以携带 cookie
  20.  
    //最终的结果是可以 在跨域请求的时候获取同一个 session
  21.  
    corsConfiguration.setAllowCredentials(true);
  22.  
    return corsConfiguration;
  23.  
    }
  24.  
    @Bean
  25.  
    public CorsFilter corsFilter() {
  26.  
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  27.  
    //配置 可以访问的地址
  28.  
    source.registerCorsConfiguration("/**", buildConfig());
  29.  
    return new CorsFilter(source);
  30.  
    }
  31.  
    }
学新通

security大功告成,

接下来就是controller的接口。

  1.  
    @GetMapping("/deleteUser")
  2.  
    public JsonResult deleteUser(){
  3.  
    return ResultTool.success();
  4.  
    }
  5.  
     
  6.  
    @GetMapping("test")
  7.  
    public JsonResult test(){
  8.  
    Map<String,String> map = new HashMap<>();
  9.  
    map.put("hell","wolrd");
  10.  
    return ResultTool.success(map);
  11.  
    }

接下来就是我们的测试阶段,测试的话直接使用ApiPost或者是PostMan来测试把。有前段的也可以用前段来测试。

未登录时

学新通

 登录普通用户

学新通

 普通用户访问没有权限等接口时

学新通

再登入管理员身份来测试deleteUser接口

学新通

学新通

退出功能

学新通

 退出后再访问就是需要重新登录了

学新通

像个项目的Gitee地址如下:

整个项目Gitee地址学新通https://gitee.com/lihaJay/ssj

总结

基本上没讲什么底层实现,这篇是使用篇,后续会出Spring Security的底层源码讲解,并且如果本帖有问题的有疑问的读者可以在评论区留言,本人会积极处理。

最后,如果本帖对您有帮助,希望能点赞 关注 收藏。您的支持是我最大的动力,本人一直在努力的更新各种框架的使用和框架的源码解读~!

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhfkgf
系列文章
更多 icon
同类精品
更多 icon
继续加载