news 2026/10/19 7:55:03

Spring MVC + CAS + Shiro 单点登录实战:认证流与权限边界解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring MVC + CAS + Shiro 单点登录实战:认证流与权限边界解析

简介:本资源是一套基于CAS、SpringMVC与Shiro三者深度集成的单点登录(SSO)企业级实践项目,面向Java Web开发工程师及安全架构学习者,解决多系统间统一身份认证与细粒度权限管控难题,适用于政务、金融、教育等需合规SSO落地的中大型应用开发场景。压缩包共316个文件,含126个jar依赖库(支撑CAS客户端、Shiro核心及Spring生态)、44个JSP页面(登录跳转、票据验证响应等前端交互)、38个Java类(含自定义CasRealm、CasFilter及Shiro拦截器实现)、38个XML配置(SpringMVC与Shiro安全配置)、33个properties(环境与CAS服务端地址等参数化配置),整体40.79MB,结构完整、模块解耦清晰。已有492人学习下载,读者可直接部署运行,获取从CAS服务端对接、客户端票据解析、Shiro Realm定制、SpringMVC拦截器集成到登出联动的全链路可执行代码,并通过CSS/JS/GIF等静态资源理解UI层与SSO流程的协同设计。

1. CAS + Spring MVC + Shiro 单点登录:不是堆砌框架,而是理清认证流与权限边界的实战落地

你有没有遇到过这样的场景:系统里同时跑着 Spring MVC 写的老业务模块、Shiro 管理的后台管理页、还有第三方系统要求统一登录入口——结果用户在 A 模块登了,在 B 模块又要输一遍密码,后台日志里还频繁出现Subject.getPrincipal()返回 null、SecurityUtils.getSubject().isAuthenticated()偶尔为 false 的玄学问题?这不是配置没写全,而是没搞清 CAS 和 Shiro 在认证生命周期里的分工边界。这份资源不是“Spring Boot + CAS + Shiro 三件套一键启动包”,而是一套经某高校实验室多个模拟项目X 验证过的、可拆解、可调试、可断点跟踪的 Spring MVC(非 Boot)环境下的 CAS 单点登录集成方案。它明确回答三个关键问题:CAS 负责什么(仅身份断言)、Shiro 负责什么(会话管理 + 权限决策)、Spring MVC 又在哪一环做桥接(Filter 链顺序与 Subject 绑定时机)。适合正在维护传统 Spring MVC 架构、需接入企业级统一认证平台、又不愿放弃 Shiro 精细权限控制能力的中高级开发者。别急着改web.xml,先看清这张认证流图里哪一步是黑匣子。


2. 认证流程解耦:为什么 CAS 不该直接管权限,Shiro 也不该自己去调 CAS 接口

2.1 CAS 与 Shiro 的职责切分:从协议层到应用层的三层映射

CAS 是一个基于票据(Ticket)的中心化身份认证服务,其核心输出只有一个:当前请求是否来自合法 CAS Server 的可信断言(Assertion)。它不关心你是管理员还是普通用户,不存储你的角色列表,更不会返回ROLE_ADMIN这样的字符串。而 Shiro 是一个应用级权限框架,它的强项在于:基于Subject的会话生命周期管理、基于AuthorizationInfo的动态权限加载、以及@RequiresRoles这类声明式注解的拦截执行。二者强行耦合(比如让 Shiro Realm 直接调 CAS REST API 去查用户属性)会导致两个严重后果:一是 CAS Server 成为单点性能瓶颈;二是权限逻辑被拖进网络 I/O,一旦 CAS 响应慢,整个页面渲染就卡住。
所以真实项目中我们采用「CAS 负责身份断言 → Shiro 负责会话与权限」的流水线模式。具体映射关系如下:

协议层(CAS)应用层(Shiro)Spring MVC 层作用
ticket参数(GET 请求携带)由CasFilter拦截并验证,生成CasTokenCasFilter是org.jasig.cas.client.authentication.AttributePrincipal的消费者
Assertion.getPrincipal().getName()(唯一标识)作为CasToken.getPrincipal()传入 Shiro,触发Realm.doGetAuthenticationInfo()CasFilter将CasToken提交至SecurityManager.login()
CAS Attributes(如cn,mail,groups)在Realm中通过Assertion对象提取,封装进SimpleAuthenticationInfo的principals或credentials需重写CasFilter.createCasToken(),将HttpServletRequest中的Assertion注入 Token

提示:CasFilter并非 Shiro 原生 Filter,而是cas-client-core提供的独立 Filter,它必须在ShiroFilter之前执行,否则ShiroFilter根本收不到已验证的CasToken。

2.2 Spring MVC 环境下 Filter 链顺序的硬性约束

在web.xml中,Filter 执行顺序严格按声明顺序。以下是最小可行配置(删减版),注意CasFilter必须排在ShiroFilter之前,且ShiroFilter必须是最后一个安全相关 Filter:

<!-- 1. CAS 客户端 Filter:负责票据验证与 Assertion 解析 --> <filter> <filter-name>CasAuthenticationFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://cas.example.com/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8080</param-value> </init-param> </filter> <!-- 2. CAS Attribute 获取 Filter:可选,用于透传用户属性 --> <filter> <filter-name>CasValidationFilter</filter-name> <filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>https://cas.example.com/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8080</param-value> </init-param> </filter> <!-- 3. Shiro 主 Filter:必须放在 CasFilter 之后 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <!-- Filter 映射顺序必须与声明一致 --> <filter-mapping> <filter-name>CasAuthenticationFilter</filter-name> <url-pattern>/app/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CasValidationFilter</filter-name> <url-pattern>/app/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/app/*</url-pattern> </filter-mapping>

这段配置的关键逻辑在于:当用户首次访问/app/dashboard,CasAuthenticationFilter检测到无ticket,302 重定向至 CAS 登录页;登录成功后 CAS 重定向回/app/dashboard?ticket=ST-xxx,此时CasAuthenticationFilter拿到ticket向 CAS Server 验证,验证通过后将Assertion存入HttpServletRequest属性(默认 key 为CONST_CAS_ASSERTION);紧接着CasValidationFilter读取该Assertion,并将其解析出的用户属性(如memberOf)也存入 request;最后ShiroFilter启动,发现当前请求未认证(Subject.getPrincipal() == null),于是触发SecurityManager.login(new CasToken()),而这个CasToken的构造,正是我们下一步要重写的重点。

2.3 自定义 CasToken:把 CAS Assertion 里的用户属性真正喂给 Shiro Realm

Shiro 原生CasToken只携带ticket和rememberMe,完全忽略 CAS 返回的Assertion。我们必须继承它,注入Assertion实例,以便在Realm中使用。以下是CustomCasToken.java的核心实现:

// CustomCasToken.java public class CustomCasToken extends CasToken { private Assertion assertion; public CustomCasToken(String ticket, boolean rememberMe, String host, Assertion assertion) { super(ticket, rememberMe, host); this.assertion = assertion; } public Assertion getAssertion() { return assertion; } }

接着重写CasFilter.createCasToken()方法,从 request 中取出Assertion并构建自定义 Token:

// CustomCasFilter.java public class CustomCasFilter extends AuthenticationFilter { @Override protected CasToken createCasToken(String ticket, HttpServletRequest request, HttpServletResponse response) { // 从 request attribute 中获取 CAS Assertion(由 CasValidationFilter 设置) Assertion assertion = (Assertion) request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION); if (assertion == null) { throw new IllegalStateException("CAS Assertion not found in request attribute. " + "Make sure CasValidationFilter is configured before this filter."); } return new CustomCasToken( ticket, request.getRemoteUser() != null, // rememberMe logic request.getRemoteAddr(), assertion ); } }

最后在web.xml中替换 Filter 类:

<filter> <filter-name>CasAuthenticationFilter</filter-name> <filter-class>com.example.security.CustomCasFilter</filter-class> <!-- 其他 init-param 不变 --> </filter>

这一步做完,CustomCasToken就带着完整的Assertion进入 Shiro 认证流程。接下来,Realm就能基于Assertion.getPrincipal().getName()查库,同时用Assertion.getAttributes()加载角色与权限,彻底打通 CAS 与 Shiro 的数据链路。


3. Shiro Realm 实现:从 CAS Assertion 提取用户、角色、权限的完整闭环

3.1 Realm 的核心任务:不是重复验证,而是构建 AuthorizationInfo

很多开发者误以为Realm.doGetAuthenticationInfo()要再次调 CAS 接口验证 ticket —— 这是典型翻车点。CasFilter已完成票据验证,Realm此时只需做两件事:

  1. 认证(Authentication):用Assertion.getPrincipal().getName()(即 CAS 用户名)查本地用户表,确认该用户是否被允许接入本系统(例如检查is_enabled = 1);
  2. 授权(Authorization):用Assertion.getAttributes()中的groups、roles等字段,或结合本地数据库查询,组装SimpleAuthenticationInfo与SimpleAuthorizationInfo。

注意:doGetAuthenticationInfo()返回的AuthenticationInfo必须包含principals(用户标识)和credentials(凭证,此处可设为ByteSource.Util.bytes("CAS_AUTHENTICATED"),表示已由 CAS 认证),否则 Shiro 会认为认证失败。

3.2 基于 Assertion Attributes 的角色与权限加载策略

CAS Server 可配置返回用户属性(如 LDAP 同步的memberOf、eduPersonAffiliation)。假设 CAS 返回如下 Attributes:

{ "cn": ["张三"], "mail": ["zhangsan@example.com"], "memberOf": ["CN=Admins,OU=Groups,DC=example,DC=com", "CN=Developers,OU=Groups,DC=example,DC=com"] }

我们的Realm需将memberOf映射为 Shiro 角色。但直接存 CN 字符串不安全(含特殊字符、长度超限),因此采用白名单映射策略:

// CustomCasRealm.java public class CustomCasRealm extends AuthorizingRealm { // 角色映射白名单:CAS group DN → Shiro role name private final Map<String, String> groupRoleMap = new HashMap<String, String>() {{ put("CN=Admins,OU=Groups,DC=example,DC=com", "admin"); put("CN=Developers,OU=Groups,DC=example,DC=com", "developer"); put("CN=Testers,OU=Groups,DC=example,DC=com", "tester"); }}; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { CustomCasToken casToken = (CustomCasToken) token; Assertion assertion = casToken.getAssertion(); String username = assertion.getPrincipal().getName(); // CAS 用户名 // 1. 查询本地用户表,确认启用状态 User user = userDao.findByUsername(username); if (user == null || !user.isEnabled()) { throw new UnknownAccountException("User " + username + " is not enabled in local system."); } // 2. 构建 AuthenticationInfo:principals = username, credentials = CAS 认证标记 SimpleAuthenticationInfo authInfo = new SimpleAuthenticationInfo( username, // principals "CAS_AUTHENTICATED", // credentials(非密码,仅为标记) ByteSource.Util.bytes(username), // salt(用 username 作 salt,保证唯一性) getName() // realm name ); return authInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo(); // 1. 从 CAS Assertion 中提取 Attributes Assertion assertion = getCurrentAssertion(); // 需从 ThreadLocal 或 request 中获取 if (assertion != null && assertion.getAttributes() != null) { List<String> memberOf = (List<String>) assertion.getAttributes().get("memberOf"); if (memberOf != null) { for (String groupDn : memberOf) { String roleName = groupRoleMap.get(groupDn); if (roleName != null) { authInfo.addRole(roleName); } } } } // 2. 【可选】补充数据库权限:根据 username 查询 menu_urls、api_perms 等 List<String> permissions = permissionDao.findPermissionsByUsername(username); authInfo.addStringPermissions(permissions); return authInfo; } // 辅助方法:从当前线程绑定的 request 中获取 Assertion private Assertion getCurrentAssertion() { HttpServletRequest request = WebUtils.getHttpRequest(); return (Assertion) request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION); } }

注意:getCurrentAssertion()方法依赖WebUtils.getHttpRequest(),这意味着你的Realm必须运行在 Web 环境中(即ShiroFilter已生效)。若在非 Web 场景(如定时任务)调用Realm,此方法会返回 null —— 这是设计使然,因为 CAS 认证本身就是一个 Web 交互过程。

3.3 ShiroFilter 配置:URL 权限规则与 CasFilter 的协同

shiroFilter的filterChainDefinitions必须与CasFilter的url-pattern保持一致,且需显式放行 CAS 回调路径。典型shiro.ini片段如下:

[main] # 自定义 Realm casRealm = com.example.security.CustomCasRealm securityManager.realm = $casRealm # Session Manager(可选,增强会话控制) sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO sessionManager.sessionDAO = $sessionDAO [urls] # 放行 CAS 登录/登出回调路径(必须!否则重定向死循环) /cas/login = anon /cas/logout = anon /app/login = anon /app/logout = logout # 所有 /app/** 路径走 CAS + Shiro 认证流 /app/** = authc # 静态资源放行 /static/** = anon /assets/** = anon

关键点在于/app/** = authc:它表示所有匹配该路径的请求,都需经过 Shiro 的authcFilter(即FormAuthenticationFilter的父类AuthenticationFilter)。而由于CasFilter已提前将CustomCasToken提交至SecurityManager,authcFilter 检测到Subject.isAuthenticated() == true,便直接放行,不再跳转登录页。这就是单点登录的静默体验来源。


4. 避坑 / 常见问题 / 排查:五个血泪经验换来的 CAS+Shiro 调试清单

4.1 现象:用户登录 CAS 后,反复重定向到/cas/login,形成死循环

原因:CasAuthenticationFilter与ShiroFilter的url-pattern不一致,或ShiroFilter未正确识别CasToken认证状态。
解决:

  • 检查web.xml中两个 Filter 的<url-pattern>是否完全相同(如都是/app/*);
  • 在CustomCasRealm.doGetAuthenticationInfo()中加日志,确认方法是否被调用;若未调用,说明CasFilter未成功提交 Token,检查CustomCasFilter.createCasToken()是否抛异常(常见于Assertion为 null);
  • 确保shiro.ini中/app/** = authc规则存在,且无更宽泛的/** = anon覆盖它。

4.2 现象:Subject.getPrincipal()返回 CAS 用户名,但Subject.getRoles()为空

原因:Realm.doGetAuthorizationInfo()未被触发,或Assertion.getAttributes()为空。
解决:

  • 在doGetAuthorizationInfo()开头加日志,确认是否执行;若未执行,检查ShiroFilter是否配置了authc规则(而非anon);
  • 使用浏览器开发者工具查看 CAS 登录后重定向 URL,确认是否带ticket=参数;若无,说明CasAuthenticationFilter未拦截成功;
  • 登录 CAS Server 后台,检查 Service Ticket 验证日志,确认Attributes是否正常返回;若 CAS Server 未配置返回memberOf,则assertion.getAttributes().get("memberOf")必为 null。

4.3 现象:@RequiresRoles("admin")注解不生效,页面仍可访问

原因:Shiro 的AuthorizationAttributeFilter未启用,或shiro.ini中未开启 AOP 代理。
解决:

  • 确认shiro.ini中已配置aop模块:
    [main] # 启用 AOP 代理(Spring MVC 环境必需) advisor = org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor advisor.securityManager = $securityManager
  • 在 Spring 配置文件(如applicationContext.xml)中注册该 Advisor:
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
  • 检查 Controller 类是否被 Spring AOP 代理(即是否通过ApplicationContext.getBean()获取,而非new实例化)。

4.4 现象:CAS 登录后,Subject.getSession()报NullPointerException

原因:ShiroFilter初始化时未正确设置SessionManager,或web.xml中ShiroFilter的dispatcher未包含REQUEST。
解决:

  • 在shiro.ini中显式配置SessionManager:
    [main] sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager securityManager.sessionManager = $sessionManager
  • 检查web.xml中shiroFilter的<filter-mapping>是否包含<dispatcher>REQUEST</dispatcher>(默认存在,但显式声明更稳妥):
    <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/app/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping>

4.5 现象:本地开发时 CAS 登录正常,部署到测试环境后CasValidationFilter报javax.net.ssl.SSLHandshakeException

原因:测试环境服务器 JDK 信任库(cacerts)未导入 CAS Server 的 HTTPS 证书。
解决:

  • 用openssl s_client -connect cas.example.com:443 -showcerts导出 CAS Server 证书;
  • 使用keytool -import -alias cas-server -file cas.crt -keystore $JAVA_HOME/jre/lib/security/cacerts导入;
  • 重启应用服务器。

提示:生产环境应使用受信 CA 签发的证书,避免手动导入;开发环境可临时禁用 SSL 验证(不推荐),方法是在Cas30ProxyReceivingTicketValidationFilter的init-param中添加redirectAfterValidation=false并配合自定义CasValidator,但此举牺牲安全性。


5. 权限动态刷新与登出联动:让 CAS 单点登出真正“单点”

5.1 CAS 单点登出(SLO)的双向通知机制

CAS 单点登出不是简单地清空本系统 Session,而是 CAS Server 主动向所有已登录的 Client 发起 HTTP POST 请求(/cas/logout),要求它们主动销毁本地会话。这就要求我们的应用必须:

  1. 暴露/cas/logout端点,接收 CAS Server 的登出通知;
  2. 在该端点中,调用Subject.logout()销毁 Shiro Session;
  3. 同时,当用户主动点击“退出”按钮时,不仅要调Subject.logout(),还要重定向至 CAS Server 的/cas/logout完成全局登出。

这是一个典型的双向通信场景:CAS Server → Client(被动通知),Client → CAS Server(主动发起)。

5.2 实现/cas/logout端点:用 Spring MVC Controller 接收 CAS SLO 请求

CAS Server 发送的登出请求是POST /cas/logout,Body 中包含<samlp:LogoutRequest>XML。我们无需解析 SAML,只需确保该路径被ShiroFilter放行,并在 Controller 中强制登出:

// CasLogoutController.java @Controller public class CasLogoutController { @RequestMapping(value = "/cas/logout", method = RequestMethod.POST) public String handleCasLogout(HttpServletRequest request, HttpServletResponse response) { // 1. 获取当前 Subject Subject subject = SecurityUtils.getSubject(); if (subject != null && subject.isAuthenticated()) { try { // 2. 强制登出:销毁 Session & 清除 Principal subject.logout(); // 3. 清空 Cookie(可选,增强安全性) Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("JSESSIONID".equals(cookie.getName())) { cookie.setMaxAge(0); cookie.setPath("/"); response.addCookie(cookie); } } } } catch (Exception e) { // 记录错误,但不中断响应(CAS 不校验登出响应内容) log.error("Failed to logout on CAS SLO notification", e); } } // 4. 返回 200 OK,CAS Server 会继续通知其他 Client return "redirect:/cas/loggedout"; } @RequestMapping("/cas/loggedout") public String loggedOutPage() { return "cas_loggedout"; // 登出成功提示页 } }

同时,更新shiro.ini,放行该路径:

[urls] /cas/logout = anon /cas/loggedout = anon

注意:/cas/logout必须设为anon,否则ShiroFilter会先尝试认证,而此时用户 Session 可能已被 CAS Server 作废,导致 401 错误,CAS Server 则认为登出失败。

5.3 主动登出:前端按钮触发 CAS 全局登出的完整链路

用户点击“退出”时,不能只调Subject.logout(),否则只是本地登出,CAS Server 仍认为用户在线。正确做法是:

  1. 前端 JavaScript 先调用本系统/app/logout(触发Subject.logout());
  2. 然后立即重定向至https://cas.example.com/cas/logout?service=http%3A%2F%2Flocalhost%3A8080%2Fapp%2Flogin。

service参数指定了登出完成后 CAS Server 应重定向回的地址(即本系统登录页)。后端/app/logoutController 如下:

// AppLogoutController.java @Controller public class AppLogoutController { @RequestMapping("/app/logout") public String localLogout(HttpServletRequest request, HttpServletResponse response) { Subject subject = SecurityUtils.getSubject(); if (subject != null) { subject.logout(); } // 清空 JSESSIONID Cookie Cookie cookie = new Cookie("JSESSIONID", ""); cookie.setMaxAge(0); cookie.setPath("/"); response.addCookie(cookie); return "redirect:/app/login"; } }

前端按钮代码(纯 HTML + JS):

<a href="#" onclick="casLogout()">退出登录</a> <script> function casLogout() { // 1. 先本地登出 fetch('/app/logout', { method: 'GET' }) .then(() => { // 2. 再跳转 CAS 全局登出 window.location.href = 'https://cas.example.com/cas/logout?service=' + encodeURIComponent('http://localhost:8080/app/login'); }) .catch(err => console.error('Local logout failed:', err)); } </script>

5.4 权限动态刷新:当用户角色变更时,如何避免重启应用?

CAS 用户的角色由 CAS Server 的后端(如 LDAP)决定,而 Shiro 的AuthorizationInfo默认是缓存的(AuthorizationInfo缓存时间由CacheManager控制)。如果用户在 LDAP 中被移出Admins组,但本系统Realm仍返回旧的AuthorizationInfo,就会出现权限滞后。解决方案有两种:
方案一(推荐):禁用 AuthorizationInfo 缓存
在shiro.ini中关闭缓存:

[main] # 禁用 AuthorizationInfo 缓存(每次请求都调用 doGetAuthorizationInfo) authorizationCache = org.apache.shiro.cache.MemoryConstrainedCacheManager securityManager.authorizationCacheManager = $authorizationCache # 或直接设为 null securityManager.authorizationCacheManager = null

方案二:手动清除缓存
当检测到用户角色变更(如监听 LDAP 变更事件),调用:

// 清除指定用户的 AuthorizationInfo 缓存 Cache<Object, AuthorizationInfo> cache = securityManager.getAuthorizationCache(); cache.remove(username);

我一般会选方案一,因为 CAS 场景下权限变更频率低,而每次请求都查一次Assertion.getAttributes()开销极小(内存操作),远小于引入缓存一致性难题的成本。从那以后我每次做 CAS 集成,都强制在shiro.ini里加上securityManager.authorizationCacheManager = null这一行,省去后续排查权限不更新的半夜电话。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 7:54:54

Adobe Illustrator高效实战:从矢量设计到品牌VI全流程指南

用了十来年Adobe Illustrator&#xff0c;从最初拿它做logo、画名片&#xff0c;到现在处理一整套视觉识别方案&#xff0c;这个软件已经成了我日常设计流程里最顺手也最绕不开的工具。经常有人问我“AI怎么学”&#xff0c;我的答案往往不是“去背快捷键”或者“把教程刷一遍”…

作者头像 李华
网站建设 2026/10/19 7:53:40

从0到1分析Office恶意文档:宏病毒、OLE与模板注入的识别与处置

1. 先搞清楚office病毒到底是什么形态1.1 常见的三副面孔&#xff1a;宏、OLE对象与模板注入我在实际分析里&#xff0c;恶意office文档翻来覆去就那么几类“套路”&#xff0c;归根结底是攻击者想把一段代码塞进文档&#xff0c;然后诱导目标打开。最常见的第一类是宏病毒&…

作者头像 李华
网站建设 2026/10/19 7:53:24

OpenCV移动侦测实战:KNN背景建模+双路融合+ROI时序滤波

简介&#xff1a;本资源是一个基于OpenCV的C移动侦测实现项目&#xff0c;面向计算机视觉初学者、安防系统开发人员及嵌入式图像处理学习者&#xff0c;解决视频流中运动物体实时检测的核心问题&#xff0c;适用于家庭监控、智能门铃、交通行为分析等典型场景。压缩包共16个文件…

作者头像 李华
网站建设 2026/10/19 7:51:28

技术图表的专业设计方法:从选型到细节,降低认知负担

画图这件事&#xff0c;我在刚工作时是完全没放在心上的。当时的想法很简单&#xff1a;功能写完、接口调通、文档一交&#xff0c;谁有空看你画的什么鬼图。真正被教育的是一次设计评审&#xff1a;我把自己辛辛苦苦画好的系统关系图投到大屏上&#xff0c;讲了三分钟&#xf…

作者头像 李华
网站建设 2026/10/19 7:51:22

2026届毕业生实测:AI写作工具如何高效完成论文、简历与文案

2026届毕业生&#xff0c;说实话&#xff0c;这两年关于“用AI写论文/写简历”的讨论已经多到不行。但真正到了自己要用的时候&#xff0c;很多人反而懵了&#xff1a;网上评测说得天花乱坠&#xff0c;实际打开工具&#xff0c;却不知道从哪下手。我最近帮几个学弟学妹试了一圈…

作者头像 李华
网站建设 2026/10/19 7:51:06

STM32Cube F1 V1.8.0 固件包实战:HAL与LL库选型、工程配置与避坑指南

简介&#xff1a;STM32Cube_FW_F1_V1.8.0 是意法半导体面向 STM32 F1 系列微控制器推出的官方固件封装库&#xff0c;配套 STM32CubeMX 图形化配置工具&#xff0c;适用于使用 Keil、IAR 进行嵌入式开发的初学者与工程师。它通过 HAL 与 LL 两层标准化 API 屏蔽底层寄存器差异&…

作者头像 李华