记录一下使用springSecurity搭建用户认证和授权的代码、、、

技术栈使用springSecurity + redis + JWT + mybatisPlus

部分代码来自:https://blog.csdn.net/I_am_Hutengfei/article/details/100561564

零. 共用工具类和sql

1.工具类

@Data
public class JsonResult<T> implements Serializable {
   private Boolean success;
   private Integer errorCode;
   private String errorMsg;
   private T data;
​
   public JsonResult() {
  }
​
   public JsonResult(Boolean success, Integer errorCode, String errorMsg, T data) {
       this.success = success;
       this.errorCode = errorCode;
       this.errorMsg = errorMsg;
       this.data = data;
  }
​
   // 成功或者失败都能走这个
   public JsonResult(boolean success) {
       this.success = success;
       this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage();
       this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode();
  }
​
   // 成功或者失败都能走这个,并且可以传一个枚举来改变默认枚举的值
   public JsonResult(boolean success, ResultCode resultEnum) {
       this.success = success;
       // 传来的枚举为null就用默认的,不为null就用传来的枚举
       this.errorCode = success ? (resultEnum==null?ResultCode.SUCCESS.getCode():resultEnum.getCode()) : (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode());
       this.errorMsg = success ? (resultEnum==null?ResultCode.SUCCESS.getMessage():resultEnum.getMessage()): (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage());
  }
​
   // 成功或者失败都能用
   // 用户可以传一个任意对象过来,用默认的成功或者失败的枚举
   public JsonResult(boolean success, T data) {
       this.success = success;
       this.errorCode = success ? ResultCode.SUCCESS.getCode() : ResultCode.COMMON_FAIL.getCode();
       this.errorMsg = success ? ResultCode.SUCCESS.getMessage() : ResultCode.COMMON_FAIL.getMessage();
       this.data = data;
  }
​
   // 成功或者失败都能用
   // 用户可以传一个任意对象和自定义枚举过来
   public JsonResult(boolean success, ResultCode resultEnum, T data) {
       this.success = success;
       this.errorCode = success ? (resultEnum==null ? ResultCode.SUCCESS.getCode() : resultEnum.getCode()): (resultEnum == null ? ResultCode.COMMON_FAIL.getCode() : resultEnum.getCode());
       this.errorMsg = success ? (resultEnum==null ? ResultCode.SUCCESS.getMessage() : resultEnum.getMessage()) : (resultEnum == null ? ResultCode.COMMON_FAIL.getMessage() : resultEnum.getMessage());
       this.data = data;
  }
​
   public static JsonResult success() {
       return new JsonResult(true);
  }
​
   public static JsonResult success(ResultCode resultEnum) {
       return new JsonResult(true,resultEnum);
  }
​
   public static <T> JsonResult<T> success(T data) {
       return new JsonResult(true, data);
  }
​
   public static <T> JsonResult<T> success(ResultCode resultEnum,T data){
       return new JsonResult<>(true,resultEnum,data);
  }
​
   public static JsonResult fail() {
       return new JsonResult(false);
  }
​
   public static <T> JsonResult fail(T data) {
       return new JsonResult(false, data);
  }
​
   public static JsonResult fail(ResultCode resultEnum) {
       return new JsonResult(false, resultEnum);
  }
​
   public static <T> JsonResult<T> fail(ResultCode resultEnum, T data) {
       return new JsonResult(false, resultEnum, data);
  }
}
​
public enum ResultCode {
   /* 成功 */
   SUCCESS(200, "请求成功"),
   SUCCESS_login(200, "用户登录成功"),
   SUCCESS_logout(200, "用户退出成功"),
​
   /* 默认失败 */
   COMMON_FAIL(999, "失败"),
​
   /* 参数错误:1000~1999 */
   PARAM_NOT_VALID(1001, "参数无效"),
   PARAM_IS_BLANK(1002, "参数为空"),
   PARAM_TYPE_ERROR(1003, "参数类型错误"),
   PARAM_NOT_COMPLETE(1004, "参数缺失"),
​
   /* 用户错误 */
   USER_FAIL_LOGIN(2000, "登陆失败"),
   USER_NOT_LOGIN(2001, "用户未登录"),
   USER_ACCOUNT_EXPIRED(2002, "账号已过期"),
   USER_CREDENTIALS_ERROR(2003, "密码错误"),
   USER_CREDENTIALS_EXPIRED(2004, "密码过期"),
   USER_ACCOUNT_DISABLE(2005, "账号不可用"),
   USER_ACCOUNT_LOCKED(2006, "账号被锁定"),
   USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"),
   USER_ACCOUNT_ALREADY_EXIST(2008, "账号已存在"),
   USER_ACCOUNT_USE_BY_OTHERS(2009, "账号下线"),
​
   /* 业务错误 */
   NO_PERMISSION(3001, "当前账号没有此权限");
   private Integer code;
   private String message;
​
   ResultCode(Integer code, String message) {
       this.code = code;
       this.message = message;
  }
​
   public Integer getCode() {
       return code;
  }
​
   public void setCode(Integer code) {
       this.code = code;
  }
​
   public String getMessage() {
       return message;
  }
​
   public void setMessage(String message) {
       this.message = message;
  }
​
   /**
    * 根据code获取message
    *
    * @param code
    * @return
    */
   public static String getMessageByCode(Integer code) {
       for (ResultCode ele : values()) {
           if (ele.getCode().equals(code)) {
               return ele.getMessage();
          }
      }
       return null;
  }
}

2.mybatisplus和sql

//mybatisPlus自动生成代码使用
public class CreateCode {
   public static void main(String[] args) {
       FastAutoGenerator.create("jdbc:mysql://localhost:3306/security", "root", "123456")
              .globalConfig(builder -> {
                   builder.author("bao") // 设置作者
                          .enableSwagger() // 开启 swagger 模式
                          .fileOverride() // 覆盖已生成文件
                          .outputDir("D://user"); // 指定输出目录
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.xml, "D://user")); // 设置mapperXml生成路径
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.controller, "D://user")); // 设置mapperXml生成路径
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.entity, "D://user")); // 设置mapperXml生成路径
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.mapper, "D://user")); // 设置mapperXml生成路径
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.service, "D://")); // 设置mapperXml生成路径
              })
              .packageConfig(builder -> {
                   builder.parent("com.example.alipay.user") // 设置父包名
                          .moduleName("") // 设置父包模块名
                          .pathInfo(Collections.singletonMap(OutputFile.serviceImpl, "D://")); // 设置mapperXml生成路径
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_user") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_role") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_permission") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_request_path") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_request_path_permission_relation") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_role_permission_relation") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .strategyConfig(builder -> {
                   builder.addInclude("sys_user_role_relation") // 设置需要生成的表名
                          .addTablePrefix("t_", "c_"); // 设置过滤表前缀
              })
              .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
              .execute();
​
  }
}

sql

/*
Navicat Premium Data Transfer
​
Source Server         : localhost_3306
Source Server Type   : MySQL
Source Server Version : 50719
Source Host           : localhost:3306
Source Schema         : security
​
Target Server Type   : MySQL
Target Server Version : 50719
File Encoding         : 65001
​
Date: 27/05/2022 15:05:34
*/
​
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
 `permission_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限code',
 `permission_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = COMPACT;
​
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'sys:user:list', '用户查询');
INSERT INTO `sys_permission` VALUES (2, 'sys:user:delete', '用户删除');
INSERT INTO `sys_permission` VALUES (3, 'sys:user:online', '在线用户查询');
​
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
 `role_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色值',
 `role_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
 `role_description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色说明',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色表' ROW_FORMAT = COMPACT;
​
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '管理员,拥有所有权限');
INSERT INTO `sys_role` VALUES (2, 'user', '普通用户', '普通用户,拥有部分权限');
​
-- ----------------------------
-- Table structure for sys_role_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation`  (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
 `role_id` int(11) NULL DEFAULT NULL COMMENT '角色id',
 `permission_id` int(11) NULL DEFAULT NULL COMMENT '权限id',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色-权限关联关系表' ROW_FORMAT = COMPACT;
​
-- ----------------------------
-- Records of sys_role_permission_relation
-- ----------------------------
INSERT INTO `sys_role_permission_relation` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission_relation` VALUES (7, 1, 2);
INSERT INTO `sys_role_permission_relation` VALUES (8, 1, 3);
INSERT INTO `sys_role_permission_relation` VALUES (9, 2, 1);
​
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
 `id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
 `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',
 `nick_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
 `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户密码',
 `last_login_time` datetime NULL DEFAULT NULL COMMENT '最后一次登录时间',
 `enabled` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否可用。默认为1(可用)',
 `not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '是否过期。默认为1(没有过期)',
 `account_not_locked` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否锁定。默认为1(没有锁定)',
 `credentials_not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '证书(密码)是否过期。默认为1(没有过期)',
 `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
 `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
 `create_user` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
 `update_user` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = COMPACT;
​
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('4044aaa1-dca1-11ec-81da-00ff8ecb7c92', 'admin', 'admin', '$2a$10$fdaPecPEB3jJQCR4/E2lz.Gg6cnXLlsR1Qa2C4r7yO.hPjTOMr/he', '2022-05-27 14:18:28', 1, 1, 1, 1, '2019-08-29 06:29:24', '2022-05-26 14:13:09', '', '');
INSERT INTO `sys_user` VALUES ('63561932-dca1-11ec-81da-00ff8ecb7c92', 'user001', 'user001', '$2a$10$fdaPecPEB3jJQCR4/E2lz.Gg6cnXLlsR1Qa2C4r7yO.hPjTOMr/he', '2022-05-27 10:45:30', 1, 1, 1, 1, NULL, '2022-05-25 13:16:30', 'admin', 'admin');
​
-- ----------------------------
-- Table structure for sys_user_role_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation`  (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
 `user_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户id',
 `role_id` int(11) NULL DEFAULT NULL COMMENT '角色id',
 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关联关系表' ROW_FORMAT = COMPACT;
​
-- ----------------------------
-- Records of sys_user_role_relation
-- ----------------------------
INSERT INTO `sys_user_role_relation` VALUES (1, '4044aaa1-dca1-11ec-81da-00ff8ecb7c92', 1);
INSERT INTO `sys_user_role_relation` VALUES (2, '63561932-dca1-11ec-81da-00ff8ecb7c92', 2);
​
SET FOREIGN_KEY_CHECKS = 1;
​

一.redis配置

使用redis来保存登陆用户的信息,一来防止验证重复查询数据库,二来后期可以在此基础上做在线用户查询,强退用户等操作。

1. redis工具类(来自若依框架)

/**
* spring redis 工具类
*
* @author ruoyi
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
   @Autowired
   public RedisTemplate redisTemplate;
​
   /**
    * 缓存基本的对象,Integer、String、实体类等
    *
    * @param key 缓存的键值
    * @param value 缓存的值
    */
   public <T> void setCacheObject(final String key, final T value)
  {
       redisTemplate.opsForValue().set(key, value);
  }
​
   /**
    * 缓存基本的对象,Integer、String、实体类等
    *
    * @param key 缓存的键值
    * @param value 缓存的值
    * @param timeout 时间
    * @param timeUnit 时间颗粒度
    */
   public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
  {
       redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
  }
​
   /**
    * 设置有效时间
    *
    * @param key Redis键
    * @param timeout 超时时间
    * @return true=设置成功;false=设置失败
    */
   public boolean expire(final String key, final long timeout)
  {
       return expire(key, timeout, TimeUnit.SECONDS);
  }
​
   /**
    * 设置有效时间
    *
    * @param key Redis键
    * @param timeout 超时时间
    * @param unit 时间单位
    * @return true=设置成功;false=设置失败
    */
   public boolean expire(final String key, final long timeout, final TimeUnit unit)
  {
       return redisTemplate.expire(key, timeout, unit);
  }
​
   /**
    * 获得缓存的基本对象。
    *
    * @param key 缓存键值
    * @return 缓存键值对应的数据
    */
   public <T> T getCacheObject(final String key)
  {
       ValueOperations<String, T> operation = redisTemplate.opsForValue();
       return operation.get(key);
  }
​
   /**
    * 删除单个对象
    *
    * @param key
    */
   public boolean deleteObject(final String key)
  {
       return redisTemplate.delete(key);
  }
​
   /**
    * 删除集合对象
    *
    * @param collection 多个对象
    * @return
    */
   public long deleteObject(final Collection collection)
  {
       return redisTemplate.delete(collection);
  }
​
   /**
    * 缓存List数据
    *
    * @param key 缓存的键值
    * @param dataList 待缓存的List数据
    * @return 缓存的对象
    */
   public <T> long setCacheList(final String key, final List<T> dataList)
  {
       Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
       return count == null ? 0 : count;
  }
​
   /**
    * 获得缓存的list对象
    *
    * @param key 缓存的键值
    * @return 缓存键值对应的数据
    */
   public <T> List<T> getCacheList(final String key)
  {
       return redisTemplate.opsForList().range(key, 0, -1);
  }
​
   /**
    * 缓存Set
    *
    * @param key 缓存键值
    * @param dataSet 缓存的数据
    * @return 缓存数据的对象
    */
   public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
  {
       BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
       Iterator<T> it = dataSet.iterator();
       while (it.hasNext())
      {
           setOperation.add(it.next());
      }
       return setOperation;
  }
​
   /**
    * 获得缓存的set
    *
    * @param key
    * @return
    */
   public <T> Set<T> getCacheSet(final String key)
  {
       return redisTemplate.opsForSet().members(key);
  }
​
   /**
    * 缓存Map
    *
    * @param key
    * @param dataMap
    */
   public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
  {
       if (dataMap != null) {
           redisTemplate.opsForHash().putAll(key, dataMap);
      }
  }
​
   /**
    * 获得缓存的Map
    *
    * @param key
    * @return
    */
   public <T> Map<String, T> getCacheMap(final String key)
  {
       return redisTemplate.opsForHash().entries(key);
  }
​
   /**
    * 往Hash中存入数据
    *
    * @param key Redis键
    * @param hKey Hash键
    * @param value 值
    */
   public <T> void setCacheMapValue(final String key, final String hKey, final T value)
  {
       redisTemplate.opsForHash().put(key, hKey, value);
  }
​
   /**
    * 获取Hash中的数据
    *
    * @param key Redis键
    * @param hKey Hash键
    * @return Hash中的对象
    */
   public <T> T getCacheMapValue(final String key, final String hKey)
  {
       HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
       return opsForHash.get(key, hKey);
  }
​
   /**
    * 删除Hash中的数据
    *
    * @param key
    * @param mapkey
    */
   public void delCacheMapValue(final String key, final String hkey)
  {
       HashOperations hashOperations = redisTemplate.opsForHash();
       hashOperations.delete(key, hkey);
  }
​
   /**
    * 获取多个Hash中的数据
    *
    * @param key Redis键
    * @param hKeys Hash键集合
    * @return Hash对象集合
    */
   public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
  {
       return redisTemplate.opsForHash().multiGet(key, hKeys);
  }
​
   /**
    * 获得缓存的基本对象列表
    *
    * @param pattern 字符串前缀
    * @return 对象列表
    */
   public Collection<String> keys(final String pattern)
  {
       return redisTemplate.keys(pattern);
  }
}
​

2. redis序列化与反序列化配置

这里不做序列化,保存的redis信息在可视化工具中将是一堆乱码,后期在通过redisTemplate.keys(pattern)获取key值时会获取不到。

/**
* @descriptions: fastjson自定义序列化配置
* @author: bao
* @date: 2022/5/27 11:18
* @version: 1.0
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
   private ObjectMapper objectMapper = new ObjectMapper();
   public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
​
   private Class<T> clazz;
​
   static {
       ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
       //如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径
//         ParserConfig.getGlobalInstance().addAccept("org.springframework.security.core.authority");
  }
   public FastJson2JsonRedisSerializer(Class<T> clazz) {
       super();
       this.clazz = clazz;
  }
​
   public byte[] serialize(T t) throws SerializationException {
       if (t == null) {
           return new byte[0];
      }
       return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
  }
​
   public T deserialize(byte[] bytes) throws SerializationException {
       if (bytes == null || bytes.length <= 0) {
           return null;
      }
       String str = new String(bytes, DEFAULT_CHARSET);
​
       return JSON.parseObject(str, clazz);
  }
   public void setObjectMapper(ObjectMapper objectMapper) {
       Assert.notNull(objectMapper, "'objectMapper' must not be null");
       this.objectMapper = objectMapper;
  }
​
   protected JavaType getJavaType(Class<?> clazz) {
       return TypeFactory.defaultInstance().constructType(clazz);
  }
​
}
​
/**
* @descriptions: redis序列化,redisTemplate配置
* @author: bao
* @date: 2022/5/26 13:26
* @version: 1.0
*/
@Configuration
public class RedisConfig {
   @Bean
   public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
       RedisTemplate<Object, Object> template = new RedisTemplate<>();
       template.setConnectionFactory(redisConnectionFactory);
       //使用Fastjson2JsonRedisSerializer来序列化和反序列化redis的value值 by zhengkai
       FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
​
       ObjectMapper mapper = new ObjectMapper();
       mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
       mapper.activateDefaultTyping(new DefaultBaseTypeLimitingValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
       serializer.setObjectMapper(mapper);
​
       template.setValueSerializer(serializer);
       //使用StringRedisSerializer来序列化和反序列化redis的key值
       template.setKeySerializer(new StringRedisSerializer());
       template.afterPropertiesSet();
       return template;
  }
}
​

3.redis缓存常量

由于没写别的功能,目前只有一个常量
public class CacheConst {
  public static String LOGIN_USER_KEY = "loginUser:";
}

二 . JWT配置

1.工具类

public class JwtUtils {
   //token过期时间设置24小时
   public static final long EXPIRE = 1000 * 60 * 60 * 24;
   //密钥
   public static final String APP_SECRET = "sdfkdjAYANMENGod33ksfdkds";
​
​
   /**
    * @descriptions 保存用户所有信息
    * @author bao
    * @date 2022/5/25 15:49
    * @return java.lang.String
    */
   public static String getJwtToken(String loginId){
​
       String JwtToken = Jwts.builder()
              .setHeaderParam("typ", "JWT")
              .setHeaderParam("alg", "HS256")
              .setSubject("jacob-user")
              .setIssuedAt(new Date())
              .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
              .claim("loginId", loginId)
              .signWith(SignatureAlgorithm.HS256, APP_SECRET)
              .compact();
​
       return JwtToken;
  }
​
​
​
   /**
    * 根据token,判断token是否存在与有效
    * @param jwtToken
    * @return
    */
   public static boolean checkToken(String jwtToken) {
       if(StringUtils.isEmpty(jwtToken)) return false;
       try {
           Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
      } catch (Exception e) {
           e.printStackTrace();
           return false;
      }
       return true;
  }
​
   /**
    * 根据request判断token是否存在与有效(也就是把token取出来罢了)
    * @param request
    * @return
    */
   public static boolean checkToken(HttpServletRequest request) {
       try {
           String jwtToken = request.getHeader("UserToken");
           if(StringUtils.isEmpty(jwtToken)) return false;
           Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
      } catch (Exception e) {
           e.printStackTrace();
           return false;
      }
       return true;
  }
​
   /**
    * 根据token获取用户的id
    * @param request
    * @return
    */
   public static String getUseridByJwtToken(HttpServletRequest request) {
       String jwtToken = request.getHeader("UserToken");
       if(StringUtils.isEmpty(jwtToken)) return null;
       try{
           Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
           Claims claims = claimsJws.getBody();
           return (String) claims.get("loginId");
      }catch (Exception e){
           e.printStackTrace();
           return null;
      }
  }
}

2. jwt过滤器

* 继承了OncePerRequestFilter抽象类,该抽象类在每次请求时只执行一次过滤,即它的作用就是保证一次请求只通过一次filter,
* 重写其doFilterInternal方法来自定义处理逻辑
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
   /**
    * 直接将我们前面写好的service注入进来,通过service获取到当前用户的权限
    * */
   @Autowired
   private RedisCache redisCache;
​
   @Override
   protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
       // 验证当前token是否有效
       if (JwtUtils.checkToken(httpServletRequest)) {
           // 获取到当前用户的account
           String id = JwtUtils.getUseridByJwtToken(httpServletRequest);
           //根据id从redis中获取登录用户信息
           ParserConfig.getGlobalInstance().addAccept("org.springframework.security.core.authority.SimpleGrantedAuthority.");
           LoginUser userDetails = redisCache.getCacheObject(CacheConst.LOGIN_USER_KEY + id);
​
           // token中username不为空,并且Context中的认证为空,进行token验证
           if (!Objects.isNull(userDetails) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
               System.out.println("自定义JWT过滤器获得用户名为-" + userDetails.getUsername());
               UsernamePasswordAuthenticationToken authentication =
                       new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
               authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
               //将authentication放入SecurityContextHolder中
               SecurityContextHolder.getContext().setAuthentication(authentication);
          }
      }
       // 放行给下个过滤器
       filterChain.doFilter(httpServletRequest, httpServletResponse);
  }
}
​

三. springSecurity配置

1.自定义UserDetails实现类

这个类里面的 Collection<? extends GrantedAuthority> authorities,在反序列化时会失败,上面的fastjson自定义序列化方式,主要是为了解决这个问题。

public class LoginUser implements Serializable, UserDetails{
​
   private static final long serialVersionUID = 1L;
   private SysUser sysUser;
   private Collection<? extends GrantedAuthority> authorities;
   public LoginUser() {
  }
​
   public LoginUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
       Assert.isTrue(sysUser.getUserName() != null && !"".equals(sysUser.getUserName()) && sysUser.getPassword() != null,
               "Cannot pass null or empty values to constructor");
       this.sysUser = sysUser;
       this.authorities = Collections.unmodifiableSet((Set<? extends GrantedAuthority>) authorities);
  }
​
   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
       return this.authorities;
  }
​
   @Override
   public String getPassword() {
       return this.sysUser.getPassword();
  }
​
   @Override
   public String getUsername() {
       return this.sysUser.getUserName();
  }
​
   @Override
   public boolean isAccountNonExpired() {
       return this.sysUser.getCredentialsNotExpired();
  }
​
   @Override
   public boolean isAccountNonLocked() {
       return this.sysUser.getAccountNotLocked();
  }
​
   @Override
   public boolean isCredentialsNonExpired() {
       return this.sysUser.getCredentialsNotExpired();
  }
​
   @Override
   public boolean isEnabled() {
       return this.sysUser.getEnabled();
  }
​
   public SysUser getSysUser() {
       return sysUser;
  }
​
   public void setSysUser(SysUser sysUser) {
       this.sysUser = sysUser;
  }
​
   public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
       this.authorities = authorities;
  }
}
​

2.自定义UserDetailsService实现类

在调用登录接口(这里用的security默认登录接口/login),会自动执行UserDetailsService的loadUserByUsername,返回一个userdetails,也就是上面的自定义实现类。

在这个方法中要配置用户信息,和用户权限。

@Service
public class SecurityUserService implements UserDetailsService {
​
   @Autowired
   private SysUserMapper sysUserMapper;
   @Autowired
   private SysPermissionMapper sysPermissionMapper;
​
   /**
    * 根据用户名查找数据库,判断是否存在这个用户
    *
    * @return*/
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
​
       // 用户名必须是唯一的,不允许重复
       SysUser sysUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().eq("user_name",username));
//获取用户权限
       List<SysPermission> sysPermissions = sysPermissionMapper.getUserRolesByUserId(sysUser.getId());
       //将用户权限存入Userdetails的authorities中,以便security做权限判断
       Set<GrantedAuthority> grantedAuthorities = new LinkedHashSet<>();
       sysPermissions.stream().forEach(sysPermission -> {
           GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(sysPermission.getPermissionCode());
           grantedAuthorities.add(grantedAuthority);
      });
​
       LoginUser loginUser = new LoginUser(sysUser, grantedAuthorities);
       return loginUser;
  }
}

这里要用到的一个sql

sysPermissionMapper.getUserRolesByUserId(sysUser.getId());

​
   <select id="getUserRolesByUserId" resultType="com.bao.xinghe.user.entity.SysPermission">
      SELECT
          p.*
      FROM
          sys_user AS u
              LEFT JOIN sys_user_role_relation AS ur
                        ON u.id = ur.user_id
              LEFT JOIN sys_role AS r
                        ON r.id = ur.role_id
              LEFT JOIN sys_role_permission_relation AS rp
                        ON r.id = rp.role_id
              LEFT JOIN sys_permission AS p
                        ON p.id = rp.permission_id
      WHERE u.id = #{userId}
   </select>

3.一些要用到的异常过滤器

登陆成功过滤器

在这个过滤器中实现一些登陆成功后的业务处理逻辑,我这里包括修改登录用户信息,保存登录缓存。

@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
   @Autowired
   SysUserMapper mapper;
   @Autowired
   RedisCache redisCache;
​
   @Override
   public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
       //更新用户表上次登录时间、更新人、更新时间等字段
       LoginUser userDetails = (LoginUser) authentication.getPrincipal();
       SysUser sysUser = userDetails.getSysUser();
       sysUser.setLastLoginTime(new Date());
       mapper.update(sysUser,new QueryWrapper<SysUser>().eq("id",sysUser.getId()));
​
       //登陆成功用户信息保存redis、登陆有效时长1小时
       String id = sysUser.getId();
       redisCache.setCacheObject(CacheConst.LOGIN_USER_KEY + id, userDetails, 60, TimeUnit.MINUTES);
       // 根据用户的id生成token并返回
       String jwtToken = JwtUtils.getJwtToken(id);
       Map<String,String> results = new HashMap<>();
       results.put("token",jwtToken);
​
       //返回json数据
       JsonResult result = JsonResult.success(ResultCode.SUCCESS_login,results);
       //处理编码方式,防止中文乱码的情况
       httpServletResponse.setContentType("text/json;charset=utf-8");
       // 把Json数据放入HttpServletResponse中返回给前台
       httpServletResponse.getWriter().write(JSON.toJSONString(result));
  }
}

登陆失败过滤器

​
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
​
   @Override
   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
       //返回json数据
       JsonResult result = null;
       if (e instanceof AccountExpiredException) {
           //账号过期
           result = JsonResult.fail(ResultCode.USER_ACCOUNT_EXPIRED);
      } else if (e instanceof BadCredentialsException) {
           //密码错误
           result = JsonResult.fail(ResultCode.USER_CREDENTIALS_ERROR);
      } else if (e instanceof CredentialsExpiredException) {
           //密码过期
           result = JsonResult.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
      } else if (e instanceof DisabledException) {
           //账号不可用
           result = JsonResult.fail(ResultCode.USER_ACCOUNT_DISABLE);
      } else if (e instanceof LockedException) {
           //账号锁定
           result = JsonResult.fail(ResultCode.USER_ACCOUNT_LOCKED);
      } else if (e instanceof InternalAuthenticationServiceException) {
           //用户不存在
           result = JsonResult.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
      }else{
           //其他错误
           result = JsonResult.fail(ResultCode.COMMON_FAIL);
      }
       //处理编码方式,防止中文乱码的情况
       httpServletResponse.setContentType("text/json;charset=utf-8");
       // 把Json数据放入到HttpServletResponse中返回给前台
       httpServletResponse.getWriter().write(JSON.toJSONString(result));
  }
}
​

未登录处理

@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
​
   // 返回的是Json数据
   @Override
   public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
       JsonResult result = JsonResult.fail(ResultCode.USER_NOT_LOGIN);
       httpServletResponse.setContentType("text/json;charset=utf-8");
       httpServletResponse.getWriter().write(JSON.toJSONString(result));
  }
}

无权限处理

@Component
public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
   @Override
   public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
       JsonResult noPermission = JsonResult.fail(ResultCode.NO_PERMISSION);
       //处理编码方式,防止中文乱码的情况
       httpServletResponse.setContentType("text/json;charset=utf-8");
       // 把Json数据放到HttpServletResponse中返回给前台
       httpServletResponse.getWriter().write(JSON.toJSONString(noPermission));
  }
}
​

登出处理

登出时做删除redis缓存操作

@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
   @Autowired
   RedisCache redisCache;
   @Override
   public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
       //退出成功删除redis缓存
       LoginUser userDetails = (LoginUser) authentication.getPrincipal();
       redisCache.deleteObject(CacheConst.LOGIN_USER_KEY + userDetails.getSysUser().getId());
       JsonResult result = JsonResult.success(ResultCode.SUCCESS_logout);
       httpServletResponse.setContentType("text/json;charset=utf-8");
       httpServletResponse.getWriter().write(JSON.toJSONString(result));
  }
}

4.核心配置类

自定义SecurityConfig继承WebSecurityConfigurerAdapter

重写configure方法对security进行核心配置

之前写的所有过滤器都要在这里配置进去。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //此注解用于开启controller接口注解权限认证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
​
  @Autowired
  private SecurityUserService securityUserService;
​
  @Autowired
  private AuthenticationEntryPoint authenticationEntryPoint;
​
  @Autowired
  private AuthenticationFailureHandler authenticationFailureHandler;
​
  @Autowired
  private LogoutSuccessHandler logoutSuccessHandler;
​
  @Autowired
  private AuthenticationSuccessHandler authenticationSuccessHandler;
​
  @Autowired
  private CustomizeAccessDeniedHandler customizeAccessDeniedHandler;
​
  @Autowired
  private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
​
  /**
    * 对请求进行鉴权的配置
    *
    * @param http
    * @throws Exception
    */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
//关闭cors
      http.cors()
              .and().csrf().disable();
​
      /**
        * anyRequest         |   匹配所有请求路径
        * access             |   SpringEl表达式结果为true时可以访问
        * anonymous           |   匿名可以访问
        * denyAll             |   用户不能访问
        * fullyAuthenticated |   用户完全认证可以访问(非remember-me下自动登录)
        * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
        * hasAnyRole         |   如果有参数,参数表示角色,则其中任何一个角色可以访问
        * hasAuthority       |   如果有参数,参数表示权限,则其权限可以访问
        * hasIpAddress       |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
        * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
        * permitAll           |   用户可以任意访问
        * rememberMe         |   允许通过remember-me登录的用户访问
        * authenticated       |   用户登录后可访问
        */
      http.authorizeRequests()
              .antMatchers("/sysUser/signUp").permitAll() //这是测试代码。。注册用户
              .anyRequest().authenticated()
          .and()
              .exceptionHandling()
              .authenticationEntryPoint(authenticationEntryPoint)
              .accessDeniedHandler(customizeAccessDeniedHandler)
          .and()
              .formLogin() // 登录
              .permitAll() //允许所有用户
              .successHandler(authenticationSuccessHandler) //登录成功处理逻辑
              .failureHandler(authenticationFailureHandler) //登录失败处理逻辑
          .and()
              .logout()     // 退出
              .permitAll()   //允许所有用户
              .logoutSuccessHandler(logoutSuccessHandler) //退出成功处理逻辑
              .deleteCookies("JSESSIONID")   //登出之后删除cookie
          .and()
              // 不通过session保存securityContext
              .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
​
      http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
      http.addFilterBefore(jwtAuthenticationTokenFilter, LogoutFilter.class);
      http.headers().cacheControl();
  }
​
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(securityUserService);
  }
​
  /**
    * 默认开启密码加密,前端传入的密码Security会在加密后和数据库中的密文进行比对,一致的话就登录成功
    * 所以必须提供一个加密对象,供security加密前端明文密码使用
    * @return
    */
  @Bean
  PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }
​
  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
  }
}
​

四.测试接口

@RestController
@RequestMapping("/sysUser")
public class SysUserController {
​
   @Autowired
   SysUserServiceImpl sysUserService;
​
   @Autowired
   PasswordEncoder passwordEncoder;
​
   @RequestMapping("getUser")
   @PreAuthorize("hasAuthority('sys:user:list')")
   public JsonResult list(){
       List<SysUser> list = sysUserService.list();
       return new JsonResult(true, list);
  }
​
​
   @RequestMapping("deleteUser")
   @PreAuthorize("hasAuthority('sys:user:delete')")
   public JsonResult deleteUser(){
       List<SysUser> list = sysUserService.list();
       return new JsonResult(true, list);
  }
​
​
   @RequestMapping("signUp")
   public JsonResult signUp(SysUser sysUser){
       String password = passwordEncoder.encode(sysUser.getPassword());
       sysUser.setPassword(password);
       sysUserService.save(sysUser);
       return new JsonResult(true, sysUser);
  }
​
   @GetMapping("onlineUser")
   @PreAuthorize("hasAuthority('sys:user:online')")
   public JsonResult onlineUser(){
       List<SysUser> userList = sysUserService.onlineUser();
       if (userList == null){
           JsonResult.success("当前无登录用户");
      }
       return JsonResult.success(userList);
  }
​
}
​
    @Override
   public List<SysUser> onlineUser() {
       //获取redis所有在线用户key值、
       Set<String> keys = (Set<String>) redisCache.keys("*" + CacheConst.LOGIN_USER_KEY + "*");
       if (keys == null){
           return null;
      }
       List<SysUser> userList = new ArrayList<>();
       keys.stream().forEach( key -> {
           LoginUser loginUser = redisCache.getCacheObject(key);
           userList.add(loginUser.getSysUser());
      });
       return userList;
  }
 

springSecurity + jwt + redis 前后端分离用户认证和授权的更多相关文章

  1. 从零玩转SpringSecurity+JWT整合前后端分离

    从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...

  2. Spring Security + JWT实现前后端分离权限认证

    现在国内前后端很多公司都在使用前后端分离的开发方式,虽然也有很多人并不赞同前后端分离,比如以下这篇博客就很有意思: https://www.aliyun.com/jiaocheng/650661.ht ...

  3. SpringSecurity04 利用JPA和SpringSecurity实现前后端分离的认证和授权

    1 环境搭建 1.1 环境说明 JDK:1.8 MAVEN:3.5 SpringBoot:2.0.4 SpringSecurity:5.0.7 IDEA:2017.02旗舰版 1.2 环境搭建 创建一 ...

  4. SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建

    SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建 - lhc0512的博客 - CSDN博客 https://blog.csdn.net/lhc0512 ...

  5. 实战!spring Boot security+JWT 前后端分离架构认证登录!

    大家好,我是不才陈某~ 认证.授权是实战项目中必不可少的部分,而Spring Security则将作为首选安全组件,因此陈某新开了 <Spring Security 进阶> 这个专栏,写一 ...

  6. spring boot + spring security +JWT令牌 +前后端分离--- 心得

    1.前言 观看这篇随笔需要有spring security基础. 心得: 1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定 2.生成的token是将登录通过的用户的权限拼接的字符 ...

  7. Shrio使用Jwt达到前后端分离

    概述 前后端分离之后,因为HTTP本身是无状态的,Session就没法用了.项目采用jwt的方案后,请求的主要流程如下:用户登录成功之后,服务端会创建一个jwt的token(jwt的这个token中记 ...

  8. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  9. Spring Security OAuth2.0认证授权六:前后端分离下的登录授权

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

随机推荐

  1. safari浏览器fixed后,被软键盘遮盖的问题—【未解决】

    safari浏览器fixed后,被软键盘遮盖的问题,已经有好多人问相关的问题,应该是问的角度不一样,还的再次提出咯. 问题描述 测试环境:ios 10.2/10.3 简单来说就是在html5页面中底部 ...

  2. 基于React的仿QQ音乐(移动端)

    前言 由于这段时间工作上也是挺忙的,就没有时间去写这个项目,中间一直都是写写停停,进度也是非常慢的.正好前几天都还比较空,就赶紧抓着空闲时间去写这个项目,最后紧赶慢赶地完成了.本项目采用了React的 ...

  3. python-杨辉三角形

    [题目描述]输出n(0<n)行杨辉三角形,n由用户输入. [练习要求]请给出源代码程序和运行测试结果,源代码程序要求添加必要的注释. [输入格式]一行中输入1个整数n. [输出格式]输出n行杨辉 ...

  4. 使用html5绘图技术事项调用摄像头拍照;

    在mui框架中调用手机摄像头进行拍照可以直接使用原声的HTML5: 以下是HTML代码 <video id="video" width="640" hei ...

  5. QT 如何让release生成的EXE可在他人电脑上运行(尝试了全网的方法后,这应该能帮你解决)

    这两天,迅速上手了QT并用其基于C++编写了一个含UI的小程序 跳过中间的开发阶段 当我兴致满满的要将程序打包时,却是费尽周折,搜尽全网的关于QT的打包教程,最后都不顶用. 后面自己和队友的共同发现, ...

  6. 2020极客大挑战Web题

    前言 wp是以前写的,整理一下发上来. 不是很全. 2020 极客大挑战 WEB 1.sha1碰撞 题目 图片: 思路 题目说,换一种请求方式.于是换成post.得到一给含有代码的图片 图片: 分析该 ...

  7. JAVASE Scanner

    package com.huang.boke.flowPath;import java.util.Scanner;public class test01 { public static void ma ...

  8. shell、bash和sh区别

    shell是你(用户)和Linux(或者更准确的说,是你和Linux内核)之间的接口程序.你在提示符下输入的每个命令都由shell先解释然后传给Linux内核. shell 是一个命令语言解释器(co ...

  9. Machine Learning 学习笔记 03 最小二乘法、极大似然法、交叉熵

    损失函数. 最小二乘法. 极大似然估计. 复习一下对数. 交叉熵. 信息量. 系统熵的定义. KL散度

  10. Python Pandas库 初步使用

    用pandas+numpy读取UCI iris数据集中鸢尾花的萼片.花瓣长度数据,进行数据清理,去重,排序,并求出和.累积和.均值.标准差.方差.最大值.最小值