
1. 项目概述为什么我们需要盘点登录与鉴权框架在任何一个需要区分用户身份、控制资源访问的应用里登录和鉴权都是绕不开的基石。简单来说登录解决“你是谁”的问题而鉴权则回答“你能做什么”。我见过太多项目初期为了快速上线用几行简单的用户名密码校验就草草了事结果随着业务扩张权限体系变得一团乱麻代码里到处是if-else判断维护起来苦不堪言。更别提安全漏洞了一个不小心用户数据就可能暴露。所以今天我们不聊具体的业务代码而是把目光投向那些经过社区千锤百炼、能帮我们系统化解决这些问题的“轮子”——开源登录及权限认证框架。无论是单体应用还是微服务架构无论是传统的网页表单登录还是时髦的微信扫码、JWT令牌选对一个合适的框架就等于为你的应用安全性和可维护性打下了坚实的地基。这次盘点我会结合自己这些年踩过的坑和实战经验带你梳理几个主流框架的核心思想、适用场景和那些“坑爹”的细节目标是让你看完后能根据自己项目的实际情况做出最合适的技术选型。2. 核心概念辨析认证、授权、鉴权与权限在深入框架之前我们必须先把几个容易混淆的概念理清楚。很多开发者甚至一些文档都会混用这些术语但这会导致我们在设计和沟通时出现偏差。2.1 认证证明你是你认证英文是Authentication简称AuthN。它的核心任务是验证主体的身份。这个“主体”通常就是用户。最常见的例子就是输入用户名和密码。系统核对密码是否正确就是在完成认证。除了密码还有手机验证码、指纹、人脸识别、第三方登录微信、GitHub等这些都是认证的手段。认证成功后系统会建立一个会话Session或颁发一个令牌Token用来在后续请求中标识这个已认证的用户。所以认证回答的问题是“你是否是系统所声称的那个用户”2.2 授权与权限你能做什么授权英文是Authorization简称AuthZ。它发生在认证之后。当系统知道“你是谁”之后授权要决定“你被允许做什么”。权限则是授权的具体体现是访问特定资源或执行特定操作的许可。这里通常分为两个层次访问控制粗粒度地控制用户能否进入某个功能模块或页面。例如普通用户不能访问后台管理页面。权限控制细粒度地控制用户对具体数据或操作的权利。例如部门经理只能审批本部门的报销单而不能审批其他部门的。2.3 鉴权检查的过程鉴权这个词在中文语境里有点特殊它有时是认证和授权的统称有时特指授权检查的过程。在技术框架中我们通常把它理解为“权限鉴定”的过程即系统在用户试图执行某个操作或访问某个资源时根据用户的身份和角色去检查他是否拥有相应的权限。这个过程就是鉴权。所以一个完整的流程是用户先通过认证登录然后在其发起请求时系统进行鉴权依据授权规则判断是否放行。注意在实际开发中尤其是在Spring Security这类框架的语境下我们常说“配置权限”其实就是在定义授权规则而框架在过滤器链中自动执行的过程就是鉴权。理清这些概念后我们就能明白一个完整的登录及权限认证框架需要提供一套机制来处理从用户声明身份登录到系统验证身份认证再到根据规则判断访问资格授权与鉴权的全流程。3. 主流开源登录及权限认证框架深度解析上市面上框架众多各有侧重。有的重功能全但学习曲线陡有的轻灵活但需要自己组装更多部件。我选取了几个在Java和泛Web开发领域最具代表性、生态最成熟的框架进行拆解。3.1 Spring SecurityJava企业级安全的“定海神针”提到Java领域的权限框架Spring Security是绝对无法绕开的名字。它与其说是一个框架不如说是一个高度可定制、功能全面的安全解决方案。3.1.1 核心设计思想过滤器链与委托Spring Security的核心是一系列串联的Servlet Filter。一个HTTP请求到达应用后会经过这条安全过滤器链。链上的每个过滤器负责一项特定的安全任务例如UsernamePasswordAuthenticationFilter: 处理表单登录。BasicAuthenticationFilter: 处理HTTP Basic认证。FilterSecurityInterceptor: 进行最终的访问决策鉴权判断某个URL是否需要特定权限。这种设计的好处是职责清晰你可以轻松地添加、移除或替换过滤器来定制安全流程。它的授权模型主要基于“角色”和“权限”通过注解如PreAuthorize(“hasRole(‘ADMIN’)”)或配置HttpSecurity来声明访问规则。3.1.2 核心优势与适用场景深度集成Spring生态与Spring Boot无缝结合几乎成为Spring技术栈项目的默认安全选择。功能极其全面从经典的Session管理、Remember-Me、防CSRF、防点击劫持到OAuth2客户端/资源服务器、LDAP、SAML等企业级协议应有尽有。高度可配置与可扩展几乎每一个组件都可以被覆盖或扩展能满足最复杂的安全需求。3.1.3 典型配置与实操要点一个最基本的Spring Security配置可能长这样Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/, /home).permitAll() // 首页允许所有人访问 .antMatchers(/admin/**).hasRole(ADMIN) // /admin/ 下的路径需要ADMIN角色 .antMatchers(/user/**).hasRole(USER) // /user/ 下的路径需要USER角色 .anyRequest().authenticated() // 其他所有请求都需要认证 .and() .formLogin() .loginPage(/login) // 自定义登录页 .permitAll() .and() .logout() .permitAll(); } Bean Override public UserDetailsService userDetailsService() { // 这里简单使用内存用户生产环境需从数据库加载 UserDetails user User.withDefaultPasswordEncoder() .username(user) .password(password) .roles(USER) .build(); UserDetails admin User.withDefaultPasswordEncoder() .username(admin) .password(admin) .roles(ADMIN, USER) .build(); return new InMemoryUserDetailsManager(user, admin); } }3.1.4 避坑指南与心得配置顺序很重要在authorizeRequests()中规则的声明顺序就是匹配顺序。更具体的规则要放在更通用的规则前面。如果把.anyRequest().authenticated()放在最前面后面的所有规则都会失效。小心密码编码器上面的例子用了withDefaultPasswordEncoder()这仅用于演示绝对禁止在生产环境使用。生产环境必须使用BCryptPasswordEncoder、Pbkdf2PasswordEncoder等安全的、带随机盐的编码器。理解“角色”与“权限”Spring Security中角色本质上是一种带有ROLE_前缀的特殊权限。hasRole(‘ADMIN’)会自动检查ROLE_ADMIN。如果你直接使用权限字符串则用hasAuthority(‘WRITE_PRIVILEGE’)。微服务下的挑战在纯粹的微服务架构中每个服务都引入完整的Spring Security会显得笨重且Session状态难以共享。此时通常会将认证网关化服务本身采用无状态的Token如JWT鉴权Spring Security可以配置为OAuth2资源服务器来适配这种模式。3.2 Apache Shiro力求简单灵活的“轻骑兵”与Spring Security的“重”相比Shiro的设计哲学是“简单和灵活”。它不依赖任何容器或框架可以运行在任何环境从简单的命令行应用到大型的Web应用。3.2.1 核心设计思想Subject、SecurityManager与RealmShiro的架构非常清晰核心是三个概念Subject代表当前执行操作的用户或第三方服务、定时任务等。所有权限判断都围绕Subject进行。SecurityManagerShiro的核心管理所有Subject负责认证、授权、会话管理等。它是Shiro的“大脑”。Realm充当Shiro与应用安全数据如用户数据库、LDAP服务器之间的“桥梁”。开发者需要自己实现Realm告诉Shiro如何根据用户名获取用户信息及权限。这种设计让Shiro的学习曲线相对平缓概念直观。3.2.2 核心优势与适用场景API直观易用subject.login(token),subject.hasRole(“admin”),subject.isPermitted(“user:delete”)代码读起来就像自然语言。轻量级无侵入不强制依赖Spring等框架可以轻松集成到任何项目中。功能模块化虽然核心功能是认证授权但也提供了会话管理、缓存、加密、Remember-Me等可选模块按需取用。易于理解对于中小型项目或团队中Spring背景不深的成员Shiro更容易上手。3.2.3 典型配置与实操要点在Spring Boot中集成Shiro的一个简单示例引入依赖(Maven):dependency groupIdorg.apache.shiro/groupId artifactIdshiro-spring-boot-starter/artifactId version1.11.0/version /dependency自定义Realm:public class MyShiroRealm extends AuthorizingRealm { Autowired private UserService userService; // 授权获取用户的角色和权限信息 Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo new SimpleAuthorizationInfo(); // 从数据库查询角色和权限 SetString roles userService.findRolesByUsername(username); SetString permissions userService.findPermissionsByUsername(username); authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } // 认证验证用户身份 Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username (String) token.getPrincipal(); User user userService.findByUsername(username); if (user null) { throw new UnknownAccountException(); // 用户不存在 } // 参数用户名数据库中的密码当前Realm名 return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } }Shiro配置类:Configuration public class ShiroConfig { Bean public Realm myRealm() { return new MyShiroRealm(); } Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager manager new DefaultWebSecurityManager(); manager.setRealm(realm); return manager; } Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chain new DefaultShiroFilterChainDefinition(); // 静态资源允许匿名访问 chain.addPathDefinition(/static/**, anon); // 登录接口允许匿名访问 chain.addPathDefinition(/login, anon); // 退出登录 chain.addPathDefinition(/logout, logout); // 管理员路径需要admin角色 chain.addPathDefinition(/admin/**, authc, roles[admin]); // 其他所有路径都需要认证 chain.addPathDefinition(/**, authc); return chain; } }3.2.4 避坑指南与心得密码比较在Realm的doGetAuthenticationInfo方法中我们返回的SimpleAuthenticationInfo包含了从数据库查出的正确密码。Shiro会自动用它来比较用户登录时输入的密码。你只需要确保数据库存储的是加密后的密码并在Realm中配置相应的CredentialsMatcher如HashedCredentialsMatcher来指定加密算法。Session管理Shiro提供了自己的Session API可以独立于Servlet容器的HttpSession工作。这在分布式环境下很有用但需要注意如果你同时使用了Shiro Session和HttpSession要理清它们的关系避免混淆。过滤器链Shiro的过滤器authc,anon,roles等是静态配置的不如Spring Security的DSL动态灵活。对于非常复杂的、动态的URL权限规则配置起来可能会有些繁琐。注解支持Shiro也支持RequiresRoles,RequiresPermissions等注解但需要借助AOP如Spring AOP来生效集成步骤比Spring Security原生注解稍多一步。3.3 JSON Web Tokens无状态时代的“通行证”JWT本身不是一个框架而是一种开放标准。但在现代前后端分离、微服务架构中它已经成为实现无状态认证/授权事实上的标准协议几乎所有主流的安全框架都支持或基于它构建。3.3.1 核心设计思想自包含的令牌JWT的核心思想是将认证和授权信息直接编码到一个令牌里这个令牌由服务端签发客户端保存并在每次请求时携带。服务端只需验证令牌的签名即可信任其中的内容无需再去查询数据库或共享Session。一个JWT由三部分组成用点分隔Header.Payload.Signature。Header声明令牌类型和签名算法如{“alg”: “HS256”, “typ”: “JWT”}。Payload存放实际需要传递的数据比如用户ID、角色、过期时间等。这部分信息是Base64编码的任何人都可以解码看到所以绝不能存放密码等敏感信息。Signature对前两部分的签名用于验证消息在传输过程中未被篡改以及确认发送者的身份。3.3.2 核心优势与适用场景无状态可扩展服务端不需要存储会话信息天生适合分布式系统和微服务。跨域友好可以轻松在移动端、浏览器、API网关之间传递。自包含减少了查询用户信息的数据库开销。标准化有严格的RFC标准各种语言都有成熟库支持。3.3.3 典型工作流程用户使用凭证如密码登录。认证服务验证凭证生成一个包含用户身份和权限的JWT并返回给客户端通常放在Authorization: Bearer token头中。客户端将JWT存储在本地如LocalStorage或Cookie。客户端在后续请求的Header中携带此JWT。资源服务或API网关验证JWT的签名和有效期。如果有效则从Payload中提取用户信息进行鉴权。3.3.4 实操要点与避坑指南令牌存储与传输安全不要将JWT存储在localStorage中如果网站存在XSS漏洞令牌可能被窃取。相对更安全的方式是使用HttpOnly、Secure的Cookie。必须使用HTTPS来传输JWT防止令牌在传输中被窃听。令牌过期与刷新JWT一旦签发在过期前无法被服务端主动废止。这是双刃剑。通常策略是设置一个较短的过期时间如15分钟并提供一个刷新令牌来获取新的访问令牌。刷新令牌需要持久化存储并可以主动撤销。Payload不要过大JWT在每次请求中都会携带过大的Payload会增加网络开销。只存放必要的最小信息集。签名算法选择HS256对称加密用同一个密钥进行签名和验证。简单高效但密钥需要在签发方和验证方之间安全共享。适合单体或服务端完全受控的环境。RS256非对称加密用私钥签名公钥验证。公钥可以公开分发验证方无需知道私钥。这是更安全、更推荐用于分布式环境的方式。“注销”难题由于无状态服务端无法直接让一个未过期的JWT失效。常见的解决方案有使用短有效期令牌降低风险窗口。维护一个很小的令牌黑名单用于注销近期令牌但这又引入了状态。改变密钥极端情况会使所有令牌失效。3.4 OAuth 2.0 与 OpenID Connect第三方授权与身份认证的“黄金标准”当你的应用需要允许用户使用微信、GitHub、Google等第三方账号登录或者你需要开放API给第三方应用调用时OAuth 2.0和OpenID Connect就是你必须掌握的标准。3.4.1 OAuth 2.0专注授权OAuth 2.0是一个授权框架核心是解决“第三方应用在用户授权下访问用户在资源服务器上的受保护资源”的问题。它不处理身份认证。例如一个第三方天气应用想获取你在微信的头像它引导你到微信授权页面你同意后微信给天气应用一个访问令牌天气应用凭此令牌去微信获取你的头像。在这个过程中天气应用并不知道你的微信密码也不知道你到底是不是你它只知道“微信说这个令牌可以访问某个用户的头像”。3.4.2 OpenID Connect在OAuth 2.0之上构建身份层OIDC是建立在OAuth 2.0之上的一个身份认证协议。它在授权流程中额外返回一个ID Token一个特殊的JWT这个令牌里包含了用户的身份信息如用户唯一标识sub。这样第三方应用不仅能拿到访问资源的令牌还能确切地知道登录的用户是谁。“使用微信登录”这个场景本质上用的就是OIDC协议。3.4.3 核心角色与流程以授权码模式最安全、最常用的模式为例资源所有者用户。客户端你的Web应用或移动应用。授权服务器提供登录和授权页面的服务如微信开放平台、GitHub。资源服务器存放用户受保护资源的服务如微信存储用户头像的服务器。 流程客户端将用户重定向到授权服务器的登录/授权页面。用户登录并授权。授权服务器将用户重定向回客户端并附上一个授权码。客户端用授权码和自己的密钥向授权服务器换取访问令牌和ID Token。客户端使用访问令牌向资源服务器请求资源。3.4.4 在Spring Security中实现OAuth 2.0客户端Spring Security提供了强大的OAuth 2.0客户端支持配置非常简单以GitHub登录为例引入依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-oauth2-client/artifactId /dependency配置文件application.ymlspring: security: oauth2: client: registration: github: client-id: your-github-client-id client-secret: your-github-client-secret scope: user:email, read:user配置安全规则Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); // 启用OAuth2登录 } }完成Spring Security会自动处理所有重定向、换令牌的流程并将认证成功的用户信息注入到SecurityContext中。3.4.5 避坑指南与心得授权码模式是王道对于Web服务器端应用务必使用授权码模式。隐式模式等已被标记为不安全不应再使用。妥善保管client-secret这是你应用的身份凭证必须像保护密码一样保护它绝不能泄露到前端。正确配置回调地址在第三方平台注册应用时回调地址必须完全匹配包括协议、域名、端口和路径。state参数防CSRF在发起OAuth请求时必须生成一个随机的state参数并保存在会话中在回调时验证其一致性防止CSRF攻击。区分OAuth2资源服务器与客户端如果你的应用是提供API的一方资源服务器需要配置EnableResourceServer如果是调用第三方API的一方客户端则配置EnableOAuth2Client或使用oauth2Client()。概念别搞混。4. 框架选型核心考量因素看了这么多框架和协议到底该怎么选没有银弹只有最适合你当前场景的选择。你可以从下面几个维度来评估4.1 项目架构与规模单体/简单Web应用Spring Security或Apache Shiro都是不错的选择。如果项目本身就是Spring Boot技术栈用Spring Security更省心如果追求轻量、简单或者项目非Spring体系Shiro是很好的选择。前后端分离/SPA应用无状态的JWT是天然搭档。可以选择Spring Security JWT或者使用专门针对现代应用的安全库。微服务/分布式系统需要将认证网关化如使用Spring Cloud Gateway OAuth2。各个微服务作为资源服务器使用JWT进行无状态鉴权。OAuth2和OIDC是解决服务间授权和外部身份联合的标准方案。4.2 团队技术栈与熟悉度如果团队对Spring生态非常熟悉Spring Security的学习成本相对较低集成也更顺畅。如果团队背景多样或者项目技术栈较老Shiro的简单API和低侵入性可能更容易被接受。如果团队正在构建全新的云原生或微服务应用那么从设计之初就采用基于JWT和OAuth2的现代化安全架构是更面向未来的选择。4.3 安全需求与合规性对于金融、政务等对安全要求极高的场景需要框架支持细粒度的权限模型如RBAC、ABAC、完整的审计日志、多因素认证等。Spring Security的生态和扩展性能更好地满足这些复杂需求。如果需要对接已有的企业身份提供商如LDAP、Active Directory、SAML IdPSpring Security对企业协议的支持通常更全面。如果应用需要面向公众提供社交账号登录那么支持OAuth2/OIDC是必须的。4.4 性能与可维护性JWT的无状态特性在性能上有优势但需要仔细设计令牌刷新和注销机制。Session方案更成熟服务端可控性强但在分布式环境下需要解决Session共享问题如用Redis引入了状态和复杂度。权限规则的维护方式是硬编码在配置/注解里还是需要动态从数据库加载Spring Security和Shiro都支持动态权限但实现方式不同需要评估哪种更符合你的运维习惯。5. 常见问题与排查技巧实录在实际集成和使用这些框架时你几乎一定会遇到下面这些问题。我把它们和排查思路整理出来希望能帮你快速定位。5.1 Spring Security 相关问题登录成功后无限重定向到登录页。排查这是最常见的问题之一。首先检查你的登录页面URL是否被permitAll()放行。然后检查登录成功后的默认跳转路径defaultSuccessUrl是否也是一个需要认证的路径导致循环。使用浏览器开发者工具的网络面板查看重定向链能清晰看到跳转过程。心得建议显式配置successHandler来处理登录成功后的逻辑而不是依赖默认跳转。问题PreAuthorize注解不生效。排查确保在配置类上添加了EnableGlobalMethodSecurity(prePostEnabled true)注解。同时确保方法调用是通过Spring代理的例如在同一个类内部调用带注解的方法会绕过代理导致注解失效。问题CSRF防护导致POST请求被拒绝403。排查Spring Security默认启用CSRF防护。对于传统的表单提交你需要在前端页面如Thymeleaf的表单中插入input type”hidden” name”_csrf” th:value”${_csrf.token}”/。对于纯API如前后端分离如果不需要CSRF可以在配置中禁用.csrf().disable()但务必确保你的API有其他方式防止CSRF如使用JWT并在Header中传递。5.2 Shiro 相关问题自定义Realm的授权方法doGetAuthorizationInfo被多次调用。排查Shiro默认每次权限检查都会调用授权方法。这可能导致数据库查询压力。解决方案是启用缓存。可以集成Redis或Ehcache并在Realm中配置缓存管理。心得在生产环境中必须为授权信息配置缓存并合理设置缓存过期时间。问题Shiro的注解如RequiresRoles无效。排查Shiro的注解需要AOP支持。在Spring中你需要确保配置了EnableAspectJAutoProxy并且将Shiro的AuthorizationAttributeSourceAdvisorBean加入到Spring容器中。5.3 JWT 相关问题JWT令牌过期后如何实现无感刷新方案采用双令牌机制。访问令牌Access Token有效期短如15分钟刷新令牌Refresh Token有效期长如7天且存储于服务端如数据库。当访问令牌过期客户端用刷新令牌去获取新的访问令牌。刷新令牌只能使用一次获取新令牌后旧刷新令牌失效并颁发一个新的刷新令牌滚动刷新。注意刷新令牌的端点必须严格防护通常需要验证客户端身份和令牌有效性。问题如何防止JWT令牌被盗用措施 1.强制HTTPS。 2.使用HttpOnlyCookie存储尽管对SPA不友好但更安全。 3.设置较短的过期时间。 4. 对于敏感操作如修改密码、支付要求二次认证。 5. 监控异常如同一个令牌在短时间内从地理位置上相距甚远的两个IP地址使用。5.4 OAuth 2.0 相关问题获取授权码后用/oauth/token换令牌时返回invalid_grant。排查这是OAuth集成中最常见的错误。请按顺序检查 1.授权码是否已使用过授权码是一次性的。 2.回调地址是否完全一致包括http和https末尾的/。 3.client_id和client_secret是否正确 4.grant_type参数是否为authorization_code问题在资源服务器中如何获取当前OAuth2用户的详细信息方案如果使用JWT作为访问令牌资源服务器可以直接解析JWT的Payload。如果是不透明令牌则需要调用授权服务器的/userinfo端点OIDC或自定义的用户信息端点。在Spring Security中可以通过AuthenticationPrincipal注解注入一个OAuth2User对象来获取。6. 安全最佳实践与深度思考无论选择哪个框架一些安全原则是共通的必须在设计和开发中贯彻始终。6.1 密码安全是底线永远不要明文存储密码。使用强哈希算法如BCryptSpring Security默认、Argon2、PBKDF2。加盐现代密码哈希函数如BCrypt会自动处理盐值无需手动管理。前端传输也应加密虽然HTTPS是必须的但对于极高安全要求的场景可以考虑在前端对密码进行非对称加密如RSA服务端用私钥解密后再哈希存储。但这会增加复杂度需权衡。6.2 最小权限原则给用户或服务分配完成任务所必需的最小权限。不要图省事给所有用户admin角色。在代码审查时要特别关注权限配置和注解检查是否有过度授权的情况。6.3 防御常见攻击SQL注入使用预编译语句如MyBatis的#{}JPA的参数化查询框架本身不直接解决此问题但错误的权限查询可能引入漏洞。XSS确保所有用户输入在输出到页面时都经过正确的转义或过滤。框架的CSRF防护可以抵御一部分利用XSS发起的攻击。CSRF确保对状态修改操作POST PUT DELETE启用CSRF防护或使用JWT等无状态方案时确保令牌不通过容易被CSRF利用的方式如Cookie传输。会话固定/劫持使用安全的Cookie属性HttpOnly,Secure,SameSite用户登录成功后使旧会话失效Spring Security默认行为。6.4 监控与审计记录所有认证和授权事件谁、在什么时候、尝试访问什么资源、是否成功。这对于安全事件追溯至关重要。监控异常登录行为如频繁失败登录、来自异常地理位置的登录、同一账号多地同时登录等。定期审查和更新依赖安全框架本身也可能出现漏洞。保持Spring Security、Shiro等依赖库的版本更新。6.5 关于“自己造轮子”我强烈建议除非有极其特殊、现有框架完全无法满足的需求否则不要从头自己实现一套认证授权系统。安全是一个深度专业领域自己实现很容易在细节上埋下难以察觉的漏洞。使用成熟的开源框架不仅是利用其功能更是站在巨人的肩膀上继承了社区无数开发者共同审视和修补的安全经验。你的精力应该更多地放在业务逻辑和如何正确配置、使用这些框架上。