spring security 的安全之路环境准备与基础知识复习(一)

第一章:导读以及学习目标

  • 认证与授权
登录的认知:
  • 同时支持多种认证方式
  • 同时支持多种前端渠道
  • 支持集群环境、跨应用工作,Session控制、控制用户权限,防护与身份认证相关的攻击。

学习目标:

  • 可重用的、企业级的、认证和授权模块
  • 深入理解Spring Security及相关框架的原理、功能和代码。
  • 可以基于Spring Security及相关框架独立开发认证授权相关功能。
  • 掌握抽象和封装的常见技巧,可以编写可重用的模块供他人使用。
image.png

前置知识

  • JavaWeb基础
  • Maven基础
  • Spring基础

第二章、开发环境的准备

1.代码结构

  • seapp-security:主模块
  • seapp-security-core: 核心业务逻辑
  • seapp-security-browser:浏览器安全特定代码
  • seapp-security-app:app相关特定代码
  • seapp-security-demo:样例程序

parent工程pom.xml maven依赖的引入:



    4.0.0

    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.0.RELEASE
        
    

    com.seapp.security
    seapp-security
    1.0-SNAPSHOT
    
        security-core
        security-browser
        security-app
        security-demo
    
    pom

    
        1.8
        Hoxton.M3
        1.0-SNAPSHOT
    


    
        
            org.springframework.cloud
            spring-cloud-config-server
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    










    
        
            spring-milestones
            Spring Milestones
            https://repo.spring.io/milestone
        
    



第三章、使用Spring MVC开发RESTful API

学习目标:

使用Spring MVC编写Restful API
使用Spring MVC处理其它web应用常见的需求和场景
Restful API开发常用辅助框架


1.RESTful API简介

  • 用URL描述资源
  • 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果。
  • 使用json交互数据。
  • Restful只是一种风格,并不是强制的标准。

2.开发基础的增删改查接口

  • 编写针对RestfulApi的测试用例
  • 使用注解声明RestfulAPI
  • 在RestfulAPI中传递参数
2.1 常用注解
  • @RestController 标明此Controller提供了RestApi
  • @RequestMapping及其变体。映射http请求url到java方法。
  • @RequestParam 映射请求参数到java方法的参数。
    required:true,该参数必传。false,该参数可选。
    defaultValue:当该参数可选时,若没有传值,则使用默认值。
    value: 指定参数名称(当传参与形参不同时使用),等同于name属性。
  • @PageableDefault 指定分页参数默认值。
    page:页数
    size: 每页条目数
    sort:设定排序字段以及排序方式
  • @pathVariable 映射url片段到java方法的参数
  • RequestBody 映射请求体到java方法的参数
  • 日期类型参数的处理
    以时间戳为准,具体如何展示前台界面控制。

2.2 模拟前端测试入门(MockMvc )

package com.seapp.web;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
 * 测试用例
 * @author seapp
 * @date 2020/8/4 17:33
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void before(){
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void whenQuerySuccess() throws Exception {
        //模拟测试get请求
        mockMvc.perform(MockMvcRequestBuilders.get("/user")
                //请求类型设定
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                //请求状态码设定
                .andExpect(MockMvcResultMatchers.status().isOk())
                //请求返回集合长度设定(json返回数据)
               //jsonpath的使用规则:https://github.com/json-path/JsonPath
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()")
                        .value(3));
    }

}


3. 在url声明中使用正则表达式

示例如下:限制传递的id只能为数字。

    @RequestMapping(value = "/user/{id:\\d+}", method = RequestMethod.GET)
    public User getUserInfo(@PathVariable("id") String id) {
        System.out.println("id = " + id);
        User user = new User();
        user.setUsername("tom");
        return user;
    }

4. @JsonView控制json输出内容

目的:在不同需求下对获取到的同一个对象,输出不同的内容。

@JsonView的使用步骤:
  • 使用接口来声明多个视图
  • 在值对象的get方法上指定视图
package com.seapp.dto;

import com.fasterxml.jackson.annotation.JsonView;

/**
 * @author seapp
 * @date 2020/8/4 17:49
 */
public class User {

    //声明简单视图
    public interface UserSimpleView{};
    //声明详细视图,并集成简单视图
    public interface UserDetailView extends UserSimpleView{};

    private String username;
    private String password;
    //在get方法上,指定username在简单视图上展示
    @JsonView(UserSimpleView.class)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    //在get方法上,指定password在详情视图上展示
    @JsonView(UserDetailView.class)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

  • 在Controller方法上指定视图
package com.seapp.web.controller;

import com.fasterxml.jackson.annotation.JsonView;
import com.seapp.dto.User;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import java.awt.print.Pageable;
import java.lang.invoke.MethodType;
import java.net.Authenticator;
import java.util.ArrayList;
import java.util.List;

/**
 * @author seapp
 * @date 2020/8/4 17:32
 */
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * get请求,路径为"/user"
     *
     * @return
     */
//    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @GetMapping
    @JsonView(User.UserSimpleView.class)//指定返回json数据为简单视图
    public List query(@RequestParam(required = false, defaultValue = "tom", name = "username") String username) {
        System.out.println("username = " + username);
        List userList = new ArrayList<>();
        userList.add(new User());
        userList.add(new User());
        userList.add(new User());
        return userList;
    }


//    @RequestMapping(value = "/user/{id:\\d+}", method = RequestMethod.GET)
    @GetMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)//指定返回json数据为详细视图
    public User getUserInfo(@PathVariable("id") String id) {
        System.out.println("id = " + id);
        User user = new User();
        user.setUsername("tom");
        return user;
    }
}

  • 测试类的实现,以及调用
package com.seapp.web;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.lang.annotation.Target;

/**
 * 测试用例
 * @author seapp
 * @date 2020/8/4 17:33
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void before(){
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void whenQuerySuccess() throws Exception {
        //模拟测试get请求
        String string = mockMvc.perform(MockMvcRequestBuilders.get("/user")
                .param("username", "seapp")
                //请求类型设定
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                //请求状态码设定
                .andExpect(MockMvcResultMatchers.status().isOk())
                //请求返回集合长度设定(json返回数据)
                //jsonpath的使用规则:https://github.com/json-path/JsonPath
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()")
                        .value(3))
                .andReturn().getResponse().getContentAsString();
        System.out.println("string = " + string);
    }

    /**
     * 测试查询指定用户id的接口
     * @throws Exception
     */
    @Test
    public void whenGetInfoSuccess() throws Exception {
        String string = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.username")
                        .value("tom"))
                .andReturn().getResponse().getContentAsString();
        System.out.println("string = " + string);
    }

    /**
     * 正则表达式限制,当查询id不是字符串时,提示参数异常。
     * @throws Exception
     */
    @Test
    public void whenGetInfoFail() throws Exception {
        String string = mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(MockMvcResultMatchers.status().is4xxClientError())
                .andReturn().getResponse().getContentAsString();
        System.out.println("string = " + string);
    }

}

5. 校验

  • @Valid注解和BindingResult验证请求参数的合法性并处理校验结果
//注解@NotBlank 元素值不为空
 @NotBlank
 private String password;

//使用@Valid注解实行校验,并通过BindingResult对象来获取校验结果
 @PostMapping
    public User create(@RequestBody @Valid User user, BindingResult errors) {

        if (errors.hasErrors()) {
            errors.getAllErrors().stream()
                    .forEach(error -> System.out.println(error.getDefaultMessage()));
        }

        System.out.println(user.getId());
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getBirthday());
        user.setId(1);
        return user;
    }
//测试调用方法
  @Test
    public void whenCreateSuccess() throws Exception {

        //post请求的json字符串参数
        String content = "{\"username\":\"tom\",\"password\":\"\"}";
        String string = mockMvc.perform(MockMvcRequestBuilders.post("/user")
                .contentType(MediaType.APPLICATION_JSON_UTF8).content(content))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
                .andReturn().getResponse().getContentAsString();
        System.out.println("string = " + string);

    }


  • 常用的验证注解

  • 自定义校验注解
    ①注解类的实现:

package com.seapp.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**自定义校验注解
 * @author seapp
 * @date 2020/8/5 9:56
 */
@Target({ElementType.METHOD,ElementType.FIELD})//可标注在方法上或字段上
@Retention(RetentionPolicy.RUNTIME)//在运行时进行校验
@Constraint(validatedBy = MyConstraintValidator.class)//指定校验逻辑所在的类
public @interface MyConstraint {

    String message();

    Class[] groups() default {};

    Class[] payload() default {};
}

②校验规则类的实现:

package com.seapp.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 自定义校验注解的校验逻辑
 * @author seapp
 * @date 2020/8/5 10:00
 *
 * 两个泛型:
 *  第一个:指定自己定义的注解
 *  第二个:指定校验注解可以注解在什么类型的参数上。
 */
public class MyConstraintValidator implements ConstraintValidator {

    /**
     *注意在该类中,可通过@Autowired注解可以注入任意bean类。
     */


    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        //valid进行初始化
        System.out.println("myvalid is init");
    }

    /**
     *
     * @param value
     * @param constraintValidatorContext
     * @return   true:校验通过,false:校验失败
     */
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        //实行校验
        System.out.println(value);
        return false;
    }
}

③校验结果的获取

 @PutMapping("/{id:\\d+}")
    public User update(@RequestBody @Valid User user, BindingResult errors) {

        if (errors.hasErrors()) {
            errors.getAllErrors().stream()
                    .forEach(error -> {
                        //获取校验字段名称
                        FieldError fieldError = (FieldError) error;
                        //拼接校验结果展示字符串
                        String message = fieldError.getField() + ":" + error.getDefaultMessage();
                        System.out.println("message = " + message);

                        System.out.println(error.getDefaultMessage());
                    });
        }

        System.out.println(user.getId());
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getBirthday());
        user.setId(1);
        return user;
    }

第四章 RESTful API错误处理

学习目标:

  • Spring Boot中默认的错误处理机制
  • 自定义异常处理

1. Spring Boot对异常的处理机制

//根据发出请求头中是否包含html/text来确定返回数据类型为ModelAndView,还是ResponseEntity转换的json数据。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController
...
@Override
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map model = Collections
                .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity>(status);
        }
        Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        return new ResponseEntity<>(body, status);
    }
...

2. 自定义异常,以及异常处理器的配置

  • 自定义异常:
package com.seapp.exception;

/**自定义运行时异常
 * @author seapp
 * @date 2020/8/5 14:34
 */
public class UserNotExistException extends RuntimeException {

    private String id;

    public UserNotExistException(String message, String id) {
        super(message);
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

  • 异常处理器的配置
package com.seapp.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * @author seapp
 * @date 2020/8/5 14:35
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(UserNotExistException.class)//绑定指定异常类
    @ResponseBody//将返回数据类型转换为json数据格式
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//指定返回状态码
    public Map handlerUserNotExistException(UserNotExistException ex){
        Map result = new HashMap<>();
        result.put("id",ex.getId());
        result.put("message",ex.getMessage());
        return result;
    }

}

3. RESTful API的拦截

  • 过滤器(Filter)
    ①自定义过滤器
package com.seapp.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

/**
 * @author seapp
 * @date 2020/8/5 14:56
 */
@Component
public class TimeFilter implements Filter {

    /**
     * 过滤器初始化方法
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("tiem filter init");
    }

    /**
     * 过滤器执行方法
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("time filter start");
        long start = new Date().getTime();
        filterChain.doFilter(servletRequest,servletResponse);//执行下一个拦截器
        System.out.println("time filter:" + (new Date().getTime() - start));
        System.out.println("time filter finish");
    }

    /**
     * 过滤器销毁
     */
    @Override
    public void destroy() {
        System.out.println("time filter destory");
    }
}

②过滤器配置生效
方式一:
直接在拦截器类上添加@Component注解,注入Spring 容器即可。
方式二:
通过@Configuration Java代码注入的方式实现,这种方式可控制过滤指定的url访问路径。

 package com.seapp.config;

import com.seapp.web.filter.TimeFilter;
import com.seapp.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author seapp
 * @date 2020/8/5 15:40
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    /**
     * 配置拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }

    /**
     * 通过Java配置过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();

        //将指定的filter加入到bean中。
        TimeFilter timeFilter = new TimeFilter();
        registrationBean.setFilter(timeFilter);

        //指定该过滤器对那些url访问路径生效
        List urls = new ArrayList<>();
        urls.add("/*");//过滤所有的url路径
        registrationBean.setUrlPatterns(urls);

        return registrationBean;
    }

}

  • 拦截器(Interceptor)
    ①:自定义拦截器
package com.seapp.web.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.invoke.MethodHandle;
import java.util.Date;

/**自定义拦截器
 * @author seapp
 * @date 2020/8/5 15:49
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {

    /**
     * 方法调用之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response
            , Object handler) throws Exception {
        System.out.println("timeInterceptor: preHandle" );

        //设定拦截器开始时间
        request.setAttribute("startTime",new Date().getTime());
        //获取执行方法的类名和方法名
        String className = ((HandlerMethod) handler).getBean().getClass().getName();
        System.out.println("className = " + className);
        String methodName = ((HandlerMethod) handler).getMethod().getName();
        System.out.println("methodName = " + methodName);

        return true;
    }

    /**
     * 控制器方法正常执行之后,执行该方法
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response
            , Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("timeInterceptor: postHandle" );
        long startTime = (long)request.getAttribute("startTime");
        System.out.println("postHandle 耗时:" +  (new Date().getTime() - startTime));
    }

    /**
     * 无论控制器方法是否执行,该方法都会被执行。
     * 若执行的方法发生异常,则Exception对象中有值。
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response
            , Object handler, Exception ex) throws Exception {
        System.out.println("timeInterceptor: afterCompletion" );
        System.out.println("exception:" + ex);
        long startTime = (long)request.getAttribute("startTime");
        System.out.println("postHandle 耗时:" +  (new Date().getTime() - startTime));
    }
}

②:拦截器的配置

package com.seapp.config;

import com.seapp.web.filter.TimeFilter;
import com.seapp.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author seapp
 * @date 2020/8/5 15:40
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    /**
     * 配置拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }

    /**
     * 通过Java配置过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();

        //将指定的filter加入到bean中。
        TimeFilter timeFilter = new TimeFilter();
        registrationBean.setFilter(timeFilter);

        //指定该过滤器对那些url访问路径生效
        List urls = new ArrayList<>();
        urls.add("/*");//过滤所有的url路径
        registrationBean.setUrlPatterns(urls);

        return registrationBean;
    }
}
  • 切片(Aspect )


①定义切片,设定切入点。增强方法中获取请求参数

package com.seapp.web.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/** 使用AOP方式,实现在方法执行之前对请求参数的获取。
 * @author seapp
 * @date 2020/8/5 16:39
 */
@Aspect
@Component
public class TimeAspect {

    /**
     * @Before  方法执行之前执行
     * @After   方法执行之后执行
     * @Around  方法前后都执行
     */

    @Around("execution(* com.seapp.web.controller.UserController.*(..))")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("aspect is start");

        //在方法执行之前,获取方法参数
        Object[] args = pjp.getArgs();
        for (Object arg :
                args) {
            System.out.println("arg = " + arg);
        }

        //执行控制器方法,返回值为被调用方法的执行结果
        Object obj = pjp.proceed();

        System.out.println("aspect is finish");

        return obj;
    }
}

4. Filter(过滤器)、Interceptor(拦截器)、Aspect(切片)三者的执行顺序

你可能感兴趣的:(spring security 的安全之路环境准备与基础知识复习(一))