【Spring Boot 3】的安全防线:整合 【Spring Security 6】

金歌  金牌会员 | 2024-6-20 17:33:04 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 921|帖子 921|积分 2763

简介

Spring Security 是 Spring 家属中的一个安全管理框架。相比与别的一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
一般Web应用的需要举行认证和授权。
认证:验证当前访问系统的是不是本系统的用户,而且要确认详细是哪个用户
授权:经过认证后判定当前用户是否有权限举行某个使用
而认证和授权也是SpringSecurity作为安全框架的核心功能。
1.快速入门

1.1.引入依赖

  1. <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-security</artifactId>
  5.     <version>3.1.8</version>
  6. </dependency>
复制代码
如果是gradle则使用
  1. // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
  2. implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '3.1.8'
复制代码
引入SpringSecurity依赖后,再次输入所在,都会统一调转到一个登录界面,登录用户名是user,密码是在项目启动时,输出在控制台



2.SpringBoot整合Redis

   我是在Windos环境下安装Redis,这里在Windows下启动Redis 需要进入到安装目次库
  输入 redis-server.exe redis.windows.conf
  

2.1.引入依赖

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4.     <version>3.1.8</version>
  5. </dependency>
复制代码
  1. implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.1.8'
复制代码
2.2.设置Redis

   在设置文件中对redis举行设置
  1. # redis相关配置
  2. spring:
  3.   data:
  4.     redis:
  5.       port: 6379
  6.       host: 127.0.0.1
复制代码
2.3.使用Redis Template

2.3.1.将Redis Template注入到Spring容器中

   主要是为了 统一管理
  1. @Configuration
  2. public class RedisTemplateConfig {
  3.     @Bean("sysMyRedisTemplate")
  4.     public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  5.         RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
  6.         redisTemplate.setConnectionFactory(redisConnectionFactory);
  7.         RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  8.         ObjectMapper om = new ObjectMapper();
  9.         // 持久化改动.设置可见性,
  10.         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  11.         // 持久化改动.非final类型的对象,把对象类型也序列化进去,以便反序列化推测正确的类型
  12.         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  13.         // 持久化改动.null字段不显示
  14.         om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  15.         // 持久化改动.POJO无public属性或方法时不报错
  16.         om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
  17.         // 持久化改动.setObjectMapper方法移除.使用构造方法传入ObjectMapper
  18.         GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
  19.         redisTemplate.setKeySerializer(redisSerializer);
  20.         redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  21.         redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  22.         redisTemplate.setHashKeySerializer(redisSerializer);
  23.         redisTemplate.afterPropertiesSet();
  24.         return redisTemplate;
  25.     }
  26. }
复制代码
2.3.2.RedisTemplate工具类

   为了方便使用,可以封装一下工具类进使用用
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.data.redis.core.BoundSetOperations;
  3. import org.springframework.data.redis.core.HashOperations;
  4. import org.springframework.data.redis.core.RedisTemplate;
  5. import org.springframework.data.redis.core.ValueOperations;
  6. import org.springframework.stereotype.Component;
  7. import java.util.*;
  8. import java.util.concurrent.TimeUnit;
  9. @Component
  10. public class RedisCache {
  11.     @Autowired
  12.     public RedisTemplate redisTemplate;
  13.     /**
  14.      * 缓存基本的对象,Integer、String、实体类等
  15.      *
  16.      * @param key   缓存的键值
  17.      * @param value 缓存的值
  18.      */
  19.     public <T> void setCacheObject(final String key, final T value) {
  20.         redisTemplate.opsForValue().set(key, value);
  21.     }
  22.     /**
  23.      * 缓存基本的对象,Integer、String、实体类等
  24.      *
  25.      * @param key      缓存的键值
  26.      * @param value    缓存的值
  27.      * @param timeout  时间
  28.      * @param timeUnit 时间颗粒度
  29.      */
  30.     public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
  31.         redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
  32.     }
  33.     /**
  34.      * 设置有效时间
  35.      *
  36.      * @param key     Redis键
  37.      * @param timeout 超时时间
  38.      * @return true=设置成功;false=设置失败
  39.      */
  40.     public boolean expire(final String key, final long timeout) {
  41.         return expire(key, timeout, TimeUnit.SECONDS);
  42.     }
  43.     /**
  44.      * 设置有效时间
  45.      *
  46.      * @param key     Redis键
  47.      * @param timeout 超时时间
  48.      * @param unit    时间单位
  49.      * @return true=设置成功;false=设置失败
  50.      */
  51.     public boolean expire(final String key, final long timeout, final TimeUnit unit) {
  52.         return redisTemplate.expire(key, timeout, unit);
  53.     }
  54.     /**
  55.      * 获得缓存的基本对象。
  56.      *
  57.      * @param key 缓存键值
  58.      * @return 缓存键值对应的数据
  59.      */
  60.     public <T> T getCacheObject(final String key) {
  61.         ValueOperations<String, T> operation = redisTemplate.opsForValue();
  62.         return operation.get(key);
  63.     }
  64.     /**
  65.      * 删除单个对象
  66.      *
  67.      * @param key
  68.      */
  69.     public boolean deleteObject(final String key) {
  70.         return redisTemplate.delete(key);
  71.     }
  72.     /**
  73.      * 删除集合对象
  74.      *
  75.      * @param collection 多个对象
  76.      * @return
  77.      */
  78.     public long deleteObject(final Collection collection) {
  79.         return redisTemplate.delete(collection);
  80.     }
  81.     /**
  82.      * 缓存List数据
  83.      *
  84.      * @param key      缓存的键值
  85.      * @param dataList 待缓存的List数据
  86.      * @return 缓存的对象
  87.      */
  88.     public <T> long setCacheList(final String key, final List<T> dataList) {
  89.         Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
  90.         return count == null ? 0 : count;
  91.     }
  92.     /**
  93.      * 获得缓存的list对象
  94.      *
  95.      * @param key 缓存的键值
  96.      * @return 缓存键值对应的数据
  97.      */
  98.     public <T> List<T> getCacheList(final String key) {
  99.         return redisTemplate.opsForList().range(key, 0, -1);
  100.     }
  101.     /**
  102.      * 缓存Set
  103.      *
  104.      * @param key     缓存键值
  105.      * @param dataSet 缓存的数据
  106.      * @return 缓存数据的对象
  107.      */
  108.     public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
  109.         BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
  110.         Iterator<T> it = dataSet.iterator();
  111.         while (it.hasNext()) {
  112.             setOperation.add(it.next());
  113.         }
  114.         return setOperation;
  115.     }
  116.     /**
  117.      * 获得缓存的set
  118.      *
  119.      * @param key
  120.      * @return
  121.      */
  122.     public <T> Set<T> getCacheSet(final String key) {
  123.         return redisTemplate.opsForSet().members(key);
  124.     }
  125.     /**
  126.      * 缓存Map
  127.      *
  128.      * @param key
  129.      * @param dataMap
  130.      */
  131.     public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
  132.         if (dataMap != null) {
  133.             redisTemplate.opsForHash().putAll(key, dataMap);
  134.         }
  135.     }
  136.     /**
  137.      * 获得缓存的Map
  138.      *
  139.      * @param key
  140.      * @return
  141.      */
  142.     public <T> Map<String, T> getCacheMap(final String key) {
  143.         return redisTemplate.opsForHash().entries(key);
  144.     }
  145.     /**
  146.      * 往Hash中存入数据
  147.      *
  148.      * @param key   Redis键
  149.      * @param hKey  Hash键
  150.      * @param value 值
  151.      */
  152.     public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
  153.         redisTemplate.opsForHash().put(key, hKey, value);
  154.     }
  155.     /**
  156.      * 获取Hash中的数据
  157.      *
  158.      * @param key  Redis键
  159.      * @param hKey Hash键
  160.      * @return Hash中的对象
  161.      */
  162.     public <T> T getCacheMapValue(final String key, final String hKey) {
  163.         HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
  164.         return opsForHash.get(key, hKey);
  165.     }
  166.     public void incrementCacheMapValue(String key, String hKey, int v) {
  167.         redisTemplate.opsForHash().increment(key, hKey, v);
  168.     }
  169.     /**
  170.      * 删除Hash中的数据
  171.      *
  172.      * @param key
  173.      * @param hkey
  174.      */
  175.     public void delCacheMapValue(final String key, final String hkey) {
  176.         HashOperations hashOperations = redisTemplate.opsForHash();
  177.         hashOperations.delete(key, hkey);
  178.     }
  179.     /**
  180.      * 获取多个Hash中的数据
  181.      *
  182.      * @param key   Redis键
  183.      * @param hKeys Hash键集合
  184.      * @return Hash对象集合
  185.      */
  186.     public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
  187.         return redisTemplate.opsForHash().multiGet(key, hKeys);
  188.     }
  189.     /**
  190.      * 获得缓存的基本对象列表
  191.      *
  192.      * @param pattern 字符串前缀
  193.      * @return 对象列表
  194.      */
  195.     public Collection<String> keys(final String pattern) {
  196.         return redisTemplate.keys(pattern);
  197.     }
  198. }
复制代码
2.3.3.测试

   测试是否能正常使用
  1.         @RequestMapping("/redis")
  2.         public String redis(){
  3.                 redisCache.setCacheObject("test", "test");
  4.                 return redisCache.getCacheObject("test").toString();
  5.         }
复制代码

3.SpringBoot整合JJWT

3.1.引入依赖

  1. <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
  2. <dependency>
  3.     <groupId>io.jsonwebtoken</groupId>
  4.     <artifactId>jjwt</artifactId>
  5.     <version>0.12.5</version>
  6. </dependency>
复制代码
  1. implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.12.5'
复制代码
3.2.JJW工具类

   为了方便使用,我们将其封装成一个工具类
由于使用的版本是新版本的JDK 以及 JJWT所以网 这里的工具类 写法会有些出入
  1. /**
  2. * JWT Token工具类,用于生成和解析JWT Token
  3. *
  4. * @Author: Tiam
  5. * @Date: 2023/10/23 16:38
  6. */
  7. public class TokenUtil {
  8.     /**
  9.      * 过期时间(单位:秒)
  10.      */
  11.     public static final int ACCESS_EXPIRE = 60 * 60 * 60;
  12.     /**
  13.      * 加密算法
  14.      */
  15.     private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;
  16.     /**
  17.      * 私钥 / 生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取。
  18.      * 切记:秘钥不能外露,在任何场景都不应该流露出去。
  19.      * 应该大于等于 256位(长度32及以上的字符串),并且是随机的字符串
  20.      */
  21.     public final static String SECRET = "secrasdddddddddddddddddddddddddddddddddwqeqeqwewqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqetKey";
  22.     /**
  23.      * 秘钥实例
  24.      */
  25.     public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
  26.     /**
  27.      * jwt签发者
  28.      */
  29.     private final static String JWT_ISS = "Tiam";
  30.     /**
  31.      * jwt主题
  32.      */
  33.     private final static String SUBJECT = "Peripherals";
  34.     /**
  35.      * 生成访问令牌
  36.      *
  37.      * @param username 用户名
  38.      * @return 访问令牌
  39.      */
  40.     public static String genAccessToken(String username) {
  41.         // 生成令牌ID
  42.         String uuid = UUID.randomUUID().toString();
  43.         // 设置过期时间
  44.         Date expireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE));
  45.         return Jwts.builder()
  46.                 // 设置头部信息
  47.                 .header()
  48.                 .add("typ", "JWT")
  49.                 .add("alg", "HS256")
  50.                 .and()
  51.                 // 设置自定义负载信息
  52.                 .claim("username", username)
  53.                 .id(uuid) // 令牌ID
  54.                 .expiration(expireDate) // 过期日期
  55.                 .issuedAt(new Date()) // 签发时间
  56.                 .subject(SUBJECT) // 主题
  57.                 .issuer(JWT_ISS) // 签发者
  58.                 .signWith(KEY, ALGORITHM) // 签名
  59.                 .compact();
  60.     }
  61.     /**
  62.      * 获取payload中的用户信息
  63.      *
  64.      * @param token JWT Token
  65.      * @return 用户信息
  66.      */
  67.     public static String getUserFromToken(String token) {
  68.         String user = "";
  69.         Claims claims = parseClaims(token);
  70.         if (claims != null) {
  71.             user = (String) claims.get("username");
  72.         }
  73.         return user;
  74.     }
  75.     /**
  76.      * 获取JWT令牌的过期时间
  77.      *
  78.      * @param token JWT令牌
  79.      * @return 过期时间的毫秒级时间戳
  80.      */
  81.     public static long getExpirationTime(String token) {
  82.         Claims claims = parseClaims(token);
  83.         if (claims != null) {
  84.             return claims.getExpiration().getTime();
  85.         }
  86.         return 0L;
  87.     }
  88.     /**
  89.      * 解析token
  90.      *
  91.      * @param token token
  92.      * @return Jws<Claims>
  93.      */
  94.     public static Jws<Claims> parseClaim(String token) {
  95.         return Jwts.parser()
  96.                 .verifyWith(KEY)
  97.                 .build()
  98.                 .parseSignedClaims(token);
  99.     }
  100.     /**
  101.      * 解析token的头部信息
  102.      *
  103.      * @param token token
  104.      * @return token的头部信息
  105.      */
  106.     public static JwsHeader parseHeader(String token) {
  107.         return parseClaim(token).getHeader();
  108.     }
  109.     /**
  110.      * 解析token的载荷信息
  111.      *
  112.      * @param token token
  113.      * @return token的载荷信息
  114.      */
  115.     public static Claims parsePayload(String token) {
  116.         return parseClaim(token).getPayload();
  117.     }
  118.     /**
  119.      * 解析JWT Token中的Claims
  120.      *
  121.      * @param token JWT Token
  122.      * @return Claims
  123.      */
  124.     public static Claims parseClaims(String token) {
  125.         try {
  126.             return Jwts.parser()
  127.                     .setSigningKey(KEY)
  128.                     .build()
  129.                     .parseClaimsJws(token)
  130.                     .getBody();
  131.         } catch (Exception e) {
  132.             return null;
  133.         }
  134.     }
  135. }
复制代码
3.3.测试

  1.         @RequestMapping("/jjwt")
  2.         public Map<String, String> jjwt(){
  3.                 Map<String, String> map = new HashMap<>();
  4.                 String tokenByKey = TokenUtil.genAccessToken("hrfan");
  5.                 map.put("encoding", tokenByKey);
  6.                 return map;
  7.         }
复制代码

4.实战

配景
   在企业开辟中,一个安全的登录授权系统是至关重要的,它不仅可以保护用户的隐私信息,还能够确保只有经过授权的用户才气够访问特定的资源和功能。这样的系统不仅仅是为了满足用户的安全需求,也是为了保护企业的敏感数据和资源免受未经授权的访问和恶意攻击。
  起首,一个安全的登录授权系统必须具备可靠的身份验证机制。用户需要能够通过输入凭据(通常是用户名和密码)来验证其身份。这个过程需要包管用户的密码被安全地存储,而且在传输过程中使用加密技能保障用户凭据的安全性。
其次,授权系统需要根据用户的身份和角色来管理用户对资源和功能的访问权限。不同的用户可能具有不同的角色和权限,比方普通用户、管理员、审计员等。系统需要根据用户的角色和权限来限制他们对资源的访问,以确保敏感数据不会被未经授权的用户获取。
下面使用SpringSecurity来实现一个简易的登录认证
用户身份验证

  • 登录页面: 我们需要一个登录页面,用户可以在该页面输入他们的凭据以举行身份验证。登录页面应该友爱且易于明确。
  • 身份验证: 用户的用户名和密码应该被验证,只有在验证通过后才气进入系统。密码应该以安全的方式存储,比方使用哈希算法加密存储。
  • 认证失败处理: 如果用户提供的凭据无效,则系统应该向用户提供相应的错误消息,并允许他们再次尝试登录。
访问控制

  • 受保护资源: 我们的系统将有一些受保护的资源和功能,比方管理课程、学生信息等。只有经过身份验证的用户才气访问这些资源。
  • 角色和权限: 不同类型的用户应该有不同的角色和权限。比方,管理员可能具有管理课程和学生的权限,而普通用户可能只能访问课程内容。
  • 未经授权的访问: 如果用户尝试访问他们没有权限的资源,则系统应该拒绝访问,并向用户表现适当的错误消息。
安全性

  • 防范攻击: 我们的系统应该能够防范常见的安全攻击,如跨站脚本攻击、SQL注入等。
  • 密码安全: 用户的密码不应以明文形式存储在数据库中,而应该使用安全的加密算法举行存储。
4.1.创建数据库表

4.1.1.创建用户表

   Spring Security要求实现UserDetails接口是为了统一表现用户身份和权限信息,以便于在认证和授权过程中使用。UserDetails提供了标准化的用户信息模子,包罗用户名、密码、权限等,使得Spring Security能够与不同的用户信息源集成,同时提供机动性和可定制性。
  RBCA模子先容
RBAC(Role-Based Access Control)模子是一种访问控制模子,它基于角色来管理对资源的访问权限。在RBAC模子中,用户被分配到不同的角色,而每个角色具有特定的权限。这种模子使得权限管理更加机动和可扩展,同时低落了管理的复杂性。


  • user表代表系统中的用户。
  • role表代表系统中的角色。
  • permission表代表系统中的权限。
  • user_role表用于关联用户与角色。
  • role_permission表用于关联角色与权限。

  1. CREATE TABLE "hr_manager"."t_sys_my_user" (
  2.         "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
  3.         "user_no" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  4.         "user_name" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  5.         "password" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  6.         "nick_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  7.         "phone_number" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  8.         "email" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  9.         "department_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  10.         "department_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  11.         "is_admin" VARCHAR ( 1 ) COLLATE "pg_catalog"."default",
  12.         "sex" VARCHAR ( 1 ) COLLATE "pg_catalog"."default",
  13.         "post_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  14.         "post_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  15.         "is_account_non_expired" bool,
  16.         "is_account_non_locked" bool,
  17.         "is_credentials_non_expired" bool,
  18.         "is_enabled" bool,
  19.         "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  20.         "insert_time" DATE,
  21.         "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  22.         "update_time" DATE,
  23.         "license_code" VARCHAR ( 20 ) COLLATE "pg_catalog"."default",
  24.         CONSTRAINT "t_sys_my_user_pkey" PRIMARY KEY ( "sid" )
  25. );
  26. ALTER TABLE "hr_manager"."t_sys_my_user" OWNER TO "postgres";
  27. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."sid" IS '主键SID';
  28. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."user_no" IS '用户登录账号';
  29. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."user_name" IS '用户名称';
  30. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."password" IS '用户密码';
  31. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."nick_name" IS '用户昵称';
  32. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."phone_number" IS '手机号码';
  33. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."email" IS '邮箱';
  34. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."department_id" IS '部门ID';
  35. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."department_name" IS '部门名称';
  36. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_admin" IS '是否为管理员 0 否 1 是';
  37. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."sex" IS '性别 0 男 1 女';
  38. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."post_id" IS '岗位ID';
  39. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."post_name" IS '岗位名称';
  40. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_account_non_expired" IS '账户是否过期';
  41. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_account_non_locked" IS '账户是否被锁定';
  42. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_credentials_non_expired" IS '密码是否过期';
  43. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_enabled" IS '账户是否可用';
  44. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."insert_user" IS '创建人';
  45. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."insert_time" IS '创建时间';
  46. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."update_user" IS '更新人';
  47. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."update_time" IS '更新时间';
  48. COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."license_code" IS '许可标识';
复制代码
4.1.2.创建权限表

  1. CREATE TABLE "hr_manager"."t_sys_my_permission" (
  2.         "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
  3.         "parent_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  4.         "parent_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  5.         "permission_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  6.         "permission_code" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  7.         "router_path" VARCHAR ( 255 ) COLLATE "pg_catalog"."default",
  8.         "router_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  9.         "auth_url" VARCHAR ( 255 ) COLLATE "pg_catalog"."default",
  10.         "order_no" int4,
  11.         "type" VARCHAR ( 1 ) COLLATE "pg_catalog"."default",
  12.         "icon" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  13.         "remark" VARCHAR ( 255 ) COLLATE "pg_catalog"."default",
  14.         "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  15.         "insert_time" DATE,
  16.         "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  17.         "update_time" DATE,
  18.         "license_code" VARCHAR ( 20 ) COLLATE "pg_catalog"."default",
  19.         CONSTRAINT "t_sys_my_permission_pkey" PRIMARY KEY ( "sid" )
  20. );
  21. ALTER TABLE "hr_manager"."t_sys_my_permission" OWNER TO "postgres";
  22. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."sid" IS '主键SID';
  23. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."parent_id" IS '父节点ID';
  24. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."parent_name" IS '父节点名称';
  25. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."permission_name" IS '权限名称';
  26. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."permission_code" IS '授权标识符';
  27. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."router_path" IS '路由地址';
  28. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."router_name" IS '路由名称';
  29. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."auth_url" IS '授权路径(对应文件在项目的地址)';
  30. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."order_no" IS '序号(用于排序)';
  31. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."type" IS '类型 0 目录 1 菜单 2 按钮';
  32. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."icon" IS '图标';
  33. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."remark" IS '备注';
  34. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."insert_user" IS '创建人';
  35. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."insert_time" IS '创建时间';
  36. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."update_user" IS '更新人';
  37. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."update_time" IS '更新时间';
  38. COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."license_code" IS '许可标识';
复制代码
4.1.3.创建角色表

  1. CREATE TABLE "hr_manager"."t_sys_my_role" (
  2.         "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
  3.         "role_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default",
  4.         "remark" VARCHAR ( 255 ) COLLATE "pg_catalog"."default",
  5.         "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  6.         "insert_time" DATE,
  7.         "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  8.         "update_time" DATE,
  9.         "status" VARCHAR ( 255 ) COLLATE "pg_catalog"."default",
  10.         CONSTRAINT "t_sys_my_role_pkey" PRIMARY KEY ( "sid" )
  11. );
  12. ALTER TABLE "hr_manager"."t_sys_my_role" OWNER TO "postgres";
  13. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."sid" IS '主键SID';
  14. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."role_name" IS '角色名称';
  15. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."remark" IS '备注';
  16. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."insert_user" IS '创建人';
  17. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."insert_time" IS '创建时间';
  18. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."update_user" IS '更新人';
  19. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."update_time" IS '更新时间';
  20. COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."status" IS '是否使用 0 禁用 1 使用';
复制代码
4.1.4.创建用户角色表

  1. CREATE TABLE "hr_manager"."t_sys_my_user_role" (
  2.         "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
  3.         "role_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  4.         "user_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  5.         "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  6.         "insert_time" DATE,
  7.         CONSTRAINT "t_sys_my_user_role_pkey" PRIMARY KEY ( "sid" )
  8. );
  9. ALTER TABLE "hr_manager"."t_sys_my_user_role" OWNER TO "postgres";
  10. COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."sid" IS '主键SID';
  11. COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."role_sid" IS '角色SID';
  12. COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."user_sid" IS '用户SID';
  13. COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."insert_user" IS '创建人';
  14. COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."insert_time" IS '创建时间';
复制代码
4.1.5.创建角色权限表

  1. CREATE TABLE "hr_manager"."t_sys_my_role_permission" (
  2.         "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
  3.         "role_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  4.         "permission_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  5.         "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
  6.         "insert_time" DATE,
  7.         CONSTRAINT "t_sys_my_role_permission_pkey" PRIMARY KEY ( "sid" )
  8. );
  9. ALTER TABLE "hr_manager"."t_sys_my_role_permission" OWNER TO "postgres";
  10. COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."sid" IS '主键SID';
  11. COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."role_sid" IS '角色SID';
  12. COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."permission_sid" IS '权限SID';
  13. COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."insert_user" IS '创建人';
  14. COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."insert_time" IS '创建时间';
复制代码
4.2.创建实体类

4.2.1.创建用户实体类

  1. @Data
  2. public class SysMyUser implements Serializable, UserDetails {
  3.     private static final long serialVersionUID = 1L;
  4.     @TableId
  5.     /**
  6.      * sid
  7.      */
  8.     private String sid;
  9.     /**
  10.      * user_no
  11.      */
  12.     private String userNo;
  13.     /**
  14.      * user_name
  15.      */
  16.     private String userName;
  17.     /**
  18.      * password
  19.      */
  20.     private String password;
  21.     /**
  22.      * nick_name
  23.      */
  24.     private String nickName;
  25.     /**
  26.      * phone_number
  27.      */
  28.     private String phoneNumber;
  29.     /**
  30.      * email
  31.      */
  32.     private String email;
  33.     /**
  34.      * department_id
  35.      */
  36.     private String departmentId;
  37.     /**
  38.      * department_name
  39.      */
  40.     private String departmentName;
  41.     /**
  42.      * is_admin
  43.      */
  44.     private String isAdmin;
  45.     /**
  46.      * sex
  47.      */
  48.     private String sex;
  49.     /**
  50.      * post_id
  51.      */
  52.     private String postId;
  53.     /**
  54.      * post_name
  55.      */
  56.     private String postName;
  57.     /**
  58.      * is_account_non_expired
  59.      */
  60.     private Boolean isAccountNonExpired;
  61.     /**
  62.      * is_account_non_locked
  63.      */
  64.     private Boolean isAccountNonLocked;
  65.     /**
  66.      * is_credentials_non_expired
  67.      */
  68.     private Boolean isCredentialsNonExpired;
  69.     /**
  70.      * is_enabled
  71.      */
  72.     private Boolean isEnabled;
  73.     /**
  74.      * insert_user
  75.      */
  76.     private String insertUser;
  77.     /**
  78.      * insert_time
  79.      */
  80.     private String insertTime;
  81.     /**
  82.      * update_user
  83.      */
  84.     private String updateUser;
  85.     /**
  86.      * update_time
  87.      */
  88.     private String updateTime;
  89.     /**
  90.      * license_code
  91.      */
  92.     private String licenseCode;
  93.     /**
  94.      * 权限列表 就是菜单列表
  95.      */
  96.     @TableField(exist = false)
  97.     private List<SysMyPermission> permissionList;
  98.     /**
  99.      * 认证信息 就是用户配置code
  100.      */
  101.     @TableField(exist = false)
  102.     Collection<? extends GrantedAuthority> authorities;
  103.     /**
  104.      * 用户权限信息
  105.      */
  106.     @TableField(exist = false)
  107.     private List<String> roles;
  108.     @Override
  109.     public Collection<? extends GrantedAuthority> getAuthorities() {
  110.         return authorities;
  111.     }
  112.     @Override
  113.     public String getUsername() {
  114.         return this.userNo;
  115.     }
  116.     @Override
  117.     public String getPassword() {
  118.         return this.password;
  119.     }
  120.     @Override
  121.     public boolean isAccountNonExpired() {
  122.         return this.isAccountNonExpired;
  123.     }
  124.     @Override
  125.     public boolean isAccountNonLocked() {
  126.         return this.isAccountNonLocked;
  127.     }
  128.     @Override
  129.     public boolean isCredentialsNonExpired() {
  130.         return this.isCredentialsNonExpired;
  131.     }
  132.     @Override
  133.     public boolean isEnabled() {
  134.         return this.isEnabled;
  135.     }
  136. }
复制代码
4.2.2.创建权限实体类

  1. @Data
  2. public class SysMyPermission implements Serializable {
  3.     private static final long serialVersionUID = 1L;
  4.     @TableId
  5.     /**
  6.     * sid
  7.     */
  8.     private String sid;
  9.     /**
  10.     * parent_id
  11.     */
  12.     private String parentId;
  13.     /**
  14.     * parent_name
  15.     */
  16.     private String parentName;
  17.     /**
  18.     * permission_name
  19.     */
  20.     private String permissionName;
  21.     /**
  22.     * permission_code
  23.     */
  24.     private String permissionCode;
  25.     /**
  26.     * router_path
  27.     */
  28.     private String routerPath;
  29.     /**
  30.     * router_name
  31.     */
  32.     private String routerName;
  33.     /**
  34.     * auth_url
  35.     */
  36.     private String authUrl;
  37.     /**
  38.     * order_no
  39.     */
  40.     private String orderNo;
  41.     /**
  42.     * type
  43.     */
  44.     private String type;
  45.     /**
  46.     * icon
  47.     */
  48.     private String icon;
  49.     /**
  50.     * remark
  51.     */
  52.     private String remark;
  53.     /**
  54.     * insert_user
  55.     */
  56.     private String insertUser;
  57.     /**
  58.     * insert_time
  59.     */
  60.     private String insertTime;
  61.     /**
  62.     * update_user
  63.     */
  64.     private String updateUser;
  65.     /**
  66.     * update_time
  67.     */
  68.     private String updateTime;
  69.     /**
  70.     * license_code
  71.     */
  72.     private String licenseCode;
  73.     /**
  74.      * 菜单的子集合
  75.      */
  76.     @TableField(exist = false)
  77.     @JsonInclude(JsonInclude.Include.NON_NULL)
  78.     private List<SysMyPermission> children = new ArrayList<>();
  79. }
复制代码
4.3.创建Service和Dao

   这里就不过多先容了,直接贴上代码
  4.3.1.UserService

  1. @Service
  2. public class SysMyUserService {
  3.     @Resource
  4.     private SysMyUserMapper userMapper;
  5.     /**
  6.      * 根据用户id获取用户信息(包含用户具备的权限信息)
  7.      * @param username 用户信息
  8.      * @return
  9.      */
  10.     public SysMyUser getUserInfoByUserId(String username) {
  11.         // 获取用户的基础信息
  12.         SysMyUser userInfo = userMapper.getUserInfoByUserId(username);
  13.         Assert.notNull(userInfo, "用户不存在");
  14.         // 根据用户id对应的权限信息
  15.         List<String> autorizedList = userMapper.getAutorizedListByUserId(userInfo.getSid());;
  16.         userInfo.setRoles(autorizedList);
  17.         return userInfo;
  18.     }
  19.     /**
  20.      * 获取加密后的密码 ,使用BCryptPasswordEncoder加密 10次 生成密码
  21.      * @param password 密码
  22.      * @return 加密后的密码
  23.      */
  24.     public String getEncoderPassword(String password) {
  25.         BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
  26.         String encodePassword = encoder.encode(password);
  27.         return encodePassword;
  28.     }
  29. }
复制代码
4.3.2.UserMapper

  1. @Repository
  2. public interface SysMyUserMapper extends BaseMapper<SysMyUser> {
  3.         /**
  4.          * 根据用户名账号获取用户信息
  5.          * @param username 用户信息
  6.          * @return 用户信息
  7.          */
  8.         SysMyUser getUserInfoByUserId(@Param("username") String username);
  9.         /**
  10.          * 根据用户id获取用户具备的权限信息
  11.          * @param sid 用户id
  12.          * @return 用户具备的权限信息
  13.          */
  14.         List<String> getAutorizedListByUserId(@Param("sid") String sid);
  15. }
复制代码
4.3.3.UserMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.sys.my.core.user.dao.SysMyUserMapper">
  4.     <!-- 根据用户名账号获取用户信息 -->
  5.     <select id="getUserInfoByUserId" resultType="com.sys.my.core.user.model.SysMyUser">
  6.         select * from t_sys_my_user u where u.user_no = #{username};
  7.     </select>
  8.     <!-- 根据用户id获取用户具备的权限信息 -->
  9.     <select id="getAutorizedListByUserId" resultType="java.lang.String">
  10.         select
  11.             p.permission_code
  12.         from t_sys_my_role r
  13.                  left join t_sys_my_user_role ur on ur.role_sid = r.sid
  14.                  left join t_sys_my_role_permission rp on rp.role_sid = r.sid
  15.                  left join t_sys_my_permission p on p.sid = rp.permission_sid
  16.                  left join t_sys_my_user u on u.sid = ur.user_sid
  17.         where p.status = '1' and r.status = '1' and u.sid = #{sid};
  18.     </select>
  19. </mapper>
复制代码
4.3.4.SysMyPermissionService

  1. @Service
  2. public class SysMyPermissionService {
  3.         private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  4.     @Resource
  5.     private SysMyPermissionMapper sysMyPermissionMapper;
  6.     /**
  7.      * 根据用户id查询对应的权限
  8.      * @param userId 用户id
  9.      * @return 权限列表
  10.      */
  11.     public List<SysMyPermission> getPermissionListByUserId(String userId){
  12.         // 根据用户ID获取用户对应的权限
  13.         return sysMyPermissionMapper.getMenuListByUserId(userId);
  14.     }
  15. }
复制代码
4.3.5.SysMyPermissionMapper

  1. @Repository
  2. public interface SysMyPermissionMapper extends BaseMapper<SysMyPermission> {
  3.         /**
  4.          * 根据用户ID获取用户对应的权限
  5.          * @param userId 用户ID
  6.          * @return 权限列表
  7.          */
  8.         List<SysMyPermission> getMenuListByUserId(@Param("userId") String userId);
  9. }
复制代码
4.3.6.SysMyPermissionMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.sys.my.core.permission.dao.SysMyPermissionMapper">
  4.     <!-- 根据用户id获取用户具备的权限信息 -->
  5.     <select id="getMenuListByUserId" resultType="com.sys.my.core.permission.model.SysMyPermission">
  6.         select
  7.             p.*
  8.         from t_sys_my_role r
  9.                  left join t_sys_my_user_role ur on ur.role_sid = r.sid
  10.                  left join t_sys_my_role_permission rp on rp.role_sid = r.sid
  11.                  left join t_sys_my_permission p on p.sid = rp.permission_sid
  12.                  left join t_sys_my_user u on u.sid = ur.user_sid
  13.         where p.status = '1' and r.status = '1' and u.sid = #{userId};
  14.     </select>
  15. </mapper>
复制代码
4.4.重写UserDetailsService方法

   重写 Spring Security 中的 UserDetailsService 接口的主要目的是提供自定义的用户认证逻辑。Spring Security 的 UserDetailsService 负责从数据源(通常是数据库)中加载用户信息,包罗用户名、密码和权限等,以便举行身份验证。
  通常情况下,我们需要重写 UserDetailsService 的 loadUserByUsername() 方法,该方法吸收用户名作为参数,并返回一个 UserDetails 对象,该对象包罗了与用户名对应的用户信息。在现实开辟中,我们可能需要自定义的用户信息存储方式,或者盼望在加载用户信息时举行一些特定的逻辑处理,比如自定义密码加密方式、从数据库或其他数据源加载用户信息等。
  1. /**
  2. * 自定义UserDetailsService 用于认证和授权
  3. * 此处把用户的信息和权限交给spring security
  4. * spring security会对用户的信息和权限信息进行管理
  5. * @author hffan
  6. * serDetailService接口主要定义了一个方法 l
  7. * oadUserByUsername(String username)用于完成用户信息的查询,
  8. * 其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接口,
  9. * 完成数据库查询,该接口返回UserDetail。
  10. */
  11. @Component("customerUserDetailsService")
  12. public class CustomerUserDetailsService implements UserDetailsService {
  13.     @Resource
  14.     private SysMyUserService userService;
  15.     @Resource
  16.     private SysMyPermissionService permissionService;
  17.     @Override
  18.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  19.         SysMyUser user = userService.getUserInfoByUserId(username);
  20.         // 如果用户不存在
  21.         if (user == null){
  22.             throw new UsernameNotFoundException("用户名或者密码错误");
  23.         }
  24.         // 根据用户id查询用户权限
  25.         List<SysMyPermission> permissionList = permissionService.getPermissionListByUserId(user.getSid());
  26.         // 取出权限中配置code
  27.         List<String> collect = permissionList.stream().filter(item -> item != null)
  28.                                                        .map(item -> item.getPermissionCode())
  29.                                                        .filter(item -> item != null)
  30.                                                        .collect(Collectors.toList());
  31.         // 转为数据
  32.         String[] strings = collect.toArray(new String[collect.size()]);
  33.         List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(strings);
  34.         // 配置权限
  35.         user.setAuthorities(authorityList);
  36.         // 配置菜单
  37.         user.setPermissionList(permissionList);
  38.         // 授权
  39.         return user;
  40.     }
  41. }
复制代码
4.5.自定义异常

   自定义异常,通过传入的异常 可以获取对应的信息返回给前端
  4.5.1.Token认证自定义异常

  1. /**
  2. * 自定义异常
  3. * AuthenticationException 是spring security提供的异常
  4. * 通过传入的异常 可以获取对应的信息返回给前端
  5. * token异常
  6. */
  7. public class TokenException extends AuthenticationException {
  8.     public TokenException(String msg) {
  9.         super(msg);
  10.     }
  11. }
复制代码
4.5.2.用户认证自定义异常

  1. /**
  2. * 自定义异常
  3. * 通过传入的异常 可以获取对应的信息返回给前端
  4. * 用户认证异常
  5. */
  6. public class CustomerAuthenionException extends AuthenticationException {
  7.     public CustomerAuthenionException(String msg) {
  8.         super(msg);
  9.     }
  10. }
复制代码
4.6.编写自定义处理器

   通过实现SpringSecurity提供的一些接口,我们可以更好地管理身份验证和授权流程,提高用户体验和应用程序的安全性。
  4.6.1.匿名用户访问处理器

AuthenticationEntryPoint


  • 作用:AuthenticationEntryPoint 用于处理用户尝试访问受保护资源但未举行身份验证的情况。当用户尝试访问需要身份验证的资源但尚未举行身份验证时,AuthenticationEntryPoint 将被调用来触发身份验证流程。
  • 详细讲解:当用户尝试访问安全受保护的资源但未举行身份验证时,AuthenticationEntryPoint 的 commence() 方法将被调用。在这个方法中,我们可以定制返回响应给用户,比方重定向到登录页面或返回401未授权错误等。
  1. /**
  2. * 匿名用户访问资源处理器
  3. */
  4. @Component("loginAuthenticationHandler")
  5. public class LoginAuthenticationHandler implements AuthenticationEntryPoint {
  6.     @Override
  7.     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
  8.         response.setContentType("application/json;charset=UTF-8");
  9.         ServletOutputStream out = response.getOutputStream();
  10.         String res = JSONObject.toJSONString(ResultObject.createInstance(false,600,"匿名用户没有权限进行访问!"));
  11.         out.write(res.getBytes("UTF-8"));
  12.         out.flush();
  13.         out.close();
  14.     }
  15. }
复制代码
4.6.2.认证用户无权限处理器

AccessDeniedHandler


  • 作用:AccessDeniedHandler 用于处理用户尝试访问受保护资源但权限不敷的情况。当用户固然举行了身份验证,但由于缺乏足够的权限而被拒绝访问资源时,AccessDeniedHandler 将被调用。
  • 详细讲解:AccessDeniedHandler 的 handle() 方法在访问被拒绝时被调用。我们可以在这个方法中定义自定义的行为,比方返回自定义的错误页面、向用户发送通知或记载拒绝的访问尝试。
  1. /**
  2. * 认证用户访问无权限处理器
  3. */
  4. @Component("loginAccessDefineHandler")
  5. public class LoginAccessDefineHandler implements AccessDeniedHandler {
  6.     @Override
  7.     public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
  8.         response.setContentType("application/json;charset=UTF-8");
  9.         ServletOutputStream out = response.getOutputStream();
  10.         String res = JSONObject.toJSONString(ResultObject.createInstance(false,700,"您没有开通对应的权限,请联系管理员!"));
  11.         out.write(res.getBytes("UTF-8"));
  12.         out.flush();
  13.         out.close();
  14.     }
  15. }
复制代码
4.6.3.账户信息异常处理器

AuthenticationFailureHandler


  • 作用:AuthenticationFailureHandler 用于处理身份验证失败的情况。当用户提供的凭据无效或身份验证过程出现错误时,AuthenticationFailureHandler 将被调用。
  • 详细讲解:AuthenticationFailureHandler 的 onAuthenticationFailure() 方法在身份验证失败时被调用。我们可以在这个方法中执行自定义的行为,比方记载登录失败次数、向用户发送通知或返回自定义的错误页面。
  1. @Component("loginFiledHandler")
  2. public class LoginFiledHandler implements AuthenticationFailureHandler {
  3.     @Override
  4.     public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
  5.         //1.设置响应编码
  6.         response.setContentType("application/json;charset=UTF-8");
  7.         ServletOutputStream out = response.getOutputStream();
  8.         String str = null;
  9.         int code = 500;
  10.         if(exception instanceof AccountExpiredException){
  11.             str = "账户过期,登录失败!";
  12.         }else if(exception instanceof BadCredentialsException){
  13.             str = "用户名或密码错误,登录失败!";
  14.         }else if(exception instanceof CredentialsExpiredException){
  15.             str = "密码过期,登录失败!";
  16.         }else if(exception instanceof DisabledException){
  17.             str = "账户被禁用,登录失败!";
  18.         }else if(exception instanceof LockedException){
  19.             str = "账户被锁,登录失败!";
  20.         }else if(exception instanceof InternalAuthenticationServiceException){
  21.             str = "账户不存在,登录失败!";
  22.         }else if(exception instanceof CustomerAuthenionException){
  23.             //token验证失败
  24.             code = 600;
  25.             str = exception.getMessage();
  26.         } else{
  27.             str = "登录失败!";
  28.         }
  29.         // 设置返回格式
  30.         String res = JSONObject.toJSONString(ResultObject.createInstance(false,str));
  31.         out.write(res.getBytes("UTF-8"));
  32.         out.flush();
  33.         out.close();
  34.     }
  35. }
复制代码
4.6.4.登录成功处理器

AuthenticationSuccessHandler


  • 作用:AuthenticationSuccessHandler 用于处理身份验证成功的情况。当用户成功举行身份验证并被授权访问资源时,AuthenticationSuccessHandler 将被调用。
  • 详细讲解:AuthenticationSuccessHandler 的 onAuthenticationSuccess() 方法在身份验证成功时被调用。我们可以在这个方法中执行自定义的行为,比方记载登录成功的日志、向用户发送欢迎消息或重定向到特定页面。
  1. /**
  2. * 自定义认证成功处理器
  3. */
  4. @Component("loginSuccessHandler")
  5. public class LoginSuccessHandler implements AuthenticationSuccessHandler {
  6.     @Resource
  7.     private RedisCache redisCache;
  8.     @Override
  9.     public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  10.         SysMyUser user = (SysMyUser)authentication.getPrincipal();
  11.         // 登录成功处理
  12.         //1.生成token
  13.         String token = TokenUtil.genAccessToken(user.getUsername());
  14.         long expireTime = TokenUtil.getExpirationTime(token);
  15.         // 配置一下返回给前端的token信息
  16.         LoginResultObject vo = new LoginResultObject();
  17.         // 将实体类信息转为JSON
  18.         // TODO 将token存入coookie中 后面加载页面 根据用户的id取查询对应的权限
  19.         vo.setUserInfo(user);
  20.         vo.setCode(200L);
  21.         // TODO 将token存放到redis中 退出或者修改密码 清空token 获取的时候 也从redis中进行获取
  22.         redisCache.setCacheObject(httpServletRequest.getRemoteAddr(),token,TokenUtil.ACCESS_EXPIRE, TimeUnit.MILLISECONDS);
  23.         vo.setToken(token);
  24.         vo.setExpireTime(expireTime);
  25.         String res = JSONObject.toJSONString(vo);
  26.         httpServletResponse.setContentType("application/json;charset=UTF-8");
  27.         ServletOutputStream out = httpServletResponse.getOutputStream();
  28.         out.write(res.getBytes("UTF-8"));
  29.         out.flush();
  30.         out.close();
  31.     }
  32. }
复制代码
4.7.自定义过滤器

   实现 Spring Security 中的 OncePerRequestFilter 接口,用于处理用户请求的过滤逻辑。
  

  • 该过滤器用于对用户的请求举行拦截,验证用户的访问权限和身份信息。
  • 如果请求的 URL 是某些特定的资源或者登录页面,则直接放行。
  • 如果不是登录请求,则对请求中的 token 举行验证,以确保用户的身份信息有用。
  • 如果验证通过,则将用户的身份信息设置到 Spring Security 的上下文中,从而完成用户的身份认证。
  

  • @Component("checkTokenFilter"):将该类声明为 Spring 组件,并指定其名称为 “checkTokenFilter”。
  • @EqualsAndHashCode(callSuper=false):天生 equals() 和 hashCode() 方法,忽略父类 OncePerRequestFilter。
  • @Data:Lombok 注解,主动天生 getter、setter、equals、hashCode 等方法。
  • @Autowired 和 @Value:用于依赖注入和获取设置信息。
    1. doFilterInternal
    复制代码
    方法:这是 OncePerRequestFilter 类的抽象方法,用于实现详细的请求过滤逻辑。

    • 起首判定请求的 URL 是否属于特定的资源,如果是则放行。
    • 判定是否是登录请求,如果是,则直接放行。
    • 如果不是登录请求,则验证请求中的 token,确保用户的身份信息有用。
    • 如果 token 验证失败,则调用 AuthenticationFailureHandler 处理身份验证失败的情况。
    • 如果 token 验证通过,则将用户的身份信息设置到 Spring Security 的上下文中。

    1. validateToken
    复制代码
    方法:用于验证请求中的 token。

    • 起首从请求头部获取 token,如果没有则从请求参数中获取,如果仍旧没有则从 Redis 缓存中获取。
    • 解析 token,获取其中的用户名。
    • 根据用户名加载用户信息,使用自定义的 CustomerUserDetailsService。
    • 如果用户信息加载成功,则创建 UsernamePasswordAuthenticationToken,并将用户信息设置到 Spring Security 上下文中。

  • 最后调用 filterChain.doFilter(httpServletRequest, httpServletResponse),将请求传递给下一个过滤器处理。
  1. @Data@Component("checkTokenFilter")@EqualsAndHashCode(callSuper=false)public class CheckTokenFilter extends OncePerRequestFilter {    @Value("${hrfan.login.url}")    private String loginUrl;    @Autowired    private LoginFiledHandler loginFailureHandler;    @Autowired    private CustomerUserDetailsService customerUserDetailsService;    @Resource    private RedisCache redisCache;    @Override    protected void doFilterInternal
  2. (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {        //获取请求的url(读取设置文件的url)        String url = httpServletRequest.getRequestURI();        if (StringUtils.contains(httpServletRequest.getServletPath(), "swagger")                || StringUtils.contains(httpServletRequest.getServletPath(), "webjars")                || StringUtils.contains(httpServletRequest.getServletPath(), "v3")                || StringUtils.contains(httpServletRequest.getServletPath(), "profile")                || StringUtils.contains(httpServletRequest.getServletPath(), "swagger-ui")                || StringUtils.contains(httpServletRequest.getServletPath(), "swagger-resources")                || StringUtils.contains(httpServletRequest.getServletPath(), "csrf")                || StringUtils.contains(httpServletRequest.getServletPath(), "favicon")                || StringUtils.contains(httpServletRequest.getServletPath(), "v2")                || StringUtils.contains(httpServletRequest.getServletPath(), "user")                || StringUtils.contains(httpServletRequest.getServletPath(), "getImageCode")) {            filterChain.doFilter(httpServletRequest, httpServletResponse);        }else if (StringUtils.equals(url,loginUrl)){            // 是登录请求放行            filterChain.doFilter(httpServletRequest, httpServletResponse);        }        else {            try {                //token验证(如果不是登录请求 验证toekn)                if(!url.equals(loginUrl)){                    validateToken
  3. (httpServletRequest);                }            }catch (AuthenticationException e){                loginFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);                return;            }            filterChain.doFilter(httpServletRequest,httpServletResponse);        }    }    //token验证    private void validateToken
  4. (HttpServletRequest request){        //从请求的头部获取token        String token = request.getHeader("token");        //如果请求头部没有获取到token,则从请求参数中获取token        if(StringUtils.isEmpty(token)){            token = request.getParameter("token");        }        if (StringUtils.isEmpty(token)){            // 请求参数中也没有 那就从redis中举行获取根据ip所在取            token = redisCache.getCacheObject(request.getRemoteAddr());        }        if(StringUtils.isEmpty(token)){            throw new CustomerAuthenionException("token不存在!");        }        //解析token        String username = TokenUtil.getUserFromToken(token);        if(StringUtils.isEmpty(username)){            throw new CustomerAuthenionException("token解析失败!");        }        //获取用户信息        UserDetails user = customerUserDetailsService.loadUserByUsername(username);        if(user == null){            throw new CustomerAuthenionException("token验证失败!");        }        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));        //设置到spring security上下文        SecurityContextHolder.getContext().setAuthentication(authenticationToken);    }}
复制代码
4.8.设置登录返复书息

   用户返回用户登录 成功或者失败的信息,成功后需要包罗用户的相关信息 和token
  1. /**
  2. * 登录返回信息
  3. */
  4. @Data
  5. public class LoginResultObject {
  6.     private String token;
  7.     //token过期时间
  8.     private Long expireTime;
  9.     private SysMyUser userInfo;
  10.     private Long code;
  11. }
复制代码
4.9.编写SpringSecurity设置

  1. #### 注意
  2.         因为新版本的SpringSecurity和旧版本的差距较大,所以这里保留了旧版本的写法
  3.         我使用的SpringBoot 和 SpringSecurity 版本都是相对较新的 3.1.8版本 JDK版本是21
复制代码
  1. import com.sys.my.config.security.details_service.CustomerUserDetailsService;
  2. import com.sys.my.config.security.filter.CheckTokenFilter;
  3. import com.sys.my.config.security.handler.LoginAccessDefineHandler;
  4. import com.sys.my.config.security.handler.LoginAuthenticationHandler;
  5. import com.sys.my.config.security.handler.LoginFiledHandler;
  6. import com.sys.my.config.security.handler.LoginSuccessHandler;
  7. import jakarta.annotation.Resource;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12. import org.springframework.security.config.http.SessionCreationPolicy;
  13. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  14. import org.springframework.security.crypto.password.PasswordEncoder;
  15. import org.springframework.security.web.SecurityFilterChain;
  16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  17. import org.springframework.web.cors.CorsConfiguration;
  18. import org.springframework.web.cors.CorsConfigurationSource;
  19. import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  20. import java.util.Collections;
  21. /**
  22. * SpringSecurity配置类
  23. */
  24. @Configuration
  25. @EnableWebSecurity  //启用Spring Security
  26. public class SpringSecurityConfig {
  27.     @Resource
  28.     private CustomerUserDetailsService customerUserDetailsService;
  29.     @Resource
  30.     private LoginSuccessHandler loginSuccessHandler;
  31.     @Resource
  32.     private LoginFiledHandler loginFiledHandler;
  33.     @Resource
  34.     private LoginAuthenticationHandler loginAuthenticationHandler;
  35.     @Resource
  36.     private LoginAccessDefineHandler loginAccessDefineHandler;
  37.     @Resource
  38.     private CheckTokenFilter checkTokenFilter;
  39.     /**
  40.      * 密码处理
  41.      * @return
  42.      */
  43.     @Bean
  44.     public PasswordEncoder passwordEncoder(){
  45.         return new BCryptPasswordEncoder();
  46.     }
  47.     /**
  48.      * 新版的实现方法不再和旧版一样在配置类里面重写方法,而是构建了一个过滤链对象并通过@Bean注解注入到IOC容器中
  49.      * 新版整体代码 (注意:新版AuthenticationManager认证管理器默认全局)
  50.      * @param http http安全配置
  51.      * @return SecurityFilterChain
  52.      * @throws Exception 异常
  53.      */
  54.     @Bean
  55.     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  56.         http    // 使用自己自定义的过滤器 去过滤接口请求
  57.                 .addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class)
  58.                 .formLogin((formLogin) ->
  59.                         // 这里更改SpringSecurity的认证接口地址,这样就默认处理这个接口的登录请求了
  60.                         formLogin.loginProcessingUrl("/api/v1/user/login")
  61.                                 // 自定义的登录验证成功或失败后的去向
  62.                                 .successHandler(loginSuccessHandler).failureHandler(loginFiledHandler)
  63.                 )
  64.                     // 禁用了 CSRF 保护。
  65.                 .csrf((csrf) -> csrf.disable())
  66.                     // 配置了会话管理策略为 STATELESS(无状态)。在无状态的会话管理策略下,应用程序不会创建或使用 HTTP 会话,每个请求都是独立的,服务器不会在请求之间保留任何状态信息。
  67.                 .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  68.                 .authorizeRequests((authorizeRequests) ->
  69.                         // 这里过滤一些 不需要token的接口地址
  70.                         authorizeRequests
  71.                                 .requestMatchers("/api/v1/test/getTestInfo").permitAll()
  72.                                 .requestMatchers(  "/v3/**","/profile/**","/swagger-ui.html",
  73.                                         "/swagger-resources/**",
  74.                                         "/v2/api-docs",
  75.                                         "/v3/api-docs",
  76.                                         "/webjars/**","/swagger-ui/**","/v2/**","/favicon.ico","/webjars/springfox-swagger-ui/**","/static/**", "/webjars/**", "/v2/api-docs", "/v2/feign-docs",
  77.                                         "/swagger-resources/configuration/ui",
  78.                                         "/test/user",
  79.                                         "/swagger-resources", "/swagger-resources/configuration/security",
  80.                                         "/swagger-ui.html", "/webjars/**").permitAll()
  81.                                 .requestMatchers("/api/v1/user/login","/api/v1/user/getImageCode").permitAll()
  82.                                 .anyRequest().authenticated()
  83.                 )
  84.                 .exceptionHandling((exceptionHandling) -> exceptionHandling
  85.                         .authenticationEntryPoint(loginAuthenticationHandler) // 匿名处理
  86.                         .accessDeniedHandler(loginAccessDefineHandler)  // 无权限处理
  87.                 )
  88.                 .cors((cors) -> cors.configurationSource(configurationSource()))
  89.                 .headers((headers) -> headers.frameOptions((frameOptionsConfig -> frameOptionsConfig.disable())))
  90.                 .headers((headers) -> headers.frameOptions((frameOptionsConfig -> frameOptionsConfig.sameOrigin())));
  91.         // 构建过滤链并返回
  92.         return http.build();
  93.     }
  94.     // 旧版本 需要继承  extends WebSecurityConfigurerAdapter
  95.     // 新版的比较简单,直接定义好数据源,注入就可以了,无需手动到配置类中去将它提交给AuthenticationManager进行管理。
  96.     // /**
  97.     //  * 配置认证处理器
  98.     //  * 自定义的UserDetailsService
  99.     //  * @param auth
  100.     //  * @throws Exception
  101.     //  */
  102.     // @Override
  103.     // protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  104.     //     auth.userDetailsService(customerUserDetailsService);
  105.     // }
  106.     // /**
  107.     //  * 配置权限资源
  108.     //  * @param http
  109.     //  * @throws Exception
  110.     //  */
  111.     // @Override
  112.     // protected void configure(HttpSecurity http) throws Exception {
  113.     //     // 每次请求前检查token
  114.     //     http.addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class);
  115.     //     http.formLogin()
  116.     //             .loginProcessingUrl("/api/v1/user/login")
  117.     //             // 自定义的登录验证成功或失败后的去向
  118.     //             .successHandler(loginSuccessHandler).failureHandler(loginFiledHandler)
  119.     //             // 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
  120.     //             .and().csrf().disable()
  121.     //             .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  122.     //             .and()
  123.     //             .authorizeRequests()
  124.     //             .antMatchers("/api/v1/test/getTestInfo").permitAll()
  125.     //             // 放心swagger相关请求
  126.     //             .antMatchers(  "/v3/**","/profile/**","/swagger-ui.html",
  127.     //                     "/swagger-resources/**",
  128.     //                     "/v2/api-docs",
  129.     //                     "/v3/api-docs",
  130.     //                     "/webjars/**","/swagger-ui/**","/v2/**","/favicon.ico","/webjars/springfox-swagger-ui/**","/static/**", "/webjars/**", "/v2/api-docs", "/v2/feign-docs",
  131.     //                     "/swagger-resources/configuration/ui",
  132.     //                     "/swagger-resources", "/swagger-resources/configuration/security",
  133.     //                     "/swagger-ui.html", "/webjars/**").permitAll()
  134.     //             .antMatchers("/api/v1/user/login","/api/v1/user/getImageCode").permitAll()
  135.     //             .anyRequest().authenticated()
  136.     //             .and()
  137.     //             .exceptionHandling()
  138.     //             // 匿名处理
  139.     //             .authenticationEntryPoint(loginAuthenticationHandler)
  140.     //             // 无权限处理
  141.     //             .accessDeniedHandler(loginAccessDefineHandler)
  142.     //             // 跨域配置
  143.     //             .and()
  144.     //             .cors()
  145.     //             .configurationSource(configurationSource());
  146.     //     // 设置iframe
  147.     //     http.headers().frameOptions().sameOrigin();
  148.     //     http.headers().frameOptions().disable();
  149.     //
  150.     // }
  151.     /**
  152.      * 跨域配置
  153.      */
  154.     CorsConfigurationSource configurationSource() {
  155.         CorsConfiguration corsConfiguration = new CorsConfiguration();
  156.         corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
  157.         corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
  158.         corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
  159.         corsConfiguration.setMaxAge(3600L);
  160.         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  161.         source.registerCorsConfiguration("/**", corsConfiguration);
  162.         return source;
  163.     }
  164. }
复制代码
4.10.设置文件设置

  1. hrfan:
  2.   login:
  3.     url: "/api/v1/user/login"
复制代码
5.测试

5.1.测试登录密码错误


5.2.测试正确密码


5.3.测试无token访问接口

   SpringSecurity为我们提供了基于注解的权限控制方案。
  在启动类上加上 @EnableGlobalMethodSecurity(prePostEnabled = true)
  1.         @GetMapping("/jjwt")
  2.         @PreAuthorize("hasAuthority('user_list')")
  3.         public Map<String, String> jjwt(){
  4.         // 这里的user_list 就是我们权限中permission_code
  5.                 throw new RuntimeException("测试无token访问!");
  6.         }
复制代码

5.4.测试不登陆访问


5.5.测试登录访问不受限制接口



5.6.测试放开的通用接口 比方/**



5.7.测试权限标识 和数据库不一致



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表