整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Spring Security 实战干货:必须掌握的

Spring Security 实战干货:必须掌握的一些内置 Filter

自公号:Felordcn

1. 前言

上一文我们使用 Spring Security 实现了各种登录聚合的场面。其中我们是通过在 UsernamePasswordAuthenticationFilter 之前一个自定义的过滤器实现的。我怎么知道自定义过滤器要加在 UsernamePasswordAuthenticationFilter 之前。我在这个系列开篇说了 Spring Security 权限控制的一个核心关键就是 过滤器链 ,这些过滤器如下图进行过滤传递,甚至比这个更复杂!这只是一个最小单元。

Spring Security 内置了一些过滤器,他们各有各的本事。如果你掌握了这些过滤器,很多实际开发中的需求和问题都很容易解决。今天我们来见识一下这些内置的过滤器。

2. 内置过滤器初始化

Spring Security 初始化核心过滤器时 HttpSecurity 会通过将 Spring Security 内置的一些过滤器以 FilterComparator 提供的规则进行比较按照比较结果进行排序注册。

2.1 排序规则

FilterComparator 维护了一个顺序的注册表 filterToOrder 。

 FilterComparator() {
 Step order=new Step(INITIAL_ORDER, ORDER_STEP);
 put(ChannelProcessingFilter.class, order.next());
 put(ConcurrentSessionFilter.class, order.next());
 put(WebAsyncManagerIntegrationFilter.class, order.next());
 put(SecurityContextPersistenceFilter.class, order.next());
 put(HeaderWriterFilter.class, order.next());
 put(CorsFilter.class, order.next());
 put(CsrfFilter.class, order.next());
 put(LogoutFilter.class, order.next());
 filterToOrder.put(
 "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
 order.next());
 filterToOrder.put(
 "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",
 order.next());
 put(X509AuthenticationFilter.class, order.next());
 put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
 filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
 order.next());
 filterToOrder.put(
 "org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
 order.next());
 filterToOrder.put(
 "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",
 order.next());
 put(UsernamePasswordAuthenticationFilter.class, order.next());
 put(ConcurrentSessionFilter.class, order.next());
 filterToOrder.put(
 "org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
 put(DefaultLoginPageGeneratingFilter.class, order.next());
 put(DefaultLogoutPageGeneratingFilter.class, order.next());
 put(ConcurrentSessionFilter.class, order.next());
 put(DigestAuthenticationFilter.class, order.next());
 filterToOrder.put(
 "org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
 put(BasicAuthenticationFilter.class, order.next());
 put(RequestCacheAwareFilter.class, order.next());
 put(SecurityContextHolderAwareRequestFilter.class, order.next());
 put(JaasApiIntegrationFilter.class, order.next());
 put(RememberMeAuthenticationFilter.class, order.next());
 put(AnonymousAuthenticationFilter.class, order.next());
 filterToOrder.put(
 "org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
 order.next());
 put(SessionManagementFilter.class, order.next());
 put(ExceptionTranslationFilter.class, order.next());
 put(FilterSecurityInterceptor.class, order.next());
 put(SwitchUserFilter.class, order.next());
 }

这些就是所有内置的过滤器。 他们是通过下面的方法获取自己的序号:

 private Integer getOrder(Class<?> clazz) {
 while (clazz !=null) {
 Integer result=filterToOrder.get(clazz.getName());
 if (result !=null) {
 return result;
 }
 clazz=clazz.getSuperclass();
 }
 return null;
 }

通过过滤器的类全限定名从注册表 filterToOrder 中获取自己的序号,如果没有直接获取到序号通过递归获取父类在注册表中的序号作为自己的序号,序号越小优先级越高。上面的过滤器并非全部会被初始化。有的需要额外引入一些功能包,有的看 HttpSecurity 的配置情况。 在上一篇https://www.felord.cn/spring-security-login.html中。我们禁用了 CSRF 功能,就意味着 CsrfFilter 不会被注册。

3. 内置过滤器讲解

接下来我们就对这些内置过滤器进行一个系统的认识。我们将按照默认顺序进行讲解

3.1 ChannelProcessingFilter

ChannelProcessingFilter 通常是用来过滤哪些请求必须用 https 协议, 哪些请求必须用 http 协议, 哪些请求随便用哪个协议都行。它主要有两个属性:

  • ChannelDecisionManager 用来判断请求是否符合既定的协议规则。它维护了一个 ChannelProcessor 列表 这些ChannelProcessor 是具体用来执行 ANY_CHANNEL 策略 (任何通道都可以), REQUIRES_SECURE_CHANNEL 策略 (只能通过https 通道), REQUIRES_INSECURE_CHANNEL 策略 (只能通过 http 通道)。
  • FilterInvocationSecurityMetadataSource 用来存储 url 与 对应的ANY_CHANNEL、REQUIRES_SECURE_CHANNEL、REQUIRES_INSECURE_CHANNEL 的映射关系。

ChannelProcessingFilter 通过 HttpScurity#requiresChannel() 等相关方法引入其配置对象 ChannelSecurityConfigurer 来进行配置。

3.2 ConcurrentSessionFilter

ConcurrentSessionFilter 主要用来判断session是否过期以及更新最新的访问时间。其流程为:

  1. session 检测,如果不存在直接放行去执行下一个过滤器。存在则进行下一步。
  2. 根据sessionid从SessionRegistry中获取SessionInformation,从SessionInformation中获取session是否过期;没有过期则更新SessionInformation中的访问日期;
  3. 如果过期,则执行doLogout()方法,这个方法会将session无效,并将 SecurityContext 中的Authentication中的权限置空,同时在SecurityContenxtHoloder中清除SecurityContext然后查看是否有跳转的 expiredUrl,如果有就跳转,没有就输出提示信息。

ConcurrentSessionFilter 通过SessionManagementConfigurer 来进行配置。

3.3 WebAsyncManagerIntegrationFilter

WebAsyncManagerIntegrationFilter用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager。用来处理异步请求的安全上下文。具体逻辑为:

  1. 从请求属性上获取所绑定的WebAsyncManager,如果尚未绑定,先做绑定。
  2. 从asyncManager 中获取 key 为 CALLABLE_INTERCEPTOR_KEY 的安全上下文多线程处理器 SecurityContextCallableProcessingInterceptor, 如果获取到的为 null,
  3. 新建一个 SecurityContextCallableProcessingInterceptor 并绑定 CALLABLE_INTERCEPTOR_KEY 注册到 asyncManager 中。

这里简单说一下 SecurityContextCallableProcessingInterceptor 。它实现了接口 CallableProcessingInterceptor,当它被应用于一次异步执行时,beforeConcurrentHandling() 方法会在调用者线程执行,该方法会相应地从当前线程获取SecurityContext,然后被调用者线程中执行逻辑时,会使用这个 SecurityContext,从而实现安全上下文从调用者线程到被调用者线程的传输。

WebAsyncManagerIntegrationFilter 通过 WebSecurityConfigurerAdapter#getHttp()方法添加到 HttpSecurity 中成为 DefaultSecurityFilterChain 的一个链节。

3.4 SecurityContextPersistenceFilter

SecurityContextPersistenceFilter 主要控制 SecurityContext 的在一次请求中的生命周期 。 请求来临时,创建SecurityContext 安全上下文信息,请求结束时清空 SecurityContextHolder。

SecurityContextPersistenceFilter 通过 HttpScurity#securityContext() 及相关方法引入其配置对象 SecurityContextConfigurer 来进行配置。

3.5 HeaderWriterFilter

HeaderWriterFilter 用来给 http 响应添加一些 Header,比如 X-Frame-Options, X-XSS-Protection ,X-Content-Type-Options。

你可以通过 HttpScurity#headers() 来定制请求Header 。

3.6 CorsFilter

跨域相关的过滤器。这是Spring MVC Java配置和XML 命名空间 CORS 配置的替代方法, 仅对依赖于spring-web的应用程序有用(不适用于spring-webmvc)或 要求在javax.servlet.Filter 级别进行CORS检查的安全约束链接。这个是目前官方的一些解读,但是我还是不太清楚实际机制。

你可以通过 HttpSecurity#cors() 来定制。

3.7 CsrfFilter

CsrfFilter 用于防止csrf攻击,前后端使用json交互需要注意的一个问题。

你可以通过 HttpSecurity.csrf() 来开启或者关闭它。在你使用 jwt 等 token 技术时,是不需要这个的。

3.8 LogoutFilter

LogoutFilter 很明显这是处理注销的过滤器。

你可以通过 HttpSecurity.logout() 来定制注销逻辑,非常有用。

3.9 OAuth2AuthorizationRequestRedirectFilter

和上面的有所不同,这个需要依赖 spring-scurity-oauth2 相关的模块。该过滤器是处理 OAuth2 请求首选重定向相关逻辑的。以后会我会带你们认识它,请多多关注公众号:Felordcn 。

3.10 Saml2WebSsoAuthenticationRequestFilter

这个需要用到 Spring Security SAML 模块,这是一个基于 SMAL 的 SSO 单点登录请求认证过滤器。

关于SAML

SAML 即安全断言标记语言,英文全称是 Security Assertion Markup Language。它是一个基于 XML 的标准,用于在不同的安全域(security domain)之间交换认证和授权数据。在 SAML 标准定义了身份提供者 (identity provider) 和服务提供者 (service provider),这两者构成了前面所说的不同的安全域。 SAML 是 OASIS 组织安全服务技术委员会(Security Services Technical Committee) 的产品。

SAML(Security Assertion Markup Language)是一个 XML 框架,也就是一组协议,可以用来传输安全声明。比如,两台远程机器之间要通讯,为了保证安全,我们可以采用加密等措施,也可以采用 SAML 来传输,传输的数据以 XML 形式,符合 SAML 规范,这样我们就可以不要求两台机器采用什么样的系统,只要求能理解 SAML 规范即可,显然比传统的方式更好。SAML 规范是一组 Schema 定义。

可以这么说,在 Web Service 领域,schema 就是规范,在 Java 领域,API 就是规范

3.11 X509AuthenticationFilter

X509 认证过滤器。你可以通过 HttpSecurity#X509() 来启用和配置相关功能。

3.12 AbstractPreAuthenticatedProcessingFilter

AbstractPreAuthenticatedProcessingFilter 处理处理经过预先认证的身份验证请求的过滤器的基类,其中认证主体已经由外部系统进行了身份验证。 目的只是从传入请求中提取主体上的必要信息,而不是对它们进行身份验证。

你可以继承该类进行具体实现并通过 HttpSecurity#addFilter 方法来添加个性化的AbstractPreAuthenticatedProcessingFilter 。

3.13 CasAuthenticationFilter

CAS 单点登录认证过滤器 。依赖 Spring Security CAS 模块

3.14 OAuth2LoginAuthenticationFilter

这个需要依赖 spring-scurity-oauth2 相关的模块。OAuth2 登录认证过滤器。处理通过 OAuth2 进行认证登录的逻辑。

3.15 Saml2WebSsoAuthenticationFilter

这个需要用到 Spring Security SAML 模块,这是一个基于 SMAL 的 SSO 单点登录认证过滤器。 #关于SAML

3.16 UsernamePasswordAuthenticationFilter

这个看过我相关文章的应该不陌生了。处理用户以及密码认证的核心过滤器。认证请求提交的username和 password,被封装成token进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。

你可以通过 HttpSecurity#formLogin() 及相关方法引入其配置对象 FormLoginConfigurer 来进行配置。 我们在 https://www.felord.cn/spring-security-login.html 已经对其进行过个性化的配置和魔改。

3.17 ConcurrentSessionFilter

参见 #3.2 。 该过滤器可能会被多次执行。

3.18 OpenIDAuthenticationFilter

基于OpenID 认证协议的认证过滤器。 你需要在依赖中依赖额外的相关模块才能启用它。

3.19 DefaultLoginPageGeneratingFilter

生成默认的登录页。默认 /login 。

3.20 DefaultLogoutPageGeneratingFilter

生成默认的退出页。 默认 /logout 。

3.21 ConcurrentSessionFilter

参见 #3.2 。 该过滤器可能会被多次执行。

3.23 DigestAuthenticationFilter

Digest身份验证是 Web 应用程序中流行的可选的身份验证机制 。DigestAuthenticationFilter 能够处理 HTTP 头中显示的摘要式身份验证凭据。你可以通过 HttpSecurity#addFilter() 来启用和配置相关功能。

3.24 BasicAuthenticationFilter

和Digest身份验证一样都是Web 应用程序中流行的可选的身份验证机制 。 BasicAuthenticationFilter 负责处理 HTTP 头中显示的基本身份验证凭据。这个 Spring SecuritySpring Boot 自动配置默认是启用的 。

BasicAuthenticationFilter 通过 HttpSecurity#httpBasic() 及相关方法引入其配置对象 HttpBasicConfigurer 来进行配置。

3.25 RequestCacheAwareFilter

用于用户认证成功后,重新恢复因为登录被打断的请求。当匿名访问一个需要授权的资源时。会跳转到认证处理逻辑,此时请求被缓存。在认证逻辑处理完毕后,从缓存中获取最开始的资源请求进行再次请求。

RequestCacheAwareFilter 通过 HttpScurity#requestCache() 及相关方法引入其配置对象 RequestCacheConfigurer 来进行配置。

3.26 SecurityContextHolderAwareRequestFilter

用来 实现j2ee中 Servlet Api 一些接口方法, 比如 getRemoteUser 方法、isUserInRole 方法,在使用 Spring Security 时其实就是通过这个过滤器来实现的。

SecurityContextHolderAwareRequestFilter 通过 HttpSecurity.servletApi() 及相关方法引入其配置对象 ServletApiConfigurer 来进行配置。

3.27 JaasApiIntegrationFilter

适用于JAAS (Java 认证授权服务)。 如果 SecurityContextHolder 中拥有的 Authentication 是一个 JaasAuthenticationToken,那么该 JaasApiIntegrationFilter 将使用包含在 JaasAuthenticationToken 中的 Subject 继续执行 FilterChain。

3.28 RememberMeAuthenticationFilter

处理 记住我 功能的过滤器。

RememberMeAuthenticationFilter 通过 HttpSecurity.rememberMe() 及相关方法引入其配置对象 RememberMeConfigurer 来进行配置。

3.29 AnonymousAuthenticationFilter

匿名认证过滤器。对于 Spring Security 来说,所有对资源的访问都是有 Authentication 的。对于无需登录(UsernamePasswordAuthenticationFilter )直接可以访问的资源,会授予其匿名用户身份

AnonymousAuthenticationFilter 通过 HttpSecurity.anonymous() 及相关方法引入其配置对象 AnonymousConfigurer 来进行配置。

3.30 SessionManagementFilter

Session 管理器过滤器,内部维护了一个 SessionAuthenticationStrategy 用于管理 Session 。

SessionManagementFilter 通过 HttpScurity#sessionManagement() 及相关方法引入其配置对象 SessionManagementConfigurer 来进行配置。

3.31 ExceptionTranslationFilter

主要来传输异常事件,还记得之前我们见过的 DefaultAuthenticationEventPublisher 吗?

3.32 FilterSecurityInterceptor

这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。如果你要实现动态权限控制就必须研究该类

文通俗易懂地剖析用户授权的设计原理和四种授权模式,重点介绍授权码登录模式,适合阅读的人群:开放平台/第三方合作的产品经理,初入职场的产品经理。

1. 应用场景

我们每个人都遇到过授权登录的环节,授权登录的应用无孔不入,可能你是在授权应用权限,或是授权账户登录,或是授权个人信息。

我们常见的应用场景一般有以下:

  • 新安装应用:授权获取存储空间,设备信息等(手机原生弹窗)
  • 支付宝授权登录淘宝:授权使用支付账户登录淘宝APP(淘宝原生页面)
  • 微信打开美团外卖:授权获取你的头像和地理位置(微信原生弹窗)

因而授权登录是应用间交互的重要且广泛的步骤,深入了解过其中的原理很有必要。

2. 授权登录是什么?

以美团外卖授权获取你的头像和地理位置为例:

  • 头像和地理位置属于你的个人信息,如需要传输,必须经得本人的同意,法律不允许默认传输。
  • 授权登录是经得资源所有者(亦即是用户)同意,服务提供商(亦即是微信,他为你提供授权服务)提供授权服务和应用方(他来使用你的授权)使用授权的过程。

字面意思就是如下图流程:

值得关注的是第3步和第4步:当你在授权的过程中,实则是你和微信的直接交互,与美团外卖小程序无关。

亦即是:你是跟微信同意授权,也是微信接收到你的“同意”的指令,即使在网站用微信登录也是如此,如豆瓣登录,需要微信扫描二维码,确保授权动作保留和发生在微信自己的环境内。

3. 授权登录的模式

那么从形式来说,授权登录可以分为静默授权和手动授权两种模式:

  • 静默授权:一般是用于获取一些类似于用户ID的信息,比如每个用户在微信的ID被称为openid,这种ID只是用户的唯一身份认证(相当于编号),不包含个人信息,应用获取openid并不能分析出你的手机号和身份证号这些个人信息。显然,很多用户都不知道openid是什么,总不能弹个弹窗问用户“你是否同意传输openid”吧。因而这类传输,用户是无感的,用户只需访问了某个页面,后台会向微信请求拿到你的openid。
  • 手动授权:这种亦即是我们上文提到的用户场景,这类型场景需要获取的信息是你的个人信息,比如头像,昵称,手机号和地址等等。这些个人信息是必须经过用户手动点击同意的。

4. OAuth2原理及剖析

以上第2点是授权的基本简化,本节是更重点介绍OAuth2的系统链路流程(无论是静默或是手动,系统链路一致,只是形式的区分)。目前市面上涉及授权,权限申请的业务均通过OAuth2的方法进行设计。

OAuth2具体可以分为以下四种:

  1. 授权码模式(authorization code)【重点】
  2. 简化模式(implicit)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)

4.1 授权码模式

其中最重要的就是第一种授权码模式,接下来我以企业微信授权码方法做解析,其流程图非常清晰。

例子讲解:

场景:该身份授权是用户在企业微信使用第三方应用时拉起授权页面的流程。类似于你在微信打开饿了么小程序。

系统交互的步骤:

  1. 用户在企业微信打开一个A应用。此时A应用通过静默推送获取到用户的userid,发现这个用户没有头像和昵称信息在A应用的数据库。
  2. 此时,A应用调用企业微信的OAuth认证链接,这个链接要带上企业ID(表明应用方),权限获取范围(头像+昵称),标记本次授权的编号(state)和授权完跳转的地址,做好链接之后,向微信发送过去。
  3. 企业微信收到请求后,校验企业ID和授权跳转的地址是否对应。如果验证通过,企业微信会给A应用一个令牌(code),并在前端打开企业微信的授权页面(该页面由企业微信管理)。
  4. 用户点击授权了之后,企业微信可以利用code和state向企业微信请求用户信息API,获得用户token,最终获得指定用户信息。
  5. 同时用户点击授权后,企业微信关闭授权页,并跳转到A应用在第2步提供的跳转地址。

4.2 简化模式

请记住第一个模式中的第(1)步和第(2)步都需要A应用处理,简化模式就是简化了第(2)步。

以下在微信的场景仅用于举例:

  1. 用户点击应用入口之后,微信直接让用户是否同意授权(授权的内容和触发时间提前配置好),用户点击同意。
  2. 用户同意后,跳转到该应用在后台预留的地址,并且微信把访问令牌直接告诉应用。
  3. 应用利用访问令牌找微信获取用户信息,完成。

此处你有没有发现,前面授权的过程并不需要应用本身参与,这个就是比授权码模式简化的地方。但这种模式不支持用户令牌的更新,也就是用户第一次授权过期了之后,下一次又需要重新手动授权。

4.3 密码模式

这种模式很直接,相当于你把你微信的账户密码告诉饿了么,饿了么利用你的账户密码去获取信息。这种方式极其不安全,用户的账户信息随时会被外泄。

4.4 客户端模式

这种其实不属于授权,实则就是两个应用间直接进行信息传输,与用户无关。

5. 总结

  1. 授权分为静默授权和手动授权,一般出现在打开应用登录的环节,应用广泛。
  2. 授权的组件或页面必须在拥有数据的应用中,这样才能确保用户在授权页上同意协议,清晰看到传输的数据范围,以及确保用户亲手同意授权。
  3. 授权分为四个模式,其中授权码模式是应用最广泛,最重要的模式。
  4. 授权码模式亦即是A应用拼接企业参数向企业微信请求打开授权页面,获取用户授权码code,再利用code获得用户的token,最终获取用户信息。

相关阅读

API接口入门(一):读懂API接口文档

API接口入门(一):读懂API接口文档

作者:就是爱睡觉,电商和金融业行业的产品,以TO B业务为主,文章是用于记录自己在产品工作的思考和想法,希望有想法的小伙伴共同交流。

本文由 @就是爱睡觉 原创发布于人人都是产品经理。未经许可,禁止转载

题图来自Unsplash,基于CC0协议

些项目或者系统的开发,避免不了有些非业务性代码(也叫功能性代码)。这类代码一般是具有通用性特点,比如日志,权限,授权等等。作为高级工程师或者架构师,就要考虑把这些代码进行封装,方便别人使用。

常用的封装方法,一般分为三种 1:基类的方法抽象 2:工具类 3:自定义注解。今天我们说一下第三种自定义注解的方式。先简单说一下spring注解的分类,主要两种,一种是初始化类注解。例如 @Component,@Controller.....

第二种就是AOP类注解,例如@Transcation,@Cache....。

现在有个微信公众号的授权使用场景,通过code获取访问用户的openId,如果缓存存在,从缓存中获取,不存在就从微信服务器获取,之后得到用户的相应信息,头像,名称等等。因为每个页面都需要进行授权获取用户的openID,所以这个功能可以封装起来,供所有模块使用。

第一步:定义注解,参数type用来区分是静默授权还是非静默授权。

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD, ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented //说明该注解将被包含在javadoc中
public @interface AuthAnnotation {
String type();
}

第二步:定义切面类AuthAspect

@Aspect
@Component
public class AuthAspect {
@Resource
private ObjectCacheRedisDao objectCacheRedisDao;
 private BeLogger logger=new BeLogger(AuthAspect.class);
@Around("@annotation(aa)")
public Object doAround(ProceedingJoinPoint joinPoint,AuthAnnotation aa) throws Throwable {
Object object=null;
String type=aa.type();//后去注解的参数
Object [] parm=joinPoint.getArgs();//获得切面方法当中的参数
String code=String.valueOf(parm[0]);
JSONObject jsonObject=null;
JSONObject userInfo=new JSONObject();
 if (StringUtils.isEmptyString(code)) {
return userInfo;
}
String openId="";
// 獲取授權信息
if (!code.equals("authdeny") && objectCacheRedisDao.get(code)==null) {
logger.debug("objectCache没有数据。code:" + code);
 try {
jsonObject=WechatClient.getUserTokenByAuthCode(code);
 if (jsonObject==null || !jsonObject.containsKey("openid")) {
return userInfo;
} else {
openId=jsonObject.getString("openid");
userInfo.put("openid",openId);
objectCacheRedisDao.set(code, userInfo);
}
//非静默授权的场合
if("user_info".equals(type)) {
String accessToken=jsonObject.getString("access_token");
userInfo=WechatClient.getUserInfoByTokenAndOpenId(accessToken, openId);
 if (!userInfo.containsKey("errcode")) {
objectCacheRedisDao.set(code, userInfo);
}
}
} catch (Exception e) {
logger.warn("YXKJ----------------->确认课次获取用户信息异常:" + Tools.getStackStr(e));
}
} else {
logger.debug("objectCacheRedisDao有数据。code:" + code);
userInfo=(JSONObject) objectCacheRedisDao.get(code);
}
if (userInfo !=null) {
parm[1]=userInfo.toString();
} else {
parm[1]="";
}
object=joinPoint.proceed(parm);
 return object;
}
}

第三步:自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面

<aop:aspectj-autoproxy proxy-target-class="true" />

第四布:调用注解