赞
踩
Spring Security 是 Spring
家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
一般来说中大型的项目都是使用SpringSecurity
来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
一般Web应用的需要进行认证和授权。
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
而认证和授权也是SpringSecurity作为安全框架的核心功能。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.1.8</version>
</dependency>
如果是gradle则使用
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '3.1.8'
引入SpringSecurity依赖后,再次输入地址,都会统一调转到一个登录界面,登录用户名是user,密码是在项目启动时,输出在控制台
![image-20240217123503799](https://img-
blog.csdnimg.cn/img_convert/57f2d2db3f1b1abee88345ad4135778e.png)
![image-20240217123523370](https://img-
blog.csdnimg.cn/img_convert/f6f51e8d0a898083fdd87b6b46dad9a9.png)
![image-20240217123722190](https://img-
blog.csdnimg.cn/img_convert/ff275e667e82a84cdbc13cd4d1fc6114.png)
我是在Windos环境下安装Redis,这里在Windows下启动Redis 需要
进入到安装目录库
输入 redis-server.exe redis.windows.conf
![image-20240217124753947](https://img-
blog.csdnimg.cn/img_convert/d10732f4af38e91a88a920298413a394.png)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.1.8</version>
</dependency>
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '3.1.8'
在配置文件中对redis进行配置
# redis相关配置
spring:
data:
redis:
port: 6379
host: 127.0.0.1
主要是为了 统一管理
@Configuration public class RedisTemplateConfig { @Bean("sysMyRedisTemplate") public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, T> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); ObjectMapper om = new ObjectMapper(); // 持久化改动.设置可见性, om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 持久化改动.非final类型的对象,把对象类型也序列化进去,以便反序列化推测正确的类型 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 持久化改动.null字段不显示 om.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 持久化改动.POJO无public属性或方法时不报错 om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); // 持久化改动.setObjectMapper方法移除.使用构造方法传入ObjectMapper GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
为了方便使用,可以封装一下工具类进行使用
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; @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); } public void incrementCacheMapValue(String key, String hKey, int v) { redisTemplate.opsForHash().increment(key, hKey, v); } /** * 删除Hash中的数据 * * @param key * @param hkey */ 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); } }
测试是否能正常使用
@RequestMapping("/redis")
public String redis(){
redisCache.setCacheObject("test", "test");
return redisCache.getCacheObject("test").toString();
}
![image-20240217132211204](https://img-
blog.csdnimg.cn/img_convert/37625d3d9dcd38349bd72e3cfedbca5a.png)
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.5</version>
</dependency>
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.12.5'
为了方便使用,我们将其封装成一个工具类
由于使用的版本是新版本的JDK 以及 JJWT所以网 这里的工具类 写法会有些出入
/** * JWT Token工具类,用于生成和解析JWT Token * * @Author: Tiam * @Date: 2023/10/23 16:38 */ public class TokenUtil { /** * 过期时间(单位:秒) */ public static final int ACCESS_EXPIRE = 60 * 60 * 60; /** * 加密算法 */ private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256; /** * 私钥 / 生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取。 * 切记:秘钥不能外露,在任何场景都不应该流露出去。 * 应该大于等于 256位(长度32及以上的字符串),并且是随机的字符串 */ public final static String SECRET = "secrasdddddddddddddddddddddddddddddddddwqeqeqwewqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqetKey"; /** * 秘钥实例 */ public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); /** * jwt签发者 */ private final static String JWT_ISS = "Tiam"; /** * jwt主题 */ private final static String SUBJECT = "Peripherals"; /** * 生成访问令牌 * * @param username 用户名 * @return 访问令牌 */ public static String genAccessToken(String username) { // 生成令牌ID String uuid = UUID.randomUUID().toString(); // 设置过期时间 Date expireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE)); return Jwts.builder() // 设置头部信息 .header() .add("typ", "JWT") .add("alg", "HS256") .and() // 设置自定义负载信息 .claim("username", username) .id(uuid) // 令牌ID .expiration(expireDate) // 过期日期 .issuedAt(new Date()) // 签发时间 .subject(SUBJECT) // 主题 .issuer(JWT_ISS) // 签发者 .signWith(KEY, ALGORITHM) // 签名 .compact(); } /** * 获取payload中的用户信息 * * @param token JWT Token * @return 用户信息 */ public static String getUserFromToken(String token) { String user = ""; Claims claims = parseClaims(token); if (claims != null) { user = (String) claims.get("username"); } return user; } /** * 获取JWT令牌的过期时间 * * @param token JWT令牌 * @return 过期时间的毫秒级时间戳 */ public static long getExpirationTime(String token) { Claims claims = parseClaims(token); if (claims != null) { return claims.getExpiration().getTime(); } return 0L; } /** * 解析token * * @param token token * @return Jws<Claims> */ public static Jws<Claims> parseClaim(String token) { return Jwts.parser() .verifyWith(KEY) .build() .parseSignedClaims(token); } /** * 解析token的头部信息 * * @param token token * @return token的头部信息 */ public static JwsHeader parseHeader(String token) { return parseClaim(token).getHeader(); } /** * 解析token的载荷信息 * * @param token token * @return token的载荷信息 */ public static Claims parsePayload(String token) { return parseClaim(token).getPayload(); } /** * 解析JWT Token中的Claims * * @param token JWT Token * @return Claims */ public static Claims parseClaims(String token) { try { return Jwts.parser() .setSigningKey(KEY) .build() .parseClaimsJws(token) .getBody(); } catch (Exception e) { return null; } } }
@RequestMapping("/jjwt")
public Map<String, String> jjwt(){
Map<String, String> map = new HashMap<>();
String tokenByKey = TokenUtil.genAccessToken("hrfan");
map.put("encoding", tokenByKey);
return map;
}
![image-20240217141907501](https://img-
blog.csdnimg.cn/img_convert/4573ea6eb2b3f459c45d7552b7c03b8d.png)
背景
在企业开发中,一个安全的登录授权系统是至关重要的,它不仅可以保护用户的隐私信息,还能够确保只有经过授权的用户才能够访问特定的资源和功能。这样的系统不仅仅是为了满足用户的安全需求,也是为了保护企业的敏感数据和资源免受未经授权的访问和恶意攻击。
首先,一个安全的登录授权系统必须具备可靠的身份验证机制。用户需要能够通过输入凭据(通常是用户名和密码)来验证其身份。这个过程需要保证用户的密码被安全地存储,并且在传输过程中使用加密技术保障用户凭据的安全性。
其次,授权系统需要根据用户的身份和角色来管理用户对资源和功能的访问权限。不同的用户可能具有不同的角色和权限,例如普通用户、管理员、审计员等。系统需要根据用户的角色和权限来限制他们对资源的访问,以确保敏感数据不会被未经授权的用户获取。
下面使用SpringSecurity来实现一个简易的登录认证
用户身份验证
访问控制
安全性
Spring
Security要求实现UserDetails接口是为了统一表示用户身份和权限信息,以便于在认证和授权过程中使用。UserDetails提供了标准化的用户信息模型,包括用户名、密码、权限等,使得Spring
Security能够与不同的用户信息源集成,同时提供灵活性和可定制性。
RBCA模型介绍
RBAC(Role-Based Access Control)模型是一种访问控制模型
,它基于角色来管理对资源的访问权限。在RBAC模型中,用户被分配到不同的角色,而每个角色具有特定的权限。这种模型使得权限管理更加灵活和可扩展,同时降低了管理的复杂性。
![image-20240217233212640](https://img-
blog.csdnimg.cn/img_convert/891b05327c47a25ace79795edb2e4870.png)
CREATE TABLE "hr_manager"."t_sys_my_user" ( "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL, "user_no" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "user_name" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "password" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "nick_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "phone_number" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "email" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "department_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "department_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "is_admin" VARCHAR ( 1 ) COLLATE "pg_catalog"."default", "sex" VARCHAR ( 1 ) COLLATE "pg_catalog"."default", "post_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "post_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "is_account_non_expired" bool, "is_account_non_locked" bool, "is_credentials_non_expired" bool, "is_enabled" bool, "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "insert_time" DATE, "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "update_time" DATE, "license_code" VARCHAR ( 20 ) COLLATE "pg_catalog"."default", CONSTRAINT "t_sys_my_user_pkey" PRIMARY KEY ( "sid" ) ); ALTER TABLE "hr_manager"."t_sys_my_user" OWNER TO "postgres"; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."sid" IS '主键SID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."user_no" IS '用户登录账号'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."user_name" IS '用户名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."password" IS '用户密码'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."nick_name" IS '用户昵称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."phone_number" IS '手机号码'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."email" IS '邮箱'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."department_id" IS '部门ID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."department_name" IS '部门名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_admin" IS '是否为管理员 0 否 1 是'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."sex" IS '性别 0 男 1 女'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."post_id" IS '岗位ID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."post_name" IS '岗位名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_account_non_expired" IS '账户是否过期'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_account_non_locked" IS '账户是否被锁定'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_credentials_non_expired" IS '密码是否过期'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."is_enabled" IS '账户是否可用'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."insert_user" IS '创建人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."insert_time" IS '创建时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."update_user" IS '更新人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."update_time" IS '更新时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_user"."license_code" IS '许可标识';
CREATE TABLE "hr_manager"."t_sys_my_permission" ( "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL, "parent_id" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "parent_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "permission_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "permission_code" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "router_path" VARCHAR ( 255 ) COLLATE "pg_catalog"."default", "router_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "auth_url" VARCHAR ( 255 ) COLLATE "pg_catalog"."default", "order_no" int4, "type" VARCHAR ( 1 ) COLLATE "pg_catalog"."default", "icon" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "remark" VARCHAR ( 255 ) COLLATE "pg_catalog"."default", "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "insert_time" DATE, "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "update_time" DATE, "license_code" VARCHAR ( 20 ) COLLATE "pg_catalog"."default", CONSTRAINT "t_sys_my_permission_pkey" PRIMARY KEY ( "sid" ) ); ALTER TABLE "hr_manager"."t_sys_my_permission" OWNER TO "postgres"; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."sid" IS '主键SID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."parent_id" IS '父节点ID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."parent_name" IS '父节点名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."permission_name" IS '权限名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."permission_code" IS '授权标识符'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."router_path" IS '路由地址'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."router_name" IS '路由名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."auth_url" IS '授权路径(对应文件在项目的地址)'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."order_no" IS '序号(用于排序)'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."type" IS '类型 0 目录 1 菜单 2 按钮'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."icon" IS '图标'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."remark" IS '备注'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."insert_user" IS '创建人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."insert_time" IS '创建时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."update_user" IS '更新人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."update_time" IS '更新时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_permission"."license_code" IS '许可标识';
CREATE TABLE "hr_manager"."t_sys_my_role" ( "sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL, "role_name" VARCHAR ( 100 ) COLLATE "pg_catalog"."default", "remark" VARCHAR ( 255 ) COLLATE "pg_catalog"."default", "insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "insert_time" DATE, "update_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default", "update_time" DATE, "status" VARCHAR ( 255 ) COLLATE "pg_catalog"."default", CONSTRAINT "t_sys_my_role_pkey" PRIMARY KEY ( "sid" ) ); ALTER TABLE "hr_manager"."t_sys_my_role" OWNER TO "postgres"; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."sid" IS '主键SID'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."role_name" IS '角色名称'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."remark" IS '备注'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."insert_user" IS '创建人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."insert_time" IS '创建时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."update_user" IS '更新人'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."update_time" IS '更新时间'; COMMENT ON COLUMN "hr_manager"."t_sys_my_role"."status" IS '是否使用 0 禁用 1 使用';
CREATE TABLE "hr_manager"."t_sys_my_user_role" (
"sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
"role_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"user_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"insert_time" DATE,
CONSTRAINT "t_sys_my_user_role_pkey" PRIMARY KEY ( "sid" )
);
ALTER TABLE "hr_manager"."t_sys_my_user_role" OWNER TO "postgres";
COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."sid" IS '主键SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."role_sid" IS '角色SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."user_sid" IS '用户SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."insert_user" IS '创建人';
COMMENT ON COLUMN "hr_manager"."t_sys_my_user_role"."insert_time" IS '创建时间';
CREATE TABLE "hr_manager"."t_sys_my_role_permission" (
"sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default" NOT NULL,
"role_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"permission_sid" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"insert_user" VARCHAR ( 50 ) COLLATE "pg_catalog"."default",
"insert_time" DATE,
CONSTRAINT "t_sys_my_role_permission_pkey" PRIMARY KEY ( "sid" )
);
ALTER TABLE "hr_manager"."t_sys_my_role_permission" OWNER TO "postgres";
COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."sid" IS '主键SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."role_sid" IS '角色SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."permission_sid" IS '权限SID';
COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."insert_user" IS '创建人';
COMMENT ON COLUMN "hr_manager"."t_sys_my_role_permission"."insert_time" IS '创建时间';
@Data public class SysMyUser implements Serializable, UserDetails { private static final long serialVersionUID = 1L; @TableId /** * sid */ private String sid; /** * user_no */ private String userNo; /** * user_name */ private String userName; /** * password */ private String password; /** * nick_name */ private String nickName; /** * phone_number */ private String phoneNumber; /** * email */ private String email; /** * department_id */ private String departmentId; /** * department_name */ private String departmentName; /** * is_admin */ private String isAdmin; /** * sex */ private String sex; /** * post_id */ private String postId; /** * post_name */ private String postName; /** * is_account_non_expired */ private Boolean isAccountNonExpired; /** * is_account_non_locked */ private Boolean isAccountNonLocked; /** * is_credentials_non_expired */ private Boolean isCredentialsNonExpired; /** * is_enabled */ private Boolean isEnabled; /** * insert_user */ private String insertUser; /** * insert_time */ private String insertTime; /** * update_user */ private String updateUser; /** * update_time */ private String updateTime; /** * license_code */ private String licenseCode; /** * 权限列表 就是菜单列表 */ @TableField(exist = false) private List<SysMyPermission> permissionList; /** * 认证信息 就是用户配置code */ @TableField(exist = false) Collection<? extends GrantedAuthority> authorities; /** * 用户权限信息 */ @TableField(exist = false) private List<String> roles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getUsername() { return this.userNo; } @Override public String getPassword() { return this.password; } @Override public boolean isAccountNonExpired() { return this.isAccountNonExpired; } @Override public boolean isAccountNonLocked() { return this.isAccountNonLocked; } @Override public boolean isCredentialsNonExpired() { return this.isCredentialsNonExpired; } @Override public boolean isEnabled() { return this.isEnabled; } }
@Data public class SysMyPermission implements Serializable { private static final long serialVersionUID = 1L; @TableId /** * sid */ private String sid; /** * parent_id */ private String parentId; /** * parent_name */ private String parentName; /** * permission_name */ private String permissionName; /** * permission_code */ private String permissionCode; /** * router_path */ private String routerPath; /** * router_name */ private String routerName; /** * auth_url */ private String authUrl; /** * order_no */ private String orderNo; /** * type */ private String type; /** * icon */ private String icon; /** * remark */ private String remark; /** * insert_user */ private String insertUser; /** * insert_time */ private String insertTime; /** * update_user */ private String updateUser; /** * update_time */ private String updateTime; /** * license_code */ private String licenseCode; /** * 菜单的子集合 */ @TableField(exist = false) @JsonInclude(JsonInclude.Include.NON_NULL) private List<SysMyPermission> children = new ArrayList<>(); }
这里就不过多介绍了,直接贴上代码
@Service public class SysMyUserService { @Resource private SysMyUserMapper userMapper; /** * 根据用户id获取用户信息(包含用户具备的权限信息) * @param username 用户信息 * @return */ public SysMyUser getUserInfoByUserId(String username) { // 获取用户的基础信息 SysMyUser userInfo = userMapper.getUserInfoByUserId(username); Assert.notNull(userInfo, "用户不存在"); // 根据用户id对应的权限信息 List<String> autorizedList = userMapper.getAutorizedListByUserId(userInfo.getSid());; userInfo.setRoles(autorizedList); return userInfo; } /** * 获取加密后的密码 ,使用BCryptPasswordEncoder加密 10次 生成密码 * @param password 密码 * @return 加密后的密码 */ public String getEncoderPassword(String password) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); String encodePassword = encoder.encode(password); return encodePassword; } }
@Repository public interface SysMyUserMapper extends BaseMapper<SysMyUser> { /** * 根据用户名账号获取用户信息 * @param username 用户信息 * @return 用户信息 */ SysMyUser getUserInfoByUserId(@Param("username") String username); /** * 根据用户id获取用户具备的权限信息 * @param sid 用户id * @return 用户具备的权限信息 */ List<String> getAutorizedListByUserId(@Param("sid") String sid); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sys.my.core.user.dao.SysMyUserMapper"> <!-- 根据用户名账号获取用户信息 --> <select id="getUserInfoByUserId" resultType="com.sys.my.core.user.model.SysMyUser"> select * from t_sys_my_user u where u.user_no = #{username}; </select> <!-- 根据用户id获取用户具备的权限信息 --> <select id="getAutorizedListByUserId" resultType="java.lang.String"> select p.permission_code from t_sys_my_role r left join t_sys_my_user_role ur on ur.role_sid = r.sid left join t_sys_my_role_permission rp on rp.role_sid = r.sid left join t_sys_my_permission p on p.sid = rp.permission_sid left join t_sys_my_user u on u.sid = ur.user_sid where p.status = '1' and r.status = '1' and u.sid = #{sid}; </select> </mapper>
@Service public class SysMyPermissionService { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @Resource private SysMyPermissionMapper sysMyPermissionMapper; /** * 根据用户id查询对应的权限 * @param userId 用户id * @return 权限列表 */ public List<SysMyPermission> getPermissionListByUserId(String userId){ // 根据用户ID获取用户对应的权限 return sysMyPermissionMapper.getMenuListByUserId(userId); } }
@Repository
public interface SysMyPermissionMapper extends BaseMapper<SysMyPermission> {
/**
* 根据用户ID获取用户对应的权限
* @param userId 用户ID
* @return 权限列表
*/
List<SysMyPermission> getMenuListByUserId(@Param("userId") String userId);
}
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sys.my.core.permission.dao.SysMyPermissionMapper"> <!-- 根据用户id获取用户具备的权限信息 --> <select id="getMenuListByUserId" resultType="com.sys.my.core.permission.model.SysMyPermission"> select p.* from t_sys_my_role r left join t_sys_my_user_role ur on ur.role_sid = r.sid left join t_sys_my_role_permission rp on rp.role_sid = r.sid left join t_sys_my_permission p on p.sid = rp.permission_sid left join t_sys_my_user u on u.sid = ur.user_sid where p.status = '1' and r.status = '1' and u.sid = #{userId}; </select> </mapper>
重写 Spring Security 中的 UserDetailsService 接口的主要目的是
提供自定义的用户认证逻辑
。Spring
Security 的 UserDetailsService 负责从数据源(通常是数据库)中加载用户信息,包括用户名、密码和权限等,以便进行身份验证。通常情况下,我们需要重写
UserDetailsService
的loadUserByUsername()
方法,该方法接收用户名作为参数,并返回一个 UserDetails
对象,该对象包含了与用户名对应的用户信息。在实际开发中,我们可能需要自定义的用户信息存储方式,或者希望在加载用户信息时进行一些特定的逻辑处理,比如自定义密码加密方式、从数据库或其他数据源加载用户信息等。
/** * 自定义UserDetailsService 用于认证和授权 * 此处把用户的信息和权限交给spring security * spring security会对用户的信息和权限信息进行管理 * @author hffan * serDetailService接口主要定义了一个方法 l * oadUserByUsername(String username)用于完成用户信息的查询, * 其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接口, * 完成数据库查询,该接口返回UserDetail。 */ @Component("customerUserDetailsService") public class CustomerUserDetailsService implements UserDetailsService { @Resource private SysMyUserService userService; @Resource private SysMyPermissionService permissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysMyUser user = userService.getUserInfoByUserId(username); // 如果用户不存在 if (user == null){ throw new UsernameNotFoundException("用户名或者密码错误"); } // 根据用户id查询用户权限 List<SysMyPermission> permissionList = permissionService.getPermissionListByUserId(user.getSid()); // 取出权限中配置code List<String> collect = permissionList.stream().filter(item -> item != null) .map(item -> item.getPermissionCode()) .filter(item -> item != null) .collect(Collectors.toList()); // 转为数据 String[] strings = collect.toArray(new String[collect.size()]); List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(strings); // 配置权限 user.setAuthorities(authorityList); // 配置菜单 user.setPermissionList(permissionList); // 授权 return user; } }
自定义异常,通过传入的异常 可以获取对应的信息返回给前端
/**
* 自定义异常
* AuthenticationException 是spring security提供的异常
* 通过传入的异常 可以获取对应的信息返回给前端
* token异常
*/
public class TokenException extends AuthenticationException {
public TokenException(String msg) {
super(msg);
}
}
/**
* 自定义异常
* 通过传入的异常 可以获取对应的信息返回给前端
* 用户认证异常
*/
public class CustomerAuthenionException extends AuthenticationException {
public CustomerAuthenionException(String msg) {
super(msg);
}
}
通过实现SpringSecurity提供的一些接口,我们可以更好地管理身份验证和授权流程,提高用户体验和应用程序的安全性。
AuthenticationEntryPoint :
/**
* 匿名用户访问资源处理器
*/
@Component("loginAuthenticationHandler")
public class LoginAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream out = response.getOutputStream();
String res = JSONObject.toJSONString(ResultObject.createInstance(false,600,"匿名用户没有权限进行访问!"));
out.write(res.getBytes("UTF-8"));
out.flush();
out.close();
}
}
AccessDeniedHandler :
/** * 认证用户访问无权限处理器 */ @Component("loginAccessDefineHandler") public class LoginAccessDefineHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream out = response.getOutputStream(); String res = JSONObject.toJSONString(ResultObject.createInstance(false,700,"您没有开通对应的权限,请联系管理员!")); out.write(res.getBytes("UTF-8")); out.flush(); out.close(); } }
AuthenticationFailureHandler :
@Component("loginFiledHandler") public class LoginFiledHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { //1.设置响应编码 response.setContentType("application/json;charset=UTF-8"); ServletOutputStream out = response.getOutputStream(); String str = null; int code = 500; if(exception instanceof AccountExpiredException){ str = "账户过期,登录失败!"; }else if(exception instanceof BadCredentialsException){ str = "用户名或密码错误,登录失败!"; }else if(exception instanceof CredentialsExpiredException){ str = "密码过期,登录失败!"; }else if(exception instanceof DisabledException){ str = "账户被禁用,登录失败!"; }else if(exception instanceof LockedException){ str = "账户被锁,登录失败!"; }else if(exception instanceof InternalAuthenticationServiceException){ str = "账户不存在,登录失败!"; }else if(exception instanceof CustomerAuthenionException){ //token验证失败 code = 600; str = exception.getMessage(); } else{ str = "登录失败!"; } // 设置返回格式 String res = JSONObject.toJSONString(ResultObject.createInstance(false,str)); out.write(res.getBytes("UTF-8")); out.flush(); out.close(); } }
AuthenticationSuccessHandler :
/** * 自定义认证成功处理器 */ @Component("loginSuccessHandler") public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Resource private RedisCache redisCache; @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { SysMyUser user = (SysMyUser)authentication.getPrincipal(); // 登录成功处理 //1.生成token String token = TokenUtil.genAccessToken(user.getUsername()); long expireTime = TokenUtil.getExpirationTime(token); // 配置一下返回给前端的token信息 LoginResultObject vo = new LoginResultObject(); // 将实体类信息转为JSON // TODO 将token存入coookie中 后面加载页面 根据用户的id取查询对应的权限 vo.setUserInfo(user); vo.setCode(200L); // TODO 将token存放到redis中 退出或者修改密码 清空token 获取的时候 也从redis中进行获取 redisCache.setCacheObject(httpServletRequest.getRemoteAddr(),token,TokenUtil.ACCESS_EXPIRE, TimeUnit.MILLISECONDS); vo.setToken(token); vo.setExpireTime(expireTime); String res = JSONObject.toJSONString(vo); httpServletResponse.setContentType("application/json;charset=UTF-8"); ServletOutputStream out = httpServletResponse.getOutputStream(); out.write(res.getBytes("UTF-8")); out.flush(); out.close(); } }
实现 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
:用于依赖注入和获取配置信息。
doFilterInternal
方法:这是 OncePerRequestFilter 类的抽象方法,用于实现具体的请求过滤逻辑。
* 首先判断请求的 URL 是否属于特定的资源,如果是则放行。
* 判断是否是登录请求,如果是,则直接放行。
* 如果不是登录请求,则验证请求中的 token,确保用户的身份信息有效。
* 如果 token 验证失败,则调用 AuthenticationFailureHandler 处理身份验证失败的情况。
* 如果 token 验证通过,则将用户的身份信息设置到 Spring Security 的上下文中。
validateToken
方法:用于验证请求中的 token。
* 首先从请求头部获取 token,如果没有则从请求参数中获取,如果仍然没有则从 Redis 缓存中获取。
* 解析 token,获取其中的用户名。
* 根据用户名加载用户信息,使用自定义的 CustomerUserDetailsService。
* 如果用户信息加载成功,则创建 UsernamePasswordAuthenticationToken,并将用户信息设置到 Spring Security 上下文中。
最后调用 filterChain.doFilter(httpServletRequest, httpServletResponse)
,将请求传递给下一个过滤器处理。
@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(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(httpServletRequest); } }catch (AuthenticationException e){ loginFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e); return; } filterChain.doFilter(httpServletRequest,httpServletResponse); } } //token验证 private void validateToken(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); }
}
用户返回用户登录 成功或者失败的信息,成功后需要包含用户的相关信息 和token
/**
* 登录返回信息
*/
@Data
public class LoginResultObject {
private String token;
//token过期时间
private Long expireTime;
private SysMyUser userInfo;
private Long code;
}
#### 注意 因为新版本的SpringSecurity和旧版本的差距较大,所以这里保留了旧版本的写法 我使用的SpringBoot 和 SpringSecurity 版本都是相对较新的 3.1.8版本 JDK版本是21 import com.sys.my.config.security.details_service.CustomerUserDetailsService; import com.sys.my.config.security.filter.CheckTokenFilter; import com.sys.my.config.security.handler.LoginAccessDefineHandler; import com.sys.my.config.security.handler.LoginAuthenticationHandler; import com.sys.my.config.security.handler.LoginFiledHandler; import com.sys.my.config.security.handler.LoginSuccessHandler; import jakarta.annotation.Resource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Collections; /** * SpringSecurity配置类 */ @Configuration @EnableWebSecurity //启用Spring Security public class SpringSecurityConfig { @Resource private CustomerUserDetailsService customerUserDetailsService; @Resource private LoginSuccessHandler loginSuccessHandler; @Resource private LoginFiledHandler loginFiledHandler; @Resource private LoginAuthenticationHandler loginAuthenticationHandler; @Resource private LoginAccessDefineHandler loginAccessDefineHandler; @Resource private CheckTokenFilter checkTokenFilter; /** * 密码处理 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 新版的实现方法不再和旧版一样在配置类里面重写方法,而是构建了一个过滤链对象并通过@Bean注解注入到IOC容器中 * 新版整体代码 (注意:新版AuthenticationManager认证管理器默认全局) * @param http http安全配置 * @return SecurityFilterChain * @throws Exception 异常 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 使用自己自定义的过滤器 去过滤接口请求 .addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class) .formLogin((formLogin) -> // 这里更改SpringSecurity的认证接口地址,这样就默认处理这个接口的登录请求了 formLogin.loginProcessingUrl("/api/v1/user/login") // 自定义的登录验证成功或失败后的去向 .successHandler(loginSuccessHandler).failureHandler(loginFiledHandler) ) // 禁用了 CSRF 保护。 .csrf((csrf) -> csrf.disable()) // 配置了会话管理策略为 STATELESS(无状态)。在无状态的会话管理策略下,应用程序不会创建或使用 HTTP 会话,每个请求都是独立的,服务器不会在请求之间保留任何状态信息。 .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests((authorizeRequests) -> // 这里过滤一些 不需要token的接口地址 authorizeRequests .requestMatchers("/api/v1/test/getTestInfo").permitAll() .requestMatchers( "/v3/**","/profile/**","/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "/v3/api-docs", "/webjars/**","/swagger-ui/**","/v2/**","/favicon.ico","/webjars/springfox-swagger-ui/**","/static/**", "/webjars/**", "/v2/api-docs", "/v2/feign-docs", "/swagger-resources/configuration/ui", "/test/user", "/swagger-resources", "/swagger-resources/configuration/security", "/swagger-ui.html", "/webjars/**").permitAll() .requestMatchers("/api/v1/user/login","/api/v1/user/getImageCode").permitAll() .anyRequest().authenticated() ) .exceptionHandling((exceptionHandling) -> exceptionHandling .authenticationEntryPoint(loginAuthenticationHandler) // 匿名处理 .accessDeniedHandler(loginAccessDefineHandler) // 无权限处理 ) .cors((cors) -> cors.configurationSource(configurationSource())) .headers((headers) -> headers.frameOptions((frameOptionsConfig -> frameOptionsConfig.disable()))) .headers((headers) -> headers.frameOptions((frameOptionsConfig -> frameOptionsConfig.sameOrigin()))); // 构建过滤链并返回 return http.build(); } // 旧版本 需要继承 extends WebSecurityConfigurerAdapter // 新版的比较简单,直接定义好数据源,注入就可以了,无需手动到配置类中去将它提交给AuthenticationManager进行管理。 // /** // * 配置认证处理器 // * 自定义的UserDetailsService // * @param auth // * @throws Exception // */ // @Override // protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.userDetailsService(customerUserDetailsService); // } // /** // * 配置权限资源 // * @param http // * @throws Exception // */ // @Override // protected void configure(HttpSecurity http) throws Exception { // // 每次请求前检查token // http.addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class); // http.formLogin() // .loginProcessingUrl("/api/v1/user/login") // // 自定义的登录验证成功或失败后的去向 // .successHandler(loginSuccessHandler).failureHandler(loginFiledHandler) // // 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。 // .and().csrf().disable() // .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // .and() // .authorizeRequests() // .antMatchers("/api/v1/test/getTestInfo").permitAll() // // 放心swagger相关请求 // .antMatchers( "/v3/**","/profile/**","/swagger-ui.html", // "/swagger-resources/**", // "/v2/api-docs", // "/v3/api-docs", // "/webjars/**","/swagger-ui/**","/v2/**","/favicon.ico","/webjars/springfox-swagger-ui/**","/static/**", "/webjars/**", "/v2/api-docs", "/v2/feign-docs", // "/swagger-resources/configuration/ui", // "/swagger-resources", "/swagger-resources/configuration/security", // "/swagger-ui.html", "/webjars/**").permitAll() // .antMatchers("/api/v1/user/login","/api/v1/user/getImageCode").permitAll() // .anyRequest().authenticated() // .and() // .exceptionHandling() // // 匿名处理 // .authenticationEntryPoint(loginAuthenticationHandler) // // 无权限处理 // .accessDeniedHandler(loginAccessDefineHandler) // // 跨域配置 // .and() // .cors() // .configurationSource(configurationSource()); // // 设置iframe // http.headers().frameOptions().sameOrigin(); // http.headers().frameOptions().disable(); // // } /** * 跨域配置 */ CorsConfigurationSource configurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedHeaders(Collections.singletonList("*")); corsConfiguration.setAllowedMethods(Collections.singletonList("*")); corsConfiguration.setAllowedOrigins(Collections.singletonList("*")); corsConfiguration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return source; } }
hrfan:
login:
url: "/api/v1/user/login"
![image-20240218001551442](https://img-
blog.csdnimg.cn/img_convert/79582b602b03e075dae86673603a54dc.png)
![image-20240218001649070](https://img-
blog.csdnimg.cn/img_convert/4ecf08d88e7f9cc0b32a28cc4076f68d.png)
SpringSecurity为我们提供了基于注解的权限控制方案。
在启动类上加上
@EnableGlobalMethodSecurity(prePostEnabled = true)
@GetMapping("/jjwt")
@PreAuthorize("hasAuthority('user_list')")
public Map<String, String> jjwt(){
// 这里的user_list 就是我们权限中permission_code
throw new RuntimeException("测试无token访问!");
}
![image-20240218002547214](https://img-
blog.csdnimg.cn/img_convert/09e1acbeb07ab010a886bb58301f9148.png)
![image-20240218002712121](https://img-
blog.csdnimg.cn/img_convert/6bd7fccdf9a2d9b5629477ef4b6d05da.png)
![image-20240218002909117](https://img-
blog.csdnimg.cn/img_convert/fb574bc1b061126f684ad3b10d24d57b.png)
![image-20240218004426367](https://img-
blog.csdnimg.cn/img_convert/4248d293474c3e9a479da1b371221822.png)
![image-20240218004734952](https://img-
blog.csdnimg.cn/img_convert/e64c4f2ca748abfc018198217043838f.png)
![image-20240218004839439](https://img-
blog.csdnimg.cn/img_convert/624e28a936053ec1092eb2e628ae9496.png)
![image-20240218091500059](https://img-
blog.csdnimg.cn/img_convert/47f49ff1f39aec8f476e34f43e314c0c.png)
从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,大家如果有不懂的地方可以找我咨询,我保证知无不言言无不尽,需要相关资料也可以找我要,我的网盘里一大堆资料都在吃灰呢。
干货主要有:
①1000+CTF历届题库(主流和经典的应该都有了)
②CTF技术文档(最全中文版)
③项目源码(四五十个有趣且经典的练手项目及源码)
④ CTF大赛、web安全、渗透测试方面的视频(适合小白学习)
⑤ 网络安全学习路线图(告别不入流的学习)
⑥ CTF/渗透测试工具镜像文件大全
⑦ 2023密码学/隐身术/PWN技术手册大全
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。