SpringSecurity框架入门
简介
官网
Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。
👉🏻示例代码地址
主要功能:
1. 认证(Authentication)
- 用户身份验证:Spring Security 通过多种方式(如表单登录、HTTP Basic、OAuth2 等)验证用户身份,确保只有合法用户能够访问系统。
- 灵活的认证机制:支持自定义认证流程和提供者,便于与各种身份验证方案集成。
2. 授权(Authorization)
- 访问控制:在用户通过认证后,Spring Security 根据配置的权限规则(如角色、权限等)控制用户对资源的访问。
- 方法级安全:通过注解(例如
@PreAuthorize
、@Secured
等)可以在业务逻辑层面直接控制访问权限,实现细粒度的权限管理。
3. 防御常见攻击
- CSRF 攻击防护:内置跨站请求伪造(CSRF)防护机制,有效降低攻击风险。
- 会话管理:提供会话固定攻击防护、并发登录控制等功能,保障会话安全。
- 安全头设置:自动配置 HTTP 安全头(如 X-Frame-Options、X-XSS-Protection 等)来增强安全性。
4. 可扩展性与定制化
- 高度定制化:可以根据项目需求定制安全策略,从认证流程到授权规则均可自定义。
- 与 Spring 生态系统无缝集成:与 Spring Boot、Spring MVC 等其他 Spring 模块结合紧密,简化了安全配置和管理。
5. 支持多种安全协议
- OAuth2 与 OpenID Connect:内置对 OAuth2 和 OpenID Connect 的支持,便于构建基于第三方身份验证的应用程序。
- LDAP 集成:支持 LDAP 作为用户信息和权限的存储方案,方便与企业级认证系统集成。
快速入门
在原有SpringBoot项目中引入依赖即可
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
再次访问时,会出现登录窗口

用户名为user,登录密码会打印在控制台
登录后就可以访问原来接口的数据。
认证
登录校验流程
原理
Spring Security 主要通过 一系列的过滤器(Filter) 进行请求的拦截、认证和授权。
常见的过滤器包括:
过滤器名称 | 作用 |
---|---|
SecurityContextPersistenceFilter | 读取、存储 SecurityContext 认证信息 |
UsernamePasswordAuthenticationFilter | 处理表单登录请求(用户名密码认证) |
BasicAuthenticationFilter | 处理 Basic 认证 |
BearerTokenAuthenticationFilter | 解析 JWT Token 认证 |
ExceptionTranslationFilter | 处理认证或授权失败的异常 |
FilterSecurityInterceptor | 进行授权(权限判断) |
⚠ 过滤器链的执行顺序决定了 Spring Security 处理请求的方式。
认证流程
名称 | 类型 | 作用 |
---|---|---|
UsernamePasswordAuthenticationFilter | 过滤器 | 拦截登录请求(通常是 /login ),从 HTTP 请求中提取用户名和密码,封装成一个待认证的 Authentication 对象,并触发后续认证流程。 |
ProviderManager | 认证管理器 | 实现了 AuthenticationManager ,内部维护一组 AuthenticationProvider ,负责将认证请求委派给能处理该类型令牌的 Provider。 |
DaoAuthenticationProvider | 认证提供者 | 实现了 AuthenticationProvider ,调用 UserDetailsService 加载用户、校验密码,并在认证成功后构造包含用户权限的 Authentication 。 |
InMemoryUserDetailsManager | 用户服务 | 实现了 UserDetailsService ,负责根据用户名从内存(或数据库)中查询用户信息,并返回一个包含用户名、密码、权限等的 UserDetails |
时序步骤详解
-
提交用户名和密码
- Client → UsernamePasswordAuthenticationFilter
用户在登录页面输入用户名/密码,浏览器发起 POST/login
请求,UsernamePasswordAuthenticationFilter
拦截该请求。
- Client → UsernamePasswordAuthenticationFilter
-
封装 Authentication 对象
- UsernamePasswordAuthenticationFilter
- 从请求中解析出用户名和密码
- 封装成一个
UsernamePasswordAuthenticationToken
(此时isAuthenticated()==false
,且内部只有用户名和密码,没有权限信息)
- UsernamePasswordAuthenticationFilter
-
调用认证管理器
- UsernamePasswordAuthenticationFilter → ProviderManager.authenticate(…)
将上一步的UsernamePasswordAuthenticationToken
传给ProviderManager
(即AuthenticationManager
)进行认证。
- UsernamePasswordAuthenticationFilter → ProviderManager.authenticate(…)
-
委派给 DaoAuthenticationProvider
- ProviderManager → DaoAuthenticationProvider.authenticate(…)
ProviderManager
遍历其持有的所有AuthenticationProvider
,找到支持UsernamePasswordAuthenticationToken
的DaoAuthenticationProvider
,并调用它的authenticate()
方法。
- ProviderManager → DaoAuthenticationProvider.authenticate(…)
-
加载用户信息
-
DaoAuthenticationProvider → InMemoryUserDetailsManager.loadUserByUsername(username)
DaoAuthenticationProvider
调用UserDetailsService.loadUserByUsername
,查询对应的用户记录及其权限信息。- InMemoryUserDetailsManager(或其他实现)从内存/数据库中查到用户实体和该用户的角色/权限
- 将查询到的信息封装到一个
UserDetails
(此处是LoginUser
或 Spring 提供的User
)对象中返回
-
-
返回 UserDetails 对象
- InMemoryUserDetailsManager → DaoAuthenticationProvider
返回包含用户名、加密密码、权限列表等的UserDetails
。
- InMemoryUserDetailsManager → DaoAuthenticationProvider
-
密码校验
- DaoAuthenticationProvider
- 使用注入的
PasswordEncoder
(如BCryptPasswordEncoder
)将用户提交的密码加密后,与UserDetails.getPassword()
(数据库中已加密的密码)进行比对。 - 如果不匹配,则抛出
BadCredentialsException
,认证失败。
- 使用注入的
- DaoAuthenticationProvider
-
构造已认证的 Authentication
- DaoAuthenticationProvider
- 如果密码校验通过,就基于原来的
UsernamePasswordAuthenticationToken
,将其principal
(UserDetails
)和authorities
(权限列表)填充进去,并将authenticated
标志设为true
,形成一个完整的已认证令牌。
- 如果密码校验通过,就基于原来的
- DaoAuthenticationProvider
-
返回已认证的 Authentication
- DaoAuthenticationProvider → ProviderManager → UsernamePasswordAuthenticationFilter
最终将这个已认证的Authentication
对象一路返回给最初的过滤器。
- DaoAuthenticationProvider → ProviderManager → UsernamePasswordAuthenticationFilter
-
保存到 SecurityContextHolder
- UsernamePasswordAuthenticationFilter
- 调用
SecurityContextHolder.getContext().setAuthentication(authentication)
,将认证结果存入当前线程的安全上下文中,后续同一请求的其他过滤器或业务代码都能通过SecurityContextHolder
获取到当前用户信息。 - 随后通常会跳转到登录成功页面或返回 JWT Token(前后端分离时)
- 调用
- UsernamePasswordAuthenticationFilter
形象解释
想象你要进入一家高级俱乐部,整个 Spring Security 认证流程就像你从门外走到俱乐部大堂,再到最终拿到 VIP 通行证的过程:
- 走到门口——UsernamePasswordAuthenticationFilter(门卫)
你来到俱乐部门口,门卫(过滤器)会先问:“请出示你的邀请函(用户名/密码)。”
- 如果你连邀请函都没带,他会拦下你,直接说“请先登录”。
- 如果你递上了邀请函,他就把它交给保安队长去进一步核实。
- 找保安队长——ProviderManager(保安队长)
门卫把邀请函交给保安队长,队长说:“好,我这里有好几位专门负责不同类型邀请函的保安(AuthenticationProvider),我来决定把你交给谁检查。”
- 队长看了下这是常规的“用户名+密码”邀请函,就交给负责这类的保安(
DaoAuthenticationProvider
)。
- 验证身份——DaoAuthenticationProvider(专职核查保安)
这位保安会带你去后台档案室(调用 UserDetailsService
)找你的“会员档案”:
- 去档案室查询:这就像他打开了会员数据库,找到了你的档案(
UserDetails
)。 - 核对签名:保安拿着你手上的签名(密码),用他们的密钥(
PasswordEncoder
)进行比对。
- 比对不符:保安会立刻说“这签名不对,你不是会员”,认证失败。
- 比对通过:保安给你盖章,给你的邀请函加上“已认证”标记,告诉队长“他是真会员”。
- 盖上“已认证”印章——返回 Authentication
保安把“已盖章的邀请函”(已认证的 Authentication
对象,里面写着你的会员等级、特权列表)交还给保安队长,队长再转交给门卫。
- 登记通行证——SecurityContextHolder(签到簿)
门卫把你的“已认证邀请函”放进签到簿(SecurityContext
),这样整个俱乐部其他区域的工作人员(后续的过滤器或业务逻辑)都能查到你的身份和权限。
- 发放 VIP 通行证——生成 JWT & 缓存
同时,俱乐部后台给你制作了一张带有你身份信息和到期时间的 VIP 通行证(JWT),并在门卫办公室(Redis)存了一份你的档案副本:
- JWT:就像一张加密的电子通行证,你离开后还可以凭它再次进入。
- Redis 缓存:就像门卫办公室里存了一份你的会员资料复印件,加快下次验证速度。
- 你拿着通行证入内
拿到通行证后,你就可以自由进出俱乐部的各个受保护区域(受保护的 API)。每次进门,门卫只要扫描你的通行证(解析 JWT),确认签名没问题,再从办公室(Redis)快速取出你的会员档案,就知道你有哪几种特权。
- 注销离场——logout(销毁缓存)
当你要离开时,门卫把你在办公室的会员档案复印件(Redis 缓存)销毁。这样即使有人捡到你的旧通行证,门卫扫描后也查不到你的档案,就会被拒绝入内。
具体实现
流程:前端→封装令牌→AuthenticationManager
→UserDetailsService
+PasswordEncoder
→认证通过→生成 JWT→Redis 缓存→返回 Token
创建一个类实现UserDetailsService接口,自定义加载逻辑
- 默认情况下,Spring Security 并不知道你的用户数据存在哪里,也不知道你用的是哪张表、哪种 ORM。
- 通过自己实现
loadUserByUsername
,你可以:- 从数据库(MyBatis、JPA、JDBC)、缓存(Redis)、外部系统(LDAP、微服务)中查询用户;
- 查询用户的角色和权限,把它们封装到返回的
UserDetails
(这里是LoginUser
)里; - 对不存在的用户抛出异常,让 Spring Security 知道该如何反馈“用户名不存在”或“密码错误”的信息。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询用户信息LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUserName,username);User user = userMapper.selectOne(wrapper);//如果查询不到数据就通过抛出异常来给出提示if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}//TODO 根据用户查询权限信息 添加到LoginUser中//封装成UserDetails对象返回 return new LoginUser(user);}
}
因为UserDetailsService方法的返回值是UserDetails类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
密码加密存储
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类
1.基于 WebSecurityConfigurerAdapter
(5.6 及更早版本)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过session获取SecurityContext.sessionManagement().disable().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//把token校验过滤器添加到过滤器链中//addFilterBefore表示在某某之前添加//这里表示我们的jwtAuthenticationTokenFilter过滤器在UsernamePasswordAuthenticationFilter之前执行http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}//在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,//所以需要在SecurityConfig中配置把AuthenticationManager注入容器。@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}
2.Spring Security 5.7+ 推荐的 无侵入式 Bean 配置(不再继承 WebSecurityConfigurerAdapter
)
@Configuration
@EnableWebSecurity
public class SecurityConfig {// 1. 密码加密器@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 2. 核心安全过滤链@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http,JwtAuthenticationFilter jwtFilter) throws Exception {http.csrf().disable() // 关闭 CSRF.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话.and().authorizeHttpRequests(authz -> authz.antMatchers("/user/login").anonymous() // 登录接口允许匿名.anyRequest().authenticated() // 其他都要认证)// 在用户名/密码过滤器之前,先走 JWT 认证.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}// 3. 如果你有自定义的 UserDetailsService,也可以在这里注册// @Bean// public UserDetailsService userDetailsService(...) { ... }// 4. 如果你还需要暴露 AuthenticationManager(比如在 Controller 里手动调用)@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}
}
定义登录接口
@RestController
public class LoginController {@Autowiredprivate LoginServcie loginServcie;@PostMapping("/user/login")public ResponseResult login(@RequestBody User user){return loginServcie.login(user);}
}
public class LoginServiceImpl implements LoginService {@AutowiredAuthenticationManager authenticationManager;@AutowiredRedisCache redisCache;@Overridepublic ResponseResult login(User user){UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);if(Objects.isNull(authenticate)){throw new RuntimeException("用户名或密码错误");}//使用userid 生成tokenLoginUser loginUser =(LoginUser)authenticate.getPrincipal();String userId = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userId);//authenticate存入redisredisCache.setCacheObject("login+"+userId,loginUser);//把token响应给前端HashMap<String,String> map = new HashMap<>();map.put("token",jwt);return new ResponseResult(200,"登录成功",map);}@Overridepublic ResponseResult logout(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser)authentication.getPrincipal();Long userId = loginUser.getUser().getId();redisCache.deleteObject("login:"+userId);return new ResponseResult(200,"注销成功");}
}
这段代码是 Spring Security + JWT 进行登录认证的 LoginServiceImpl
实现。它的主要作用是:
- 验证用户名和密码,通过
AuthenticationManager
进行认证。 - 生成 JWT Token,用于后续请求的身份认证。
- 将用户信息存入 Redis,方便后续的 Token 解析。
- 返回 JWT Token 给前端,前端在后续请求时携带 Token 进行身份认证。
🔍 详细解析代码
public class LoginServiceImpl implements LoginService {
这里 LoginServiceImpl
实现 了 LoginService
接口,表示它是一个用户登录的业务逻辑类。
1️⃣ 注入依赖
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisCache redisCache;
authenticationManager
:Spring Security 提供的认证管理器,用来校验用户名和密码是否正确。redisCache
:用于将用户信息存入 Redis,以便后续使用。
2️⃣ 处理用户登录
@Override
public ResponseResult login(User user){
这个方法的作用是:
用户登录 -> 验证身份 -> 生成 Token -> 存入 Redis -> 返回 Token
📌 2.1 构造 AuthenticationToken
进行认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
UsernamePasswordAuthenticationToken
是 Spring Security 内置的 用户名+密码认证 令牌。- 这个对象封装了用户提交的用户名和密码,后续交给
AuthenticationManager
进行认证。
📌 2.2 进行身份认证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
authenticationManager.authenticate(authenticationToken)
负责调用 Spring Security 的认证流程:- 通过
UserDetailsService
查询用户信息(通常是数据库查询)。 - 使用
PasswordEncoder
校验密码是否正确。 - 如果认证成功,返回一个
Authentication
对象,其中包含了用户的权限信息。
- 通过
- 如果
authenticate == null
,说明用户名或密码错误,抛出异常:
if(Objects.isNull(authenticate)){throw new RuntimeException("用户名或密码错误");
}
📌 2.3 生成 JWT Token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
authenticate.getPrincipal()
返回的是UserDetails
(我们这里的LoginUser
)。userId
取出用户 ID(因为 JWT 需要一个唯一标识)。JwtUtil.createJWT(userId)
生成 JWT Token,后续请求都会携带这个 Token 进行身份认证。
📌 2.4 存入 Redis
redisCache.setCacheObject("login+"+userId, loginUser);
- 把
LoginUser
(包括用户信息和权限)存入 Redis,键名是"login+userId"
。 - 后续用户请求时,系统会用 JWT 中的
userId
去 Redis 查找用户信息,避免每次都查询数据库。
📌 2.5 返回 Token
HashMap<String,String> map = new HashMap<>();
map.put("token", jwt);
return new ResponseResult(200, "登录成功", map);
- 创建
HashMap<String, String>
存放token
,返回给前端。 - 前端拿到 Token 后,后续请求会在
Authorization
头部携带这个 Token 进行身份认证。
🔎 代码执行流程
- 前端 发送
POST
请求到/user/login
,携带用户名和密码。 - Spring Security 认证
authenticationManager.authenticate()
调用UserDetailsService
查询用户信息。PasswordEncoder
进行密码校验。- 认证成功,返回
Authentication
对象。
- 生成 JWT Token,用于后续的请求身份认证。
- 用户信息存入 Redis,避免每次都查询数据库。
- 返回 Token 给前端,前端后续请求带上 Token。
📝 总结
步骤 | 说明 |
---|---|
1. 认证 | authenticationManager.authenticate(authenticationToken) 进行用户认证 |
2. 生成 JWT | JwtUtil.createJWT(userId) 生成 Token |
3. 存入 Redis | redisCache.setCacheObject("login+"+userId, loginUser) 缓存用户信息 |
4. 返回 Token | 返回 JWT 给前端,前端存储并在请求中携带 |
这段代码的目的是 用 JWT 替代 Session 进行身份认证,并结合 Redis 进行缓存,提升系统性能。🚀
认证过滤器
我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid。
使用userid去redis中获取对应的LoginUser对象。
然后封装Authentication对象存入SecurityContextHolder
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {// 1. 从请求头获取 tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {// 如果没有携带 token,直接放行(让后续的过滤器或控制器决定是否需要登录)filterChain.doFilter(request, response);return;}// 2. 解析 token,提取 userIdString userId;try {Claims claims = JwtUtil.parseJWT(token);userId = claims.getSubject(); // JWT 的 subject 字段里存放的是用户 ID} catch (Exception e) {// 解析失败(过期、签名不匹配等),抛出异常或返回 401e.printStackTrace();throw new RuntimeException("token 非法或已过期");}// 3. 从 Redis 中加载用户信息String redisKey = "login:" + userId;LoginUser loginUser = redisCache.getCacheObject(redisKey);if (Objects.isNull(loginUser)) {// 如果缓存中没有用户信息,说明用户未登录或已注销throw new RuntimeException("用户未登录");}// 4. 构造 Authentication 并存入 SecurityContext// TODO: 可以从 loginUser 中获取权限列表,填充到第三个参数 authoritiesUsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, // principal:当前登录用户详情null, // credentials:因为已通过 JWT 认证,不需要密码null // authorities:用户权限列表,后续可补充);SecurityContextHolder.getContext().setAuthentication(authenticationToken);// 5. 继续执行后续过滤器链filterChain.doFilter(request, response);}
}
注销
我们只需要定义一个注销接口,然后获取SecurityContextHolder中的认证信息,删除redis中对应的数据即可
@Overridepublic ResponseResult logout() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Long userid = loginUser.getUser().getId();redisCache.deleteObject("login:"+userid);return new ResponseResult(200,"退出成功");}
授权
对已经认证(登录)成功的用户,判断他是否有权限访问某些资源或执行某些操作,授权发生在认证之后。Spring Security 会根据用户的权限(Authorities
)和访问的资源,决定是否放行。
🧠 一句话理解授权流程:
用户携带 Token 请求接口 → JWTFilter 提取用户权限 → Security 判断用户是否有权访问 URL(或方法)
🔐 Spring Security 授权的三种常见方式:
✅ 1. 基于 URL 的授权(控制接口访问权限)
配置在 SecurityFilterChain
里的:
http.authorizeHttpRequests().antMatchers("/admin/**").hasRole("ADMIN") // 访问 /admin 开头接口必须有 ADMIN 角色.antMatchers("/user/**").hasAnyAuthority("user:add", "user:update") // 具备任一权限可访问.anyRequest().authenticated(); // 其他请求都要登录认证
常用方法:
方法 | 说明 |
---|---|
.hasAuthority("权限名") | 拥有某个具体权限 |
.hasAnyAuthority(...) | 拥有任意一个权限即可 |
.hasRole("角色名") | 拥有某个角色(底层其实是加前缀:ROLE_ ) |
.hasAnyRole(...) | 拥有任意一个角色 |
.authenticated() | 已登录用户可访问 |
.permitAll() | 所有人都可以访问 |
.anonymous() | 匿名用户才能访问(未登录) |
✅ 2. 基于方法的授权(精细控制某个接口或业务方法)
使用注解,在 Controller 或 Service 方法上使用:
启用方法级安全:
@EnableMethodSecurity // Spring Security 6+ 推荐的(老版本用 @EnableGlobalMethodSecurity)
然后用注解控制权限:
@PreAuthorize("hasAuthority('user:add')")
@GetMapping("/user/add")
public String addUser() {return "添加用户";
}
常用表达式:
表达式 | 说明 |
---|---|
hasAuthority('xxx') | 拥有指定权限 |
hasRole('ADMIN') | 拥有指定角色(会自动加 ROLE_ 前缀) |
hasAnyAuthority('a','b') | 拥有任一权限 |
#id == authentication.principal.id | 当前用户只能操作自己的数据(比如只改自己的资料) |
✅ 3. 自定义权限校验逻辑
如果你想写一个更加灵活的权限判断逻辑,可以自定义权限校验组件:
@Component("myAuth")
public class MyAuthorizationService {public boolean checkPermission(Authentication auth, String permission) {// 判断 auth 中的权限列表是否包含 permissionreturn auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(permission));}
}
然后这样用:
@PreAuthorize("@myAuth.checkPermission(authentication, 'user:delete')")
具体实现
限制访问资源所需权限
@RestController
public class HelloController {@RequestMapping("/hello")@PreAuthorize("hasAuthority('test')")public String hello(){return "hello";}
}
封装权限信息
封装权限信息到LoginUser
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;//存储权限信息private List<String> permissions;public LoginUser(User user,List<String> permissions) {this.user = user;this.permissions = permissions;}//存储SpringSecurity所需要的权限信息的集合@JSONField(serialize = false)private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}//把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
在UserDetailsServiceImpl中去把权限信息封装到LoginUser中了
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUserName,username);User user = userMapper.selectOne(wrapper);if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}// 根据用户查询权限信息 添加到LoginUser中List<String> list = new ArrayList<>(Arrays.asList("test"));return new LoginUser(user,list);}
}
去JwtAuthenticationTokenFilter类中把用户中的authorities权限封装到UsernamePasswordAuthenticationToken中,然后交给SecurityContextHolder。
// 存入SecurityContextHolder
// 获取权限信息封装到 setAuthentication 中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
从数据库查询权限信息
RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。


/*** 菜单表(Menu)实体类** @author makejava* @since 2021-11-24 15:30:08*/
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {private static final long serialVersionUID = -54979041104113736L;@TableIdprivate Long id;/*** 菜单名*/private String menuName;/*** 路由地址*/private String path;/*** 组件路径*/private String component;/*** 菜单状态(0显示 1隐藏)*/private String visible;/*** 菜单状态(0正常 1停用)*/private String status;/*** 权限标识*/private String perms;/*** 菜单图标*/private String icon;private Long createBy;private Date createTime;private Long updateBy;private Date updateTime;/*** 是否删除(0未删除 1已删除)*/private Integer delFlag;/*** 备注*/private String remark;
}
create table sys_menu
(id bigint auto_incrementprimary key,menu_name varchar(64) default 'NULL' not null comment '菜单名',path varchar(200) null comment '路由地址',component varchar(255) null comment '组件路径',visible char default '0' null comment '菜单状态(0显示 1隐藏)',status char default '0' null comment '菜单状态(0正常 1停用)',perms varchar(100) null comment '权限标识',icon varchar(100) default '#' null comment '菜单图标',create_by bigint null,create_time datetime null,update_by bigint null,update_time datetime null,del_flag int default 0 null comment '是否删除(0未删除 1已删除)',remark varchar(500) null comment '备注'
)comment '菜单表';
接口具体实现
mapper层实现
public interface MenuMapper extends BaseMapper<Menu> {List<String> selectPermsByUserId(Long id);
}
<?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.example.mapper.MenuMapper"><select id="selectPermsByUserId" resultType="java.lang.String">SELECTDISTINCT m.`perms`FROMsys_user_role urLEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`WHEREuser_id = #{userid}AND r.`status` = 0AND m.`status` = 0</select>
</mapper>
完善UserServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate MenuMapper menuMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUserName,username);User user = userMapper.selectOne(wrapper);if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}List<String> permissionKeyList = menuMapper.selectPermsByUserId(user.getId());return new LoginUser(user,permissionKeyList);}
}
自定义失败处理
异常拦截点:ExceptionTranslationFilter
- 职责:在过滤器链中专门捕获认证或授权过程中抛出的异常。
- 工作流程:
- 当认证(Authentication)或授权(Authorization)失败时,相关的过滤器(如
UsernamePasswordAuthenticationFilter
、FilterSecurityInterceptor
)会抛出异常。 ExceptionTranslationFilter
捕获这些异常,判断是认证失败还是授权失败。- 根据异常类型分别交给
AuthenticationEntryPoint
或AccessDeniedHandler
去处理。
- 当认证(Authentication)或授权(Authorization)失败时,相关的过滤器(如
认证失败 vs 授权失败
异常类型 | 场景 | 默认处理 |
---|---|---|
AuthenticationException | 用户未登录、登录凭证(用户名/密码、Token)不合法 | 调用 AuthenticationEntryPoint.commence() ,默认重定向到登录页面或返回 401 |
AccessDeniedException | 用户已登录,但没有访问某个资源的权限(角色/权限不足) | 调用 AccessDeniedHandler.handle() ,默认返回 403 页面或错误响应 |
自定义处理器:
自定义 AuthenticationEntryPoint
当捕获到 AuthenticationException
时会走这里,通常用于“未登录”或“Token 无效”场景。
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException)throws IOException, ServletException {// 构造统一的 JSON 响应体ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败,请重新登录");String json = JSON.toJSONString(result);// 直接将 JSON 写回响应WebUtils.renderString(response, json);}
}
commence()
方法:response
:直接往 HTTP 响应里写状态码和 JSON,前端可以根据结构化数据统一处理。ResponseResult
:你的统一响应对象,包含code
、msg
、data
等字段。WebUtils.renderString()
:工具方法,设置响应类型为application/json
并写入字符串。
自定义 AccessDeniedHandler
当捕获到 AccessDeniedException
时会走这里,通常用于“权限不足”场景。
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException)throws IOException, ServletException {ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");String json = JSON.toJSONString(result);WebUtils.renderString(response, json);}
}
handle()
方法:- 同样构造统一的 JSON 响应并写回,状态码使用 403(Forbidden)。
将自定义处理器注入到 Spring Security
在你的 HttpSecurity
配置中,调用 exceptionHandling()
方法将它们注册进去:
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate AuthenticationEntryPointImpl authenticationEntryPoint;@Autowiredprivate AccessDeniedHandlerImpl accessDeniedHandler;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// ... 其他配置 ....exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) // 认证失败处理.accessDeniedHandler(accessDeniedHandler) // 授权失败处理// ... 继续你的过滤链配置 ...;return http.build();}
}
authenticationEntryPoint(...)
:指定当出现AuthenticationException
时调用哪个 Bean。accessDeniedHandler(...)
:指定当出现AccessDeniedException
时调用哪个 Bean。
跨域
什么是跨域(CORS)?
- 同源策略:浏览器出于安全考虑,只有当 协议、域名、端口 三者都相同时,才能正常发起 AJAX 请求。
- 跨域场景:前后端分离时,前端通常跑在
http://localhost:3000
,后端在http://localhost:8080
,端口不同,就属于跨域。 - CORS(Cross‑Origin Resource Sharing)机制允许服务器声明:哪些域名、哪些方法、哪些请求头可以访问它的资源。
Spring MVC 层面允许跨域
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // ① 对所有接口都允许跨域.allowedOriginPatterns("*") // ② 允许任意域名发起跨域请求(也可以写具体域名列表).allowCredentials(true) // ③ 允许携带 Cookie(如果前端要发送或接收 cookie,必须开启).allowedMethods("GET","POST","DELETE","PUT") // ④ 允许的方法.allowedHeaders("*") // ⑤ 允许的请求头.maxAge(3600); // ⑥ 预检请求的缓存时间(秒),在这段时间内浏览器不再发第二次预检}
}
- 为什么要 MVC 配置?
Spring MVC 默认会注册一个CorsFilter
,它根据上面的规则响应浏览器的 预检请求(OPTIONS
),并在实际请求中加上相应的 CORS 响应头。
Spring Security 层面允许跨域
即使你在 MVC 层面配置了 CORS,Spring Security 默认也会拦截所有请求(包括 OPTIONS
预检),导致预检被拒绝。
所以在你的安全配置里,还要显式开启 CORS 支持:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//配置异常处理器http.exceptionHandling()//配置认证失败处理器.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);//允许跨域http.cors();}
http.cors()
它会让 Spring Security 去查找 Spring MVC 中的CorsConfiguration
(即上面CorsConfig
定义的规则),并在 SecurityFilterChain 的最前面插入一个CorsFilter
。- 效果:
- 预检请求(OPTIONS) 先经过 CORS 过滤器,返回允许的跨域响应头,浏览器才会继续发真正的请求。
- 实际请求 也会带上
Access-Control-Allow-Origin
、Access-Control-Allow-Credentials
等头,浏览器才允许前端 JS 访问响应内容。
为什么两处都要配置?
- MVC 层:负责 定义 哪些路径、哪些域名、哪些方法可以跨域。
- Security 层:负责 允许 Spring Security 过滤链里也执行这套跨域规则,否则所有跨域请求(包括预检)都会被 Security 拦截成 401/403。
自定义处理器
认证成功处理器
实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果登录成功了是会调用AuthenticationSuccessHandler的方法进行认证成功后的处理的。AuthenticationSuccessHandler就是登录成功处理器。
我们也可以自己去自定义成功处理器进行成功后的相应处理。
@Component
public class SGSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("认证成功了");}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationSuccessHandler successHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().successHandler(successHandler);http.authorizeRequests().anyRequest().authenticated();}
}
认证失败处理器
实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果认证失败了是会调用AuthenticationFailureHandler的方法进行认证失败后的处理的。AuthenticationFailureHandler就是登录失败处理器。
我们也可以自己去自定义失败处理器进行失败后的相应处理。
@Component
public class SGFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {System.out.println("认证失败了");}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationSuccessHandler successHandler;@Autowiredprivate AuthenticationFailureHandler failureHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()
// 配置认证成功处理器.successHandler(successHandler)
// 配置认证失败处理器.failureHandler(failureHandler);http.authorizeRequests().anyRequest().authenticated();}
}
**⚠️注意:**这和之前的AuthenticationEntryPoint
两个类虽然都是处理 认证失败 的情况,但它们的作用场景是完全不同的
总结区别:
比较项 | AuthenticationEntryPoint | AuthenticationFailureHandler |
---|---|---|
所在阶段 | 用户访问受保护资源但未登录 | 用户登录时输入错误 |
出发原因 | 请求接口没带 token、token 无效 | 登录接口用户名/密码错误 |
响应方式 | 通常返回 401(未认证) | 通常返回 401(登录失败)或业务自定义状态码 |
注册位置 | http.exceptionHandling().authenticationEntryPoint(...) | 登录过滤器 UsernamePasswordAuthenticationFilter 的 setAuthenticationFailureHandler(...) 方法 |
登出成功处理器
@Component
public class SGLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("注销成功");}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationSuccessHandler successHandler;@Autowiredprivate AuthenticationFailureHandler failureHandler;@Autowiredprivate LogoutSuccessHandler logoutSuccessHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()
// 配置认证成功处理器.successHandler(successHandler)
// 配置认证失败处理器.failureHandler(failureHandler);http.logout()//配置注销成功处理器.logoutSuccessHandler(logoutSuccessHandler);http.authorizeRequests().anyRequest().authenticated();}
}
相关文章:
SpringSecurity框架入门
简介 官网 Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种…...
AIDD-人工智能药物设计-双扩散模型结合多目标优化策略助力3D小分子药物设计
Adv. Sci. | 双扩散模型结合多目标优化策略助力3D小分子药物设计 药物发现中,如何精准且高效地设计具有理想物理化学性质的潜在药物分子,对当前的研究水平来说仍然是一项重大挑战。近年来,基于深度学习的全新分子生成(de novo molecular generation)方法取得了显著进展,…...
Python面向对象编程 - 接口隔离原则(ISP)
1. 原则定义 接口隔离原则(Interface Segregation Principle, ISP) 是SOLID原则中的"I",核心思想是: 客户端不应该被迫依赖它们不使用的接口 即:多个特定功能的接口比一个通用接口更好 2. 核心思想 将臃肿的接口拆分为更小、更具…...
mac安装浏览器闪退处理
安装 Chrome或edge后打开浏览器出现闪退,是因为权限不够。 以下是针对edge的处理方法。 sudo chown -R $(whoami) ~/Library/Application\ Support/Microsoft\ Edge sudo chmod -R 755 ~/Library/Application\ Support/Microsoft\ Edge 原因分析: 在…...
408 计算机网络 知识点记忆(5)
前言 本文基于王道考研课程与湖科大计算机网络课程教学内容,系统梳理核心知识记忆点和框架,既为个人复习沉淀思考,亦希望能与同行者互助共进。(PS:后续将持续迭代优化细节) 往期内容 408 计算机网络 知识…...
Java面试黄金宝典38
1. TIME_WAIT 和 CLOSE_WAIT 的区别 定义 TIME_WAIT:是主动发起关闭连接操作的一方,在发送最后一个 ACK 确认包之后进入的状态。此状态存在的意义在于确保对端能收到最后一个 ACK 包,同时让网络中可能残留的旧数据包自然消逝,防止其干扰后续相同四元组(源 IP、源端口、目…...
【算法】筛质数
目录 埃氏筛法算法原理代码 欧拉筛法算法原理代码 埃氏筛法 算法原理 算法思想就像"筛子"一样,把合数筛掉,剩下的就是质数: 从2开始,依次检查每个数如果当前数未被标记为合数,它就是质数然后把这个质数的…...
【IDEA】✈️自定义模板,自动生成类和方法注释
💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥 🏆本篇文章阅读大约耗时三分钟。 ⛳️motto:不积跬步、无以千里 📋📋📋本文目录如下:🎁🎁&a…...
笔试专题(六)
文章目录 最长无重复子数组(滑动窗口)题解代码 重排字符串(贪心 构造)题解代码 牛牛冲钻五(模拟)题解代码 最长无重复子数组(滑动窗口) 题目链接 题解 1. 滑动窗口 2. 什么时候…...
【算法实践】跳跃游戏——计算到达终点的最小跳数
问题描述 给定一个非负数数组 arr[],每个元素表示从该位置最多可向前跳跃的步数。 示例: 若 arr[i] 3,则可以从位置 i 跳跃到 i1、i2 或 i3。若 arr[i] 0,则无法从该位置向前跳跃。 任务:找到从数组第一个位置移动…...
sklearn的Pipeline
Pipeline类 介绍:Pipeline 可以将多个数据处理步骤和机器学习模型组合成一个序列,其中每个步骤都是一个变换器(Transformer)或者估计器(Estimator),并且Pipeline中的最后一个必须为估计器,其它的必须为变换器,如果Pipeline中的估计器为为分类器则整个Pipeline就作为分…...
Unity3D仿星露谷物语开发34之单击Drop项目
1、目标 当在道具栏中选中一个Item时,点击地面就可以实现Item的drop操作,每点击一次就drop一次,直到道具栏中Item数量不够。 这样的好处:避免每次Drop都从道具栏中拖拉Item,通过点击这种操作可以更加高效。 方法&am…...
每日一题(小白)回溯篇4
深度优先搜索题:找到最长的路径,计算这样的路径有多少条(使用回溯) 分析题意可以得知,每次向前后左右走一步,直至走完16步就算一条走通路径。要求条件是不能超出4*4的范围,不能重复之前的路径。…...
Day16——路由2
路由独有的两个生命周期钩子 作用:用于捕获路由组件的激活状态 具体名字: activated 路由组件被激活时触发 deactivated 路由组件失活时触发 现在在缓存路由组件的基础上,想要使h2中的文字透明度不断变化,在切换到别的组件时销毁控制文字不…...
iproute2 工具集使用详解
目录 一、iproute2 核心命令:ip二、常用功能详解1. 管理网络接口(link 对象)2. 管理 IP 地址(address 对象)3. 管理路由表(route 对象)4. 管理 ARP 和邻居缓存(neigh 对象࿰…...
C++学习之套接字并发服务器
目录 1.昨天套接字服务器的弊端 2.如何通过多进程方式实现服务器并发 3.多进程服务器-1 4.多进程服务器-2 5.多进程版程序-回收子进程被信号中断的处理 6.多线程版TCP服务处理思路 7.多线程并发服务器编写 8.为什么不能把文件描述符地址传到子线程中 9.多线程程序测试 …...
MCP项目开发-一个简单的RAG示例
MCP项目开发-一个简单的RAG示例 前言 前言 客户端是基于官网的例子改的,模型改成了openai库连接仅仅使用基础的RAG流程作为一个演示,包含了以下步骤 query改写搜索:使用google serper重排序:使用硅基流动的api 大模型api也使用…...
Windows安装ssh服务
使用管理员权限打开Windows PowerShell Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0启动服务 Start-Service sshd 设置开机自启 Set-Service sshd -StartupType Automatic允许22端口 New-NetFirewallRule -Name “SSH” -DisplayName “SSH” -Enabled Tr…...
从零实现本地大模型RAG部署
1. RAG概念 RAG(Retrieval-Augmented Generation)即检索增强生成,是一种结合信息检索与大型语言模型(大模型)的技术。从外部知识库(如文档、数据库或网页)中实时检索相关信息,并将其…...
什么是ThreadLocal
ThreadLocal 是 Java 提供的一个工具类,它为每一个使用该变量的线程都提供了一个独立的变量副本。换句话说:每个线程都有自己的本地变量副本、互不干扰。它不是用来共享数据的,而是用来隔离数据的。 一、为什么需要 ThreadLocal?…...
MySQL-SQL-DQL语句、DQL基本查询、DQL条件查询、DQL分组查询、聚合函数、DQL排序查询、DQL分页查询
一. DQL DQL:Data Query Language(数据查询语言),用来查询数据库表中的记录。 关键字:SELETE -- DQL 完整语法select字段列表 from表名列表 where条件列表 group by分组字段列表 having分组后条件列表 order by排序字段列表 limit分页参数 …...
vue2 vue3 响应式差异
vue2 响应式原理看这 链接: link 总结: object.defineproperty()是对属性的劫持,对属性劫持有两大缺陷 1. 需要遍历对象的所有属性,深层属性需递归,存在效率问题 2. 后添加的属性,无法获得响应式,因为劫持…...
常见NLP模型发展脉络:从传统方法到大语言模型
自然语言处理作为人工智能领域的重要分支,经历了从传统统计方法到深度学习的巨大飞跃。本文将带你梳理NLP模型的发展脉络,回顾那些推动技术进步的重要里程碑。 一、统计学习阶段(1990s-2010s初) 早期的NLP模型主要基于统计方法&…...
Bert论文解析
文章目录 BERT:用于语言理解的深度双向转换器的预训练一、摘要三、BERT介绍BERT及其详细实现答疑:为什么没有标注的数据可以用来预训练模型?1. 掩码语言模型(Masked Language Model, MLM)2. 下一句预测(Nex…...
【数学】勒让德定理(legendres-formula)详解
勒让德定理(Legendre’s Formula)详解 这段代码使用的数学原理是勒让德定理,它是计算质数p在n!的质因数分解中指数的核心方法。 一、定理内容 对于任意质数p和正整数n,p在n!的质因数分解中的指数(即n!能被p整除的最…...
时空联合规划算法
本文主要讲解时空时空联合规划算法。 文章目录 前言一、时空联合规划基本概念1.1 EM Planner算法求解过程1.2 时空联合规划算法求解过程二、基于搜索的规划方法2.1 构建三维时空联合规划地图2.2 基于Hybrid A*的时空联合规划二、基于迭代搜索的规划方法2.1 这段时间更新中2.2 这…...
如何在idea中新建一个项目
Java通常展现的方式就是项目,但是在不熟悉idea的情况下,我们应该如何创建一个项目呢? 第一步:点击File-->New-->Project 第二步:选择 Empty Project 第三步:点击File-->找到Project Structure--&…...
设计模式简述(十三)适配器模式
适配器模式 描述基本使用使用关于适配器关联不兼容类的方式如果原有抽象层是抽象类若原有抽象是接口使用 描述 适配器模式常用于系统已经上限稳定运行,但现有需求需要将两个不匹配的类放到一起工作时使用。 也就是说这是一个迭代阶段使用的模式。 这种模式&#x…...
功耗日志抓取需求
最近罗列了一些功耗分析需要的常见日志: 测试功耗前: adb shell dumpsys batterystats --reset adb shell dumpsys batterystats --enable full-wake-history 测试功耗后,使用脚本导出如下功耗日志: 脚本 chmod x collect_logs.s…...
设计模式简述(十一)装饰器模式
装饰器模式 描述基本使用使用 描述 装饰器模式是一种功能型模式 用于动态增强对象的功能 这么一说感觉上和代理模式有些类似 抽象装饰器 要实现原有业务接口,并注入原有业务对象 至于对原有业务对象的调用,可以采用private业务对象 实现业务接口方法的…...
MongoDB基础知识
MongoDB基础知识 目录 基础篇 一、MongoDB入门指南(零基础必读)二、MongoDB简介三、MongoDB安装与配置四、MongoDB基本操作五、MongoDB查询操作 进阶篇 六、MongoDB索引七、MongoDB聚合操作八、MongoDB数据模型九、MongoDB安全十、MongoDB备份恢复十一…...
Kubernetes详细教程(一):入门、架构及基本概念
Kubernetes(常简称为K8s)是一个开源的平台,用于自动化部署、扩展和管理容器化应用程序。 官方文档:https://kubernetes.io/zh-cn/docs/concepts/overview/components/ 一、入门 (一)Kubernetes是什么&am…...
架构思维:限流技术深度解析
文章目录 Pre业务场景熔断 VS 限流4大限流算法固定时间窗口计数滑动时间窗口计数漏桶令牌桶 方案实现使用令牌桶还是漏桶模式?在 Nginx 中实现限流还是在网关层中实现限流?使用分布式限流还是单机限流?使用哪个开源技术? 限流方案…...
批量改CAD图层颜色——CAD c#二次开发
一个文件夹下大量图纸(几百甚至几千个文件)需要改图层颜色时,可采用插件实现,效果如下: 转换前: 转换后: 使用方式如下:netload加载此dll插件,输入xx运行。 附部分代码如…...
vue猜词游戏
说明:我希望用vue实现猜词游戏 Vue Wordle 游戏规则总结 核心规则 单词选择 目标单词从预设词库(DEFAULT_WORDS)中随机选取,均为5字母单词(如apple、zebra等)。 输入要求 长度限制:必须…...
SQL ②-库操作 | 数据类型
这里是Themberfue SQL语法 数据库术语 DATABASE:数据库,保存有组织的数据的容器(通常是一个文件或一组文件)。TABLE:表,某种特定类型数据的结构化清单。SCHEMA:模式,关于数据库和表…...
云轴科技ZStack CTO王为@中国GenAI大会:AI原生实践重构AI Infra新范式
4月1-2日,2025中国生成式AI大会(GenAICon 2025)在北京举办,该会议已成为国内AI领域最具影响力的产业峰会之一。来自学术界与产业界的50位嘉宾围绕GenAI应用、大模型、AI智能体、具身智能、DeepSeek R1与推理模型等话题,…...
处理甘特图启动依赖报错。
处理甘特图启动报错 一、修改甘特图下载地址1.1 配置修改1.2 修改地址(https://registry.npmmirror.com) 二、安装依赖1.1 安装sass-loader1.2 适配安装dhtmlx-gantt 一、修改甘特图下载地址 1.1 配置修改 npm config get registry1.2 修改地址(https://registry.npmmirror.c…...
JSX、支持HTML标签、Ref的使用、虚拟DOM的使用
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
leetcode376-摆动序列
leetcode 376 思路 变量定义: prediff:记录上一次相邻元素的差值。用于判断当前差值与上一个差值的关系curdiff:记录当前相邻元素的差值result:记录当前的摆动序列的长度,初始化为 1,因为至少一个元素就…...
内网渗透(杂项集合) --- 中的多协议与漏洞利用技术(杂项知识点 重点) 持续更新
目录 1. NetBIOS 名称的网络协议在局域网中内网渗透中起到什么作用 2. 使用 UDP 端口耗尽技术强制所有 DNS 查找失败,这个技术如何应用在局域网内网渗透测试中 3. 在本地创建一个 HTTP 服务来伪造 WPAD 服务器 什么是 WPAD 服务器?这个服务器是干嘛的…...
14-产品经理-维护计划
产品经理的另一个职责是制定计划。古人云,凡事预则立,不预则废。 产品需要做规划,才能有轻重缓急,才能正确的做事。因此对于产品经理而言,计划是必需的。 对于产品经理自己而言,发布计划可以帮助他规划产…...
12-产品经理-维护模块
需求模块是帮助产品经理进行需求的分类和维护。 1. 维护模块 在具体产品的“研发需求”页面左侧,点击“维护模块”。也可以在具体产品的“设置”-“模块”下进行维护。 点击保存后,返回模块页面。还可以点击“子模块”对已有模块进行子模块的维护。 点击…...
解析HiveQL的ALTER TABLE ADD/REPLACE COLUMNS语句
阅读以下ALTER TABLE的ADD/REPLACE COLUMNS语句的语法,用C#编写解析函数,一个一个字符解析,所有关键字不区分大小写,一个或多个空格、Tab和换行的组合都可以是关键词之间的分隔,表名和字段名可能包含空格和Tab…...
MySQL-SQL-DML语句、INSER添加数据、UPDATE更新数据、DELETE删除数据
一. DML 1. DML的英文全称是Data Manipulation Language(数据操作语言),用来对数据库中表的数据记录进行增、删、改操作。 2. 添加数据(INSERT);修改数据(UPDATE);删除数据(DELETE) 二. DML-INSER添加数据 -- DML insert -- 指定字段添加数…...
学透Spring Boot — 017. 处理静态文件
这是我的《学透Spring Boot》专栏的第17篇文章,了解更多内容请移步我的专栏: Postnull CSDN 学透 Spring Boot 目录 静态文件 静态文件的默认位置 通过配置文件配置路径 通过代码配置路径 静态文件的自动配置 总结 静态文件 以前的传统MVC的项目…...
Linux进程间通信——共享内存
1.概念 共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中&#x…...
如何在大型项目中组织和管理 Vue 3 Hooks?
众所周知,Vue Hooks(通常指 Composition API 中的功能)是 Vue 3 引入的一种代码组织方式,用于更灵活地组合和复用逻辑。但是在项目中大量使用这种写法该如何更好的搭建结构呢?以下是可供参考实践的案例。 一、Hooks 组织原则 单一职责每个 Hook 应专注于完成单一功能,避…...
前后端开发的未来趋势
随着技术的不断进步,前后端开发模式也在不断演变。未来,微服务架构、Serverless、前后端融合(GraphQL、BFF)等趋势将深刻影响开发方式,使应用更高效、灵活、可扩展。 1. 微服务架构与 Serverless 1.1 微服务架构(Microservices Architecture) 微服务是一种软件架构模式…...
产品经理课程
原型工具 一、土耳其机器人 这个说法来源于 1770 年出现的一个骗局,一个叫沃尔夫冈冯肯佩伦(Wolfgang von Kempelen)的人为了取悦奥地利女皇玛丽娅特蕾莎(Maria Theresia),“制造”了一个会下国际象棋的机…...