Spring Boot强化

Spring Boot

分布式开发:

创建空的Spring Boot项目命名ChongStudy-serve,删除``src文件夹仅保留pom.xml`文件,此为父类项目,用于聚合子类项目。

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.9</version>
        <relativePath/>
    </parent>
    <groupId>com.chong</groupId>
    <artifactId>ChongStudy-serve</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ChongStudy-serve</name>
    <description>聚合服务</description>
    <packaging>pom</packaging>

 <!-- modules用于绑定子类项目-->
    <modules>
        <module>chongStudy-article</module>
        <module>chongStudy-member</module>
        <module>chongStudy-common</module>
    </modules>

分别创建Spring Boot项目

  • chongStudy-common:为公共依赖模块,该模块一般将其他子类项目中公共引用的工具类、依赖、bean放入该模块。

  • chongStudy-member:为子类项目,一般为开发中的一个功能模块。

 <!-- 通过在dependencies中加入依赖来引入公共模块--> 
		<dependency>
            <groupId>com.chong</groupId>
            <artifactId>chongStudy-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  • chongStudy-article:为子类项目,一般为开发中的一个功能模块,与chongStudy-member操作相同。

AOP

非注解类型:

  • 添加AOP依赖。
    <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  • 创建MyAop.java
/**
 * @author wangchong
 * @create 2023-04-21-20:16
 */
@Aspect
@Component
public class MyAop {

//    定义切入点,这里是任意项目总的com.chong中的任意controller实现aop
    @Pointcut("execution(* com.chong.*.controller.*.*(..))")
    public void log(){
        System.out.println("切入点签名");
    }

//    前置通知:在被切入方法执行之前执行
    @Before("log()")
    public void doBefore(JoinPoint jp) throws Throwable{
        //接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求内容
        System.out.println("URL:"+request.getRequestURI().toString());
        System.out.println("HTTP_METHOD:"+request.getMethod());
        System.out.println("CLASS_METHOD"+jp);
    }

//  后置通知:在被切入方法执行之后执行
    @After("log()")
    public void doAfter(JoinPoint jp) throws Throwable{
        System.out.println("后置通知:返回joinPoint内容"+jp.toString());
    }

//  返回通知:在被切入方法返回结果之后执行
    @AfterReturning(returning="ret",pointcut="log()")
    public void doAfterReturning(Object ret) throws Throwable{
        System.out.println("返回通知:返回值为"+ret);
    }

//  异常通知:在被切入方法抛出异常之后执行
    @AfterThrowing(throwing="ex",pointcut="log()")
    public void doAfterThrowing(JoinPoint jp, Exception ex) throws Throwable{
        System.out.println("异常通知:方法异常执行");
        System.out.println("产生异常的方法"+jp);
        System.out.println("异常种类"+ex);
    }

//  环绕通知:围绕着被切入方法执行,参数必须为ProceedingJoinPoint
    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        System.out.println(request.getRequestURI());
        System.out.println(request.getRemoteAddr());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
        //得到方法执行所需的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("传入方法的参数"+args);
        //调用方法:proceed()
        System.out.println("执行方法前");
        Object ret = joinPoint.proceed(args);
        System.out.println("执行方法后");
        System.out.println("执行完方法的返回值"+ret);
        return ret;
    }
}
  • 注意以上ServletRequestAttributes作用:用来读取调用接口用户的信息,需要用到以下依赖:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

注解类型:

  • 创建annotation包并在包内创建MyLogAnnotation.java接口。
/**
 * @author wangchong
 * @create 2023-04-21-21:33
 */
//表示此注解可以标注在类和方法上
@Target({ElementType.METHOD,ElementType.TYPE})
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLogAnnotation {
    //定义一个变量,可以接收参数
    String desc() default " ";
}
  • HelloController.java类中添加方法
    @RequestMapping("helloAnnotation")
//    表有这个注解分方法会被增强
    @MyLogAnnotation(desc = "测试注解类型:helloAnnotation")
    public Object helloAnnotation(){
        return "hello annotation";
    }
  • aop包下创建MyAopAnnotation.java
/**
 * @author wangchong
 * @create 2023-04-21-21:32
 */
@Aspect
@Component
public class MyAopAnnotation {
    //    定义切入点,这里绑定自定义的注解
    @Pointcut(value = "@annotation(com.chong.common.annotation.MyLogAnnotation)")
    public void logAnnotation(){
        System.out.println("切入点签名");
    }

    //    前置通知:在被切入方法执行之前执行,注解中加入@annotation(myLogAnnotation),且参数中加入MyLogAnnotation myLogAnnotation
    @Before("logAnnotation() && @annotation(myLogAnnotation)")
    public void doBefore(JoinPoint jp,MyLogAnnotation myLogAnnotation) throws Throwable{
        System.out.println("doBefore=======>"+myLogAnnotation.desc());
    }

    //  后置通知:在被切入方法执行之后执行
    @After("logAnnotation() && @annotation(myLogAnnotation)")
    public void doAfter(JoinPoint jp,MyLogAnnotation myLogAnnotation) throws Throwable{
        System.out.println("doAfter=======>"+myLogAnnotation.desc());
    }

    //  返回通知:在被切入方法返回结果之后执行
    @AfterReturning(returning="ret",pointcut="logAnnotation() && @annotation(myLogAnnotation)")
    public void doAfterReturning(Object ret,MyLogAnnotation myLogAnnotation) throws Throwable{
        System.out.println("doAfterReturning=======>"+myLogAnnotation.desc());
    }

    //  异常通知:在被切入方法抛出异常之后执行
    @AfterThrowing(throwing="ex",pointcut="logAnnotation() && @annotation(myLogAnnotation)")
    public void doAfterThrowing(JoinPoint jp, Exception ex,MyLogAnnotation myLogAnnotation) throws Throwable{
        System.out.println("doAfterThrowing=======>"+myLogAnnotation.desc());
    }

    //  环绕通知:围绕着被切入方法执行,参数必须为ProceedingJoinPoint
    @Around("logAnnotation() && @annotation(myLogAnnotation)")
    public Object doAround(ProceedingJoinPoint joinPoint,MyLogAnnotation myLogAnnotation) throws Throwable{
        //得到方法执行所需的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("传入方法的参数"+args);
        //调用方法:proceed()
        System.out.println("执行方法前");
        Object ret = joinPoint.proceed(args);
        System.out.println("执行方法后");
        System.out.println("执行完方法的返回值"+ret);
        System.out.println("doAround=======>"+myLogAnnotation.desc());
        return ret;
    }
}

Handler拦截器:

  • 创建类并实现HandlerInterceptor接口,其中有三个方法需要重现分别为preHandle()afterCompletion()以及postHandle(),分别对应前拦截、后拦截以及进入ModelAndView后和返回前拦截。
public class MyHandlerInterceptor implements HandlerInterceptor {
    
    public static Logger logger = (Logger) LoggerFactory.getLogger(MyHandlerInterceptor.class);

    /**
     * 前拦截,在进入ModelAndView之前拦截
     * 使用场景:身份校验,身份授权
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("preHandle:前拦截");
        return true;
    }


    /**
     * 后拦截,该方法在handler方法执行完之后执行
     * 使用场景有:日志记录,管理,异常统一处理等等
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("afterCompletion:后拦截");
    }

    /**
     * 在进入ModelView后返回之前
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("postHandle:post拦截");
    }
}
  • 创建配置类并实现WebMvcConfigurer接口,重现addInterceptors()方法,需加上配置类注解
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 添加拦截器以及添加拦截路径
         */
        registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**");
    }
}

Filter过滤器:

img

  • 过滤器实现方案有两种,一种是基于serlvet的注解@WebFilter进行配置,另一种是使用Spring Boot提供的FilterRegistrationBean注册自定义过滤器,由于该项目是使用Spring Boot开发所以使用后者。
  • 创建filter包,并在包中创建MyFilter.java实现Filter接口,代码如下:
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("-----您已进入过滤器-----");
        
//        传给下一个过滤器
       filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
  • config包下添加FilterConfig.java文件,代码如下:
@Configuration
public class FilterConfig {

    @Bean
//	过滤器1
    public FilterRegistrationBean myFilter1(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());

        //设置过滤器执行次序
        filterRegistrationBean.setOrder(1);
        //设置过滤器名称
        filterRegistrationBean.setName("myFilter");
        //配置过滤器规则,请求"/helloAop"时进行过滤
        filterRegistrationBean.addUrlPatterns("/helloAop");
        //配置多个过滤规则
//        Collection<String> urlPatterns = new ArrayList();
//        urlPatterns.add("/helloError");
//        urlPatterns.add("/helloAnnotation");
//        filterRegistrationBean.setUrlPatterns(urlPatterns);
        return filterRegistrationBean;
    }
//    @Bean
//	  过滤器2
//    public FilterRegistrationBean myFilter2(){
//        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter2());
//
//        //添加过滤器
//       filterRegistrationBean.addUrlPatterns("/helloAop");
//        //......
//        return filterRegistrationBean;
//    }
}

拦截器和过滤器的区别:

  1. Filter是基于函数回调doFilter(),而拦截器是基于AOP思想的。
  2. Filter只在Servlet前后起作用,而拦截器能够深入到方法前后、异常抛出前后等。
  3. 依赖于Servlet容器即Web应用中,而拦截器不依赖与Servlet容器所以可以运行多个环境。
  4. 在接口调用的生命周期里,拦截器可以多次调用,而Filter只能在容器初始化时初始化一次,每次请求时创建新的action,一次请求只能调用一次Filter
  5. Filter和拦截器的执行顺序:过滤前->拦截前->action执行->拦截后->过滤后。

Listener监听器

监听器:当某个事件触发,就会执行方法块。

使用场景:

  1. 监听Servlet上下文用来初始化一些数据。
  2. 监听HTTP Session用来获取当前在线人数。
  3. 监听客户端请求的ServletRequest对象来获取用户访问信息等等。

监听器接口:

  1. ApplicationListener,监听全局。
  2. SmartApplicationListener,是ApplicationListener的子类,功能强于ApplicationListener

ApplicationListener

需求:在Spring容器初始化完成之后开始监听,并打印日志。

实现:

@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {

    public static Logger logger = LoggerFactory.getLogger(MyFilter.class);
    private static boolean isFlag = false;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isFlag){
            isFlag = true;
            logger.info("我已监听到了");
        }
    }
}

自定义监听器需要实现ApplicationListener<ContextRefreshedEvent>接口。

ContextRefreshedEvent是一个事件,会在Spring容器初始化完成后触发,所以监听器会在Spring容器初始化完成之后开始监听,所以这就是所谓的全局监听。

isFlag是一个启动标志,因为web应用会出现父子容器,所以会触发两次监听器,需要一个标志,保证任务执行一次。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1glXYnMG-1683168740355)(C:\Users\wangchong\AppData\Roaming\Typora\typora-user-images\1682126409451.png)]

监听Servlet上下文对象

  • 监听 servlet 上下文对象可以用来初始化数据,用于缓存。
  • 创建实体类User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    private Integer uid;
    private String username;
    private String pwd;

}
  • 创建serviceuserService.java,模拟数据库查询数据。
@Service
public class UserService {
    /**
     * 获取用户信息
     * @return
     */
    public User getUser(){
        //实际业务场景下,会从数据库中获取数据
        return new User(10001,"张三","123456");
    }
}
  • 然后写一个监听器,实现 ApplicationListener<ContextRefreshedEvent> 接口,重写 onApplicationEvent 方法,将 ContextRefreshedEvent 对象传进去。如果我们想在加载或刷新应用上下文时,也重新刷新下我们预加载的资源,就可以通过监听 ContextRefreshedEvent 来做这样的事情。
@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //首先获去application上下文
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        //获取对应的service
        UserService userService = applicationContext.getBean(UserService.class);
        User user = userService.getUser();
        //获取application的域对象,将查到的信息放到application域中
        ServletContext application = applicationContext.getBean(ServletContext.class);
        application.setAttribute("user",user);
    }
}
  • 首先通过 contextRefreshedEvent 来获取 application 上下文,再通过 application 上下文来获取 UserService 这个 bean,项目中可以根据实际业务场景,也可以获取其他的 bean,然后再调用自己的业务代码获取相应的数据,最后存储到 application 域中,这样前端在请求相应数据的时候,我们就可以直接从 application 域中获取信息,减少数据库的压力。下面写一个 Controller 直接从 application 域中获取 user 信息来测试一下。
@RestController
@RequestMapping("/listener")
public class UserController {
    @GetMapping("/user")
    public User getUser(HttpServletRequest request){
        ServletContext application = request.getServletContext();
        User user = (User) application.getAttribute("user");
        return user;
    }
}

监听Http会话Session对象

  • 监听器还有一个比较常用的地方就是用来监听 session 对象,来获取在线用户数量,现在有很多开发者都有自己的网站,监听 session 来获取当前在下用户数量是个很常见的使用场景。
@Component
public class MyHttpSessionListener implements HttpSessionListener {

    //日志记录
    private static final Logger logger = LoggerFactory.getLogger(MyHttpSessionListener.class);

    //记录用户在线的数量
    public Integer count = 0;

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        logger.info("=========新用户上线了========");
        count++;
        httpSessionEvent.getSession().getServletContext().setAttribute("count",count);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        logger.info("==========用户下线了=========");
        count--;
        httpSessionEvent.getSession().getServletContext().setAttribute("count",count);
    }
  • Controller 中是直接获取当前 session 中的用户数量,启动服务器,在浏览器中输入 localhost:8080/listener/total 可以看到返回的结果是1,再打开一个浏览器,请求相同的地址可以看到 count 是 2 ,这没有问题。
@RestController
@RequestMapping("/listener")
public class HttpSessionListenerController {

    @GetMapping("/total")
    public String getTotalUser(HttpServletRequest request){
        Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
        return "当前在线人数:"+count;
    }
}
  • 如果关闭一个浏览器再打开,理论上应该还是2,但是实际测试却是 3。原因是 session 销毁的方法没有执行(可以在后台控制台观察日志打印情况),当重新打开时,服务器找不到用户原来的 session,于是又重新创建了一个 session,解决方法:处理逻辑是让服务器记得原来那个 session,即把原来的 sessionId 记录在浏览器中,下次再打开时,把这个 sessionId 传过去,这样服务器就不会重新再创建了。重启一下服务器,在浏览器中再次测试一下,即可避免上面的问题。我们可以将上面的 Controller 方法改造一下:
@RestController
@RequestMapping("/listener")
public class HttpSessionListenerController {

    @GetMapping("/total")
    public String getTotalUser(HttpServletRequest request, HttpServletResponse response){
        Cookie cookie;
        try {
            cookie = new Cookie("JSESSIONID", URLEncoder.encode(request.getSession().getId(),"utf-8"));
            //设置Cookie有效期
            cookie.setMaxAge(2*24*60*60);
            response.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
        return "当前在线人数:"+count;
    }
}

监听客户端请求Servlet Request对象

@Component
public class MyServletRequestListener implements ServletRequestListener {

    private static final Logger logger = LoggerFactory.getLogger(MyServletRequestListener.class);

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequestEvent.getServletContext();
        logger.info("sessionId 为:",httpServletRequest.getRequestedSessionId());
        logger.info("request url为",httpServletRequest.getRequestURL());
        httpServletRequest.setAttribute("name","张三");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        logger.info("request end");
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequestEvent.getServletContext();
        logger.info("request域中保存的name值为:",httpServletRequest.getAttribute("name"));
    }
}

  • Controller层
@RestController
@RequestMapping("/listener")
public class ServletRequestListenerController {
    @GetMapping("/request")
    public String getRequestInfo(HttpServletRequest httpServletRequest){
        System.out.println("requestListener中的初始化数据:"+httpServletRequest.getAttribute("name"));
        return "成功";
    }
}

Springboot中自定义监听事件

自定义监听器的实现步骤:

第一步:自定义事件。创建自定义事件类,继承 ApplicationEvent 对象

第二步:自定义监听器。创建自定义监听器类监听自定义事件,需要实现ApplicationListener 接口,重写onApplicationEvent 方法

第三步:设定事件触发类,使用 applicationContext.publishEvent(event)手动触发监听事件

  • 自定义事件,需要继承 ApplicationEvent 对象,在事件中定义一个 User 对象来模拟数据,构造方法中将 User 对象传进来初始化。如下:
//自定义事件
public class MyEvent extends ApplicationEvent {
    //模拟数据
    private User user;
    public MyEvent(Object source,User user) {
        super(source);
        this.user  = user;
    }

    /**
     * @description 获取事件中用户信息
     * @return User
     */
    public User getUser(){
        return user;
    }
}
  • 自定义监听器,来监听上面定义的 MyEvent 事件,自定义监听器需要实现 ApplicationListener 接口即可。如下:
//自定义监听器
@Component
public class MyDiyListener implements ApplicationListener<MyEvent> {
    
    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        //获取事件的信息
        User user = myEvent.getUser();
        System.out.println("用户名:"+user.getUsername());
        System.out.println("密码:"+user.getPwd());
    }
}
  • 需要手动发布事件,这样监听器才能监听到,这需要根据实际业务场景来触发,针对本文的例子,我写个触发逻辑,如下:
@Service
public class UserService {

    @Resource
    private ApplicationContext applicationContext;

    /**
     * 获取用户信息
     * @return
     */
    public User getUser(){
        //实际业务场景下,会从数据库中获取数据
        return new User(10001,"张三","123456");
    }

    /**
     * 发布事件
     * @return
     */
    public User getUser2(){
        User user = new User(10002, "李四", "147258");
        //发布事件
        MyEvent myEvent = new MyEvent(this,user);
        applicationContext.publishEvent(myEvent);
        return user;
    }
}
  • 在 service 中注入 ApplicationContext,在业务代码处理完之后,通过 ApplicationContext 对象手动发布 MyEvent 事件,这样我们自定义的监听器就能监听到,然后处理监听器中写好的业务逻辑。 最后,在 Controller 中写一个接口来测试一下:
@RestController
@RequestMapping("/listener")
public class GetRequestInfo {

	@Autowired
	private UserService userService;
	
    @GetMapping("/diy")
    public String getInfo(HttpServletRequest httpServletRequest){
        User user2 = userService.getUser2();
        System.out.println("信息为:"+user2.getUsername());
        return "success";
    }
}

文件的上传与下载:

文件上传
  • 创建Controller层代码,代码如下:
@PostMapping("/upload")
    public Map<String,Object> upload(MultipartFile file, HttpServletRequest req) {
        Map<String, Object> result = new HashMap<>();
        String originName = file.getOriginalFilename();
//        如果名字不是以md结尾,返回报错
        if(!originName.endsWith(".md")){
            result.put("status","error");
            result.put("msg","文件类型不对");
            return result;
        }

//        保存文件路径,临时路径
        String format = sdf.format(new Date());
        String realPath = req.getServletContext().getRealPath("/")+ format;
        File folder = new File(realPath);
        if (!folder.exists()){
            folder.mkdirs();
        }
//       文件名可能重名,搞一个新的名字
        String newName = UUID.randomUUID().toString()+".md";
        try {
            file.transferTo(new File(folder,newName));
            String url = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort()+format+newName;
            result.put("status","success");
            result.put("url",url);
        } catch (IOException e) {
            result.put("status","error");
            result.put("msg",e.getMessage());
        }
        return result;
    }
  • PostMan测试:首先编写好post请求的端口,然后使用Body中的form-data,注意KEY后选File如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaNLxBf3-1683168740356)(C:\Users\wangchong\AppData\Roaming\Typora\typora-user-images\1682173585859.png)]

  • 前端Vue,代码如下:
template>
  <div class="home">
    <el-upload
      action="http://localhost:8888/fileOperationController/upload"
      :on-preview="handlePreview"
      :limit="10"
      accept=".md"
    >
      <el-button size="small" type="primary">点击上传</el-button>
      <div slot="tip" class="el-upload__tip">
        只能上传md文件,且不超过500kb
      </div>
    </el-upload>
  </div>
</template>

<script>
export default {
  data() {
    return {
      fileList: [],
    };
  },
  methods: {
    handlePreview(file) {
      console.log(file);
      window.open(file.response.url)
    },
    
  },
};
</script>

Spring Sevurity

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
  • ExceptionTranslationFilter: 处理过滤器链中抛出的任何AccessDeniedExceptionAuthenticationException
  • FilterSecurityInterceptor: 负责权限校验的过滤器。
  • Spring Security完整流程如下图:

过滤器链

  • Spring Security过滤器链中有哪些过滤器及它们的顺序,如下图:

过滤器的顺序

添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-startersecurity</artifactId>
    </dependency>

Spring Security的两个主要目标是“认证”和“授权(访问控制)”

  • “认证”(Authentication):验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

  • “授权”(Authorization):经过认证后判断当前用户是否有权限进行某个操作

认证:

登陆校验流程

认证流程详解

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
  • AuthenticationManager接口:定义了认证Authentication的方法
  • UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

登录

  1. 自定义登录接口,调用ProviderManager的方法进行认证 如果认证通过生成jwt,把用户信息存入redis中。
  2. 自定义UserDetailsService,在这个实现类中去查询数据库。

校验:

  1. 定义jwt认证
  2. 过滤器获取token解析token获取其中的userid
  3. redis中获取用户信息,存入SecurityContextHolder

实现:

①添加依赖

        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

②添加Redis配置

  • FastJsonRedisSerializer<T>:Redis使用FastJson序列化,将对象转换为可用于存储的数据流。
  • RedisConfig:配置Redis

③响应类

  • ResponseResult<T>:将数据进行包装。

④ 工具类

  • JWT工具类:生成token等等。
  • RedisCacheRedis缓存类
  • WebUtil:将字符串渲染到客户端

⑤ 创建实例类,Dao层,进行数据库查询等操作。

⑥ 创建一个类实现UserDetailsService接口,重写其中的方法。

⑦ 因为UserDetailsService方法的返回值是UserDetails类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。

⑧ 添加Security配置信息

  • SecurityConfig
com.alibaba fastjson 1.2.33 io.jsonwebtoken jjwt 0.9.0 ```

②添加Redis配置

  • FastJsonRedisSerializer<T>:Redis使用FastJson序列化,将对象转换为可用于存储的数据流。
  • RedisConfig:配置Redis

③响应类

  • ResponseResult<T>:将数据进行包装。

④ 工具类

  • JWT工具类:生成token等等。
  • RedisCacheRedis缓存类
  • WebUtil:将字符串渲染到客户端

⑤ 创建实例类,Dao层,进行数据库查询等操作。

⑥ 创建一个类实现UserDetailsService接口,重写其中的方法。

⑦ 因为UserDetailsService方法的返回值是UserDetails类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。

⑧ 添加Security配置信息

  • SecurityConfig

授权:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值