Spring Security 用户认证和授权管理

news2024/12/27 9:59:31

文章目录

  • 一、介绍
    • 1、简介
    • 2、核心概念
    • 3、主要功能
    • 4、处理流程
  • 二、Spring Security实现权限
    • 1、添加依赖
    • 2、执行顺序和代码执行流程
      • (1)用户登录
      • (2)访问受保护资源
    • 总结
      • 1、用户登录
      • 2、访问受保护资源
  • 完整源码


一、介绍

1、简介

Spring Security 是一个强大的安全框架,旨在保护基于 Spring 的应用程序。它提供了一整套全面的安全功能,包括认证、授权、以及防护常见安全攻击的机制。

2、核心概念

  • 认证(Authentication)

    认证是验证用户身份的过程。在 Spring Security 中,用户在登录时提交凭证(如用户名和密码)。这些凭证会通过一系列过滤器进行处理和验证。如果凭证正确,系统会确认用户的身份并授予相应的权限。

  • 授权(Authorization)

    授权是确定已认证用户是否有权限执行特定操作或访问特定资源的过程。Spring Security 通过配置访问控制规则来实现授权。授权通常基于角色(Role)和权限(Authority)。例如,只有具有管理员角色的用户才能访问管理页面。

  • 过滤器(Filter)

    Spring Security 使用一系列过滤器来拦截 HTTP 请求,并在请求到达应用程序之前执行安全逻辑。每个过滤器都有特定的职责,例如处理认证、授权和会话管理。这些过滤器由 FilterChainProxy 管理,并按顺序执行,确保所有安全检查在请求处理前完成。

  • 安全上下文(Security Context)

    安全上下文包含当前用户的安全信息,包括用户的认证信息和权限。Spring Security 使用 SecurityContextHolder 来存储和访问安全上下文。每次请求到达服务器时,安全上下文会被加载并在整个请求期间保持有效,以确保所有的安全检查都基于最新的用户信息。

3、主要功能

  • 表单登录(Form-based Authentication)

    提供自定义登录页面和登录处理机制。
    处理用户提交的登录表单,并进行身份验证。

  • HTTP 基础认证(HTTP Basic Authentication)

    使用 HTTP 基础认证头(Authorization)来进行身份验证。
    常用于简单的 RESTful API 安全。

  • LDAP 支持

    集成 LDAP(轻量目录访问协议)进行用户认证和授权。

  • 方法级别安全(Method Level Security)

    使用注解(如 @PreAuthorize, @Secured)在方法级别进行权限控制。
    允许对服务层方法进行细粒度的安全控制。

  • CSRF 防护(Cross-Site Request Forgery Protection)

    防护跨站请求伪造攻击。
    默认启用,可以根据需要进行配置。

  • 会话管理(Session Management)

    控制用户会话,包括会话超时和并发会话控制。
    提供防止会话固定攻击的机制。

  • 安全事件和监听器(Security Events and Listeners)

    处理并记录安全相关的事件,如登录成功、登录失败等。
    提供监听器机制,允许开发者自定义处理这些事件。

4、处理流程

Spring Security 的处理流程主要包括以下几个步骤:

  • (1)请求进入过滤器链

    用户发起一个 HTTP 请求,首先被 Spring Security 的过滤器链拦截。

  • (2)安全上下文初始化

    SecurityContextPersistenceFilter 过滤器加载或创建一个新的安全上下文,确保请求期间的安全信息可用。

  • (3)认证过程

    例如,UsernamePasswordAuthenticationFilter 处理登录请求,提取用户名和密码,并将其传递给 AuthenticationManager 进行验证。
    AuthenticationManager 会使用一个或多个 AuthenticationProvider 验证用户凭证。如果认证成功,用户信息会存储在安全上下文中。

  • (4)授权过程

    请求经过 FilterSecurityInterceptor,该过滤器会检查用户是否有权限访问请求的资源。如果没有权限,系统会拒绝访问并返回相应的错误信息。

  • (5)会话管理

    SessionManagementFilter 负责管理用户会话,防止会话固定攻击和控制并发会话数。

  • (6)请求处理

    如果认证和授权都通过,过滤器链将请求传递给应用程序的控制器进行正常的业务处理。

  • (7)响应处理

    在响应返回之前,过滤器链会执行一些清理工作(如保存安全上下文),确保会话和安全上下文的一致性。

通过以上流程,Spring Security 确保每个 HTTP 请求都经过严格的安全检查,从而保护应用程序的安全。

二、Spring Security实现权限

Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。
在这里插入图片描述

如图所示,一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

这里面我们只需要重点关注两个过滤器即可:UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。

1、添加依赖

	 <!--   spring security    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2、执行顺序和代码执行流程

(1)用户登录

SpringSecurityConf

SpringSecurityConf 配置中,添加了自定义的过滤器 PostJsonAuthenticationFilterTokenAuthorizationFilter

/**
     * @description: TODO 定义了一系列安全策略,包括禁用 CSRF 保护、配置会话管理策略、配置白名单、添加自定义过滤器等
     *
     * @author: LLong
     * @date: 2024/03/29 17:54:41
     * @param http
     * @return: void
     **/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置了异常处理,指定了身份验证失败时的处理方式,即当用户未经身份验证访问受保护的资源时,会调用 authenticationEntryPoint 来处理
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
//                .accessDeniedHandler(accessDeniedHandler);
        //禁用了 CSRF 保护,并启用了 HTTP Basic 认证
        http.csrf().disable().httpBasic()
                //设置会话管理策略为无状态,即不创建会话
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //配置一组 URL 的白名单,这些 URL(AUTH_WHITELIST) 不需要身份验证即可访问
                .and().authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll()
                //获取标有注解 AnonymousAccess 的访问路径,不需要身份验证即可访问
                .and().authorizeRequests().antMatchers(getAnonymousUrls()).permitAll()
                .anyRequest().authenticated();
        //添加登陆过滤器,用来处理基于令牌的身份验证
        http.addFilterBefore(createTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加权限鉴定过滤器 authorizationFilter,用于进行授权验证。
        http.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class);

    }
}


	/**
     * @description: TODO 登陆过滤器
     * @author: LLong
     * @date: 2024/03/29 18:00:31
     * @param
     * @return: PostJsonAuthenticationFilter
     **/
    private PostJsonAuthenticationFilter createTokenAuthenticationFilter() throws Exception {
        // 创建PostJsonAuthenticationFilter实例,传入AuthenticationManager和ObjectMapper
        PostJsonAuthenticationFilter postJsonAuthenticationFilter =
                new PostJsonAuthenticationFilter(authenticationManagerBean(), objectMapper);

        // 设置身份验证成功处理器
        postJsonAuthenticationFilter.setAuthenticationSuccessHandler(postJsonAuthenticationSuccessHandler);

        // 设置身份验证失败处理器
        postJsonAuthenticationFilter.setAuthenticationFailureHandler(postJsonAuthenticationFailureHandler);

        return postJsonAuthenticationFilter;
    }

 	/**
     * @description: TODO 配置身份验证管理器,用于验证用户的身份
     * @author: LLong
     * @date: 2024/03/29 18:01:38
     * @param auth
     * @return: void
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 设置使用自己实现的userDetailsService(loadUserByusername)
                .userDetailsService(userDetailsService)
                // 设置密码加密方式(自定义)
                .passwordEncoder(ssha512PasswordEncoder());
    }


PostJsonAuthenticationFilter

PostJsonAuthenticationFilter 负责处理登录请求。其主要方法 attemptAuthentication 会被调用:

/**
     * @description: TODO 处理用户身份验证
     * @author: LLong
     * @date: 2024/03/29 18:16:35
     * @param request
     * @param response
     * @return: Authentication
     **/
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
          // 从请求体中读取JSON数据并映射到UserEntity对象
          AuthLoginVO authLoginVO = objectMapper.readValue(request.getInputStream(), AuthLoginVO.class);

          // 创建一个UsernamePasswordAuthenticationToken用于进行身份验证
          UsernamePasswordAuthenticationToken authenticationToken =
                  new UsernamePasswordAuthenticationToken(authLoginVO.getUsername(), authLoginVO.getPassword());

          // 进行身份验证
          return authenticationManager.authenticate(authenticationToken);
        }
    }
}

UserDetailsServiceImpl
attemptAuthentication 方法中,AuthenticationManager 调用 UserDetailsServiceImplloadUserByUsername 方法来加载用户的详细信息:(该方法是自定义的校验逻辑)

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 创建查询条件,用于根据用户名查询用户信息
    LambdaQueryWrapper<MailUserEntity> lqw = new LambdaQueryWrapper<>();
    lqw.eq(MailUserEntity::getUsername, username);

    // 使用查询条件从数据库中查询用户信息
    MailUserEntity user = mailUserMapper.selectOne(lqw);
    if (user == null) {
        // 如果用户不存在,抛出 UsernameNotFoundException 异常
        throw new UsernameNotFoundException("用户不存在");
    }

    // 获取用户的权限集合
    Collection<GrantedAuthority> authList = getAuthorities();

    // 创建自定义的 AuthLoginVO 实例,包含用户名、密码、姓名和权限集合
    AuthLoginVO authLoginVO = new AuthLoginVO(user.getUsername(), user.getPassword(), user.getName(), authList);

    // 返回包含用户信息和权限的 AuthLoginVO 对象
    return authLoginVO;
}

PostJsonAuthenticationSuccessHandler
如果登录成功,PostJsonAuthenticationFilter 将调用 PostJsonAuthenticationSuccessHandleronAuthenticationSuccess 方法:

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                    Authentication authentication) throws IOException {
    // 从 authentication 对象中获取已认证的用户信息
    Object principal = authentication.getPrincipal();
    
    // 检查 principal 是否为 AuthLoginVO 实例
    if (principal instanceof AuthLoginVO) {
        AuthLoginVO user = (AuthLoginVO) authentication.getPrincipal(); // 将 principal 强制转换为 AuthLoginVO 类型

        // 从 Redis 中获取与用户名关联的 uuid
        String uuid = redisTemplate.opsForValue().get(USER_USERNAME_PRE + user.getUsername());

        // 如果 uuid 不存在,则生成一个新的 uuid
        if (StringUtils.isEmpty(uuid)) {
            uuid = UUID.fastUUID().toString(true);
        }

        // 将用户名和 uuid 存储到 Redis 中,并设置过期时间为 12 小时
        redisTemplate.opsForValue().set(USER_USERNAME_PRE + user.getUsername(), uuid, EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);

        // 将 uuid 和用户信息存储到 Redis 中,并设置过期时间为 12 小时
        redisTemplate.opsForValue().set(USER_TOKEN_PRE + uuid, objectMapper.writeValueAsString(user), EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);

        // 设置 HTTP 响应状态为 200
        response.setStatus(200);

        // 设置响应内容类型为 JSON,字符集为 UTF-8
        response.setContentType("application/json;charset=UTF-8");

        // 创建一个新的 AuthLoginVO 对象,用于响应
        AuthLoginVO authLoginVO = new AuthLoginVO();
        authLoginVO.setId(user.getId()); // 设置用户 ID
        authLoginVO.setUsername(user.getUsername()); // 设置用户名
        authLoginVO.setToken(uuid); // 设置用户 token

        // 将用户权限信息转换为 Set 并设置到 authLoginVO 中(此代码被注释掉)
        // authLoginVO.setPermissions(AuthorityUtils.authorityListToSet(authentication.getAuthorities()));

        // 将 authLoginVO 对象转换为 JSON 并写入响应
        response.getWriter().print(objectMapper.writeValueAsString(new Result<>(authLoginVO)));

        // 刷新缓冲区,确保所有数据被写入
        response.flushBuffer();
    }
}

PostJsonAuthenticationFailureHandler
如果登录失败,PostJsonAuthenticationFilter 将调用 PostJsonAuthenticationFailureHandleronAuthenticationFailure 方法:

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                    AuthenticationException exception) throws IOException {
    // 设置 HTTP 响应状态码为 200(OK)
    response.setStatus(200);

    // 设置响应内容类型为 JSON,字符集为 UTF-8
    response.setContentType("application/json;charset=UTF-8");

    // 将登录失败的结果转换为 JSON 并写入响应
    response.getWriter().print(JSONUtil.toJsonStr(new Result<>(LOGIN_FAIL)));

    // 刷新缓冲区,确保所有数据被写入
    response.flushBuffer();
}

(2)访问受保护资源

TokenAuthorizationFilter
在请求访问受保护资源时,TokenAuthorizationFilter 会被调用:


    /**
     * @description: TODO 过滤器 基于令牌设置用户的认证信息
     * 自定义的过滤器(Filter),用于在请求中提取令牌(token),然后基于令牌设置用户的认证信息
     * @author: LLong
     * @date: 2024/03/29 18:06:38
     * @param request
     * @param response
     * @param chain
     * @return: void
     **/
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 从请求头中获取名为 "token" 的令牌
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            // 从redis中获取用户名
            String user = redisTemplate.opsForValue().get(USER_TOKEN_PRE + token);
            //从 Redis 中获取到的用户信息 JSON 字符串反序列化为 AuthLoginVO 对象
            AuthLoginVO authUser = objectMapper.readValue(user, AuthLoginVO.class);
            if (SecurityContextHolder.getContext().getAuthentication() == null && Objects.nonNull(authUser)) {
                // 创建一个 UsernamePasswordAuthenticationToken 对象,用于表示用户的认证信息。
                // 其中 authUser 是从 Redis 中获取到的用户信息对象,authUser.getAuthorities() 返回用户的权限集合
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                //将创建的认证信息对象设置到当前的安全上下文中
                SecurityContextHolder.getContext().setAuthentication(authentication);
                //刷新token
                redisTemplate.expire(USER_USERNAME_PRE + authUser.getUsername(), EXPIRE_TIME_12_HOURS,
                        TimeUnit.SECONDS);
                redisTemplate.expire(USER_TOKEN_PRE + token, EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);
            }
        }
        //调用 chain.doFilter(request, response) 继续执行过滤器链中的下一个过滤器
        chain.doFilter(request, response);
    }

TokenAuthenticationEntryPoint

如果请求没有进行身份验证或令牌无效,TokenAuthenticationEntryPoint 会被调用,返回未授权的响应:

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
                     AuthenticationException authException) throws IOException {
    // 设置 HTTP 响应状态码为 200(OK)
    response.setStatus(200);

    // 设置响应内容类型为 JSON,字符集为 UTF-8
    response.setContentType("application/json;charset=UTF-8");

    // 将未授权的结果转换为 JSON 并写入响应
    response.getWriter().print(JSONUtil.toJsonStr(new Result<>(UNAUTHORIZED)));

    // 刷新缓冲区,确保所有数据被写入
    response.flushBuffer();
}

总结

1、用户登录

  • 用户提交登录表单,PostJsonAuthenticationFilter 处理请求,提取用户名和密码,进行身份验证。
  • UserDetailsServiceImpl 加载用户详细信息,并验证用户的合法性。
  • 登录成功后,PostJsonAuthenticationSuccessHandler 生成令牌,存储到 Redis 中,并返回用户信息和令牌。
  • 登录失败后,PostJsonAuthenticationFailureHandler 返回失败消息。

2、访问受保护资源

  • 用户请求受保护资源,TokenAuthorizationFilter 验证请求中的令牌,确保用户身份有效。
  • 如果令牌无效或未提供,TokenAuthenticationEntryPoint 返回未授权消息。

完整源码

1、SpringSecurityConf

package com.cx.cxBasic.common.config.authorization;

import com.cx.cxBasic.common.config.authorization.handler.PostJsonAuthenticationFailureHandler;
import com.cx.cxBasic.common.config.authorization.handler.PostJsonAuthenticationSuccessHandler;
import com.cx.cxBasic.common.config.authorization.handler.TokenAuthenticationEntryPoint;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Author: LLong
 * @CreateTime: 2023-12-12  18:36
 * @Description: TODO Spring Security权限配置类
 * @Version: 1.0
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SpringSecurityConf extends WebSecurityConfigurerAdapter  {
    private static final String[] AUTH_WHITELIST =
            {"/login"};

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private ObjectMapper objectMapper;

    /**
     * 权限鉴定过滤器
     */
    @Resource
    private TokenAuthorizationFilter authorizationFilter;

//    /**
//     * 权限不足结果处理
//     */
//    @Resource
//    private TokenAccessDeniedHandler accessDeniedHandler;

    /**
     * 未登录结果处理
     */
    @Resource
    private TokenAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 登陆成功处理
     */
    @Resource
    private PostJsonAuthenticationSuccessHandler postJsonAuthenticationSuccessHandler;

    /**
     * 登陆失败处理
     */
    @Resource
    private PostJsonAuthenticationFailureHandler postJsonAuthenticationFailureHandler;

    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * @description: TODO 定义了一系列安全策略,包括禁用 CSRF 保护、配置会话管理策略、配置白名单、添加自定义过滤器等
     *
     * @author: LLong
     * @date: 2024/03/29 17:54:41
     * @param http
     * @return: void
     **/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置了异常处理,指定了身份验证失败时的处理方式,即当用户未经身份验证访问受保护的资源时,会调用 authenticationEntryPoint 来处理
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
//                .accessDeniedHandler(accessDeniedHandler);
        //禁用了 CSRF 保护,并启用了 HTTP Basic 认证
        http.csrf().disable().httpBasic()
                //设置会话管理策略为无状态,即不创建会话
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //配置一组 URL 的白名单,这些 URL(AUTH_WHITELIST) 不需要身份验证即可访问
                .and().authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll()
                //获取标有注解 AnonymousAccess 的访问路径,不需要身份验证即可访问
                .and().authorizeRequests().antMatchers(getAnonymousUrls()).permitAll()
                .anyRequest().authenticated();
        //添加登陆过滤器,用来处理基于令牌的身份验证
        http.addFilterBefore(createTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加权限鉴定过滤器 authorizationFilter,用于进行授权验证。
        http.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class);

    }


    /**
     * @description: TODO 登陆过滤器
     * @author: LLong
     * @date: 2024/03/29 18:00:31
     * @param
     * @return: PostJsonAuthenticationFilter
     **/
    private PostJsonAuthenticationFilter createTokenAuthenticationFilter() throws Exception {
        // 创建PostJsonAuthenticationFilter实例,传入AuthenticationManager和ObjectMapper
        PostJsonAuthenticationFilter postJsonAuthenticationFilter =
                new PostJsonAuthenticationFilter(authenticationManagerBean(), objectMapper);

        // 设置身份验证成功处理器
        postJsonAuthenticationFilter.setAuthenticationSuccessHandler(postJsonAuthenticationSuccessHandler);

        // 设置身份验证失败处理器
        postJsonAuthenticationFilter.setAuthenticationFailureHandler(postJsonAuthenticationFailureHandler);

        return postJsonAuthenticationFilter;
    }


    /**
     * @description: TODO 密码BCrypt加密
     * 密码加密器,在授权时,框架为我们解析用户名密码时,密码会通过加密器加密在进行比较 将密码加密器交给spring管理,在注册时,密码也是需要加密的,再存入数据库中 用户输入登录的密码用加密器加密,再与数据库中查询到的用户密码比较
     * @author: LLong
     * @date: 2024/03/29 18:00:38
     * @param
     * @return: BCryptPasswordEncoder 加密器
     **/
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        // 密码加密
        return new BCryptPasswordEncoder();
    }

    /**
     * @description: TODO 配置身份验证管理器,用于验证用户的身份
     * @author: LLong
     * @date: 2024/03/29 18:01:38
     * @param auth
     * @return: void
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 设置使用自己实现的userDetailsService(loadUserByusername)
                .userDetailsService(userDetailsService)
                // 设置密码加密方式
                .passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 获取标有注解 AnonymousAccess 的访问路径
     */
    /**
     * @description: TODO 获取标有注解 AnonymousAccess 的可匿名访问的路径
     * @author: LLong
     * @date: 2024/03/29 18:03:07
     * @param
     * @return: String
     **/
    private String[] getAnonymousUrls() {
        // 获取所有的 RequestMapping
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        Set<String> allAnonymousAccess = new HashSet<>();
        // 循环 RequestMapping
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod value = infoEntry.getValue();
            // 获取方法上 AnonymousAccess 类型的注解
            AnonymousAccess methodAnnotation = value.getMethodAnnotation(AnonymousAccess.class);
            // 如果方法上标注了 AnonymousAccess 注解,就获取该方法的访问全路径
            if (methodAnnotation != null) {
                allAnonymousAccess.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
        return allAnonymousAccess.toArray(new String[0]);
    }

    /**
     *  加密密码测试
     */
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
    }


}

2、PostJsonAuthenticationFilter

package com.cx.cxBasic.common.config.authorization;

import com.cx.cxBasic.common.exception.ServiceException;
import com.cx.cxBasic.entity.UserEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author: LLong
 * @CreateTime: 2023-12-12  19:14
 * @Description: TODO 登陆拦截器
 * @Version: 1.0
 */
@Slf4j
public class PostJsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper;

    private final AuthenticationManager authenticationManager;

    /**
     * @description: TODO 配置过滤器的一些参数
     * 过滤器处理的请求的 URL、请求方法,并将所需的身份验证管理器和 JSON 序列化对象传递给过滤器
     * @author: LLong
     * @date: 2024/03/29 18:13:39
     * @param authenticationManager
     * @param objectMapper
     * @return: null
     **/
    public PostJsonAuthenticationFilter(AuthenticationManager authenticationManager,
                                        ObjectMapper objectMapper) {
        super(new AntPathRequestMatcher("/login", HttpMethod.POST.toString()));
        this.authenticationManager = authenticationManager;
        this.objectMapper = objectMapper;
    }

    /**
     * @description: TODO 处理用户身份验证
     * @author: LLong
     * @date: 2024/03/29 18:16:35
     * @param request
     * @param response
     * @return: Authentication
     **/
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            // 从请求体中读取JSON数据并映射到UserEntity对象
            UserEntity users = objectMapper.readValue(request.getInputStream(), UserEntity.class);

            // 模仿usernamePasswordAuthenticationFilter的方式,创建一个usernamePasswordAuthenticationToken用于进行身份验证
            // 创建一个UsernamePasswordAuthenticationToken用于进行身份验证
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(users.getUsername(), users.getPassword());

            // 进行身份验证
            return authenticationManager.authenticate(authenticationToken);
        } catch (IOException e) {
            // 处理IO异常,例如读取请求体失败
            log.error("登录异常: {}", e.getMessage(), e);

            // 抛出usernameNotFoundException异常,表示身份验证失败
            throw new ServiceException("身份验证失败");
        }
    }
}

3、UserDetailsServiceImpl

package com.cx.cxBasic.common.config.authorization;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cx.cxBasic.entity.vamil.MailUserEntity;
import com.cx.cxBasic.mapper.vmail.MailUserMapper;
import com.cx.cxBasic.model.vo.admin.AuthLoginVO;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @Author: LLong
 * @CreateTime: 2024/06/26  20:32
 * @Description: TODO
 * @Version: 1.0
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private MailUserMapper mailUserMapper;


    /**  * 根据用户名获取用户 - 用户的角色、权限等信息   */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   // 创建查询条件,用于根据用户名查询用户信息
    LambdaQueryWrapper<MailUserEntity> lqw = new LambdaQueryWrapper<>();
    lqw.eq(MailUserEntity::getUsername, username);

    // 使用查询条件从数据库中查询用户信息
    MailUserEntity user = mailUserMapper.selectOne(lqw);
    if (user == null) {
        // 如果用户不存在,抛出 UsernameNotFoundException 异常
        throw new UsernameNotFoundException("用户不存在");
    }

    // 获取用户的权限集合
    Collection<GrantedAuthority> authList = getAuthorities();

    // 创建自定义的 AuthLoginVO 实例,包含用户名、密码、姓名和权限集合
    AuthLoginVO authLoginVO = new AuthLoginVO(user.getUsername(), user.getPassword(), user.getName(), authList);
        UserDetails userDetails = authLoginVO;

        return userDetails;
    }
    /**  * 获取用户的角色权限,为了降低实验的难度,这里去掉了根据用户名获取角色的步骤     * @param    * @return   */
    private Collection<GrantedAuthority> getAuthorities(){
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        return authList;
    }
}

4、PostJsonAuthenticationSuccessHandler

package com.cx.cxBasic.common.config.authorization.handler;

import cn.hutool.core.lang.UUID;
import com.cx.cxBasic.common.Result;
import com.cx.cxBasic.model.vo.admin.AuthLoginVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static com.cx.recordLibrary.common.constant.RedisKeyConstants.*;

/**
 * @Author: LLong
 * @CreateTime: 2023-12-14  11:50
 * @Description: TODO 登陆成功处理器
 * @Version: 1.0
 */
@Component
public class PostJsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
            // 从 authentication 对象中获取已认证的用户信息
    Object principal = authentication.getPrincipal();
    
    // 检查 principal 是否为 AuthLoginVO 实例
    if (principal instanceof AuthLoginVO) {
        AuthLoginVO user = (AuthLoginVO) authentication.getPrincipal(); // 将 principal 强制转换为 AuthLoginVO 类型

        // 从 Redis 中获取与用户名关联的 uuid
        String uuid = redisTemplate.opsForValue().get(USER_USERNAME_PRE + user.getUsername());

        // 如果 uuid 不存在,则生成一个新的 uuid
        if (StringUtils.isEmpty(uuid)) {
            uuid = UUID.fastUUID().toString(true);
        }

        // 将用户名和 uuid 存储到 Redis 中,并设置过期时间为 12 小时
        redisTemplate.opsForValue().set(USER_USERNAME_PRE + user.getUsername(), uuid, EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);

        // 将 uuid 和用户信息存储到 Redis 中,并设置过期时间为 12 小时
        redisTemplate.opsForValue().set(USER_TOKEN_PRE + uuid, objectMapper.writeValueAsString(user), EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);

        // 设置 HTTP 响应状态为 200
        response.setStatus(200);

        // 设置响应内容类型为 JSON,字符集为 UTF-8
        response.setContentType("application/json;charset=UTF-8");

        // 创建一个新的 AuthLoginVO 对象,用于响应
        AuthLoginVO authLoginVO = new AuthLoginVO();
        authLoginVO.setId(user.getId()); // 设置用户 ID
        authLoginVO.setUsername(user.getUsername()); // 设置用户名
        authLoginVO.setToken(uuid); // 设置用户 token

        // 将用户权限信息转换为 Set 并设置到 authLoginVO 中(此代码被注释掉)
        // authLoginVO.setPermissions(AuthorityUtils.authorityListToSet(authentication.getAuthorities()));

        // 将 authLoginVO 对象转换为 JSON 并写入响应
        response.getWriter().print(objectMapper.writeValueAsString(new Result<>(authLoginVO)));

        // 刷新缓冲区,确保所有数据被写入
        response.flushBuffer();
        }
    }
}

5、PostJsonAuthenticationFailureHandler

package com.cx.cxBasic.common.config.authorization.handler;

import cn.hutool.json.JSONUtil;
import com.cx.cxBasic.common.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.cx.cxBasic.common.exception.ResultCode.LOGIN_FAIL;

/**
 * @BelongsProject: cx-basic-framework
 * @Author: LLong
 * @CreateTime: 2023-12-14  11:48
 * @Description: TODO 登陆失败处理器
 * @Version: 1.0
 */
@Component
public class PostJsonAuthenticationFailureHandler implements AuthenticationFailureHandler {

   @Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                    AuthenticationException exception) throws IOException {
	    // 设置 HTTP 响应状态码为 200(OK)
	    response.setStatus(200);
	
	    // 设置响应内容类型为 JSON,字符集为 UTF-8
	    response.setContentType("application/json;charset=UTF-8");
	
	    // 将登录失败的结果转换为 JSON 并写入响应
	    response.getWriter().print(JSONUtil.toJsonStr(new Result<>(LOGIN_FAIL)));
	
	    // 刷新缓冲区,确保所有数据被写入
	    response.flushBuffer();
	}
}

6、TokenAuthorizationFilter

package com.cx.cxBasic.common.config.authorization;

import com.cx.cxBasic.model.vo.admin.AuthLoginVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.cx.cxBasic.common.constant.RedisKeyConstants.*;

/**
 * @BelongsProject: cx-record-library
 * @BelongsPackage: com.cx.recordLibrary.common.config.authorization
 * @Author: LLong
 * @CreateTime: 2023-12-12  18:40
 * @Description: TODO 请求认证拦截器
 * @Version: 1.0
 */
@Slf4j
@Component
public class TokenAuthorizationFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

 /**
     * @description: TODO 过滤器 基于令牌设置用户的认证信息
     * 自定义的过滤器(Filter),用于在请求中提取令牌(token),然后基于令牌设置用户的认证信息
     * @author: LLong
     * @date: 2024/03/29 18:06:38
     * @param request
     * @param response
     * @param chain
     * @return: void
     **/
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 从请求头中获取名为 "token" 的令牌
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            // 从redis中获取用户名
            String user = redisTemplate.opsForValue().get(USER_TOKEN_PRE + token);
            //从 Redis 中获取到的用户信息 JSON 字符串反序列化为 AuthLoginVO 对象
            AuthLoginVO authUser = objectMapper.readValue(user, AuthLoginVO.class);
            if (SecurityContextHolder.getContext().getAuthentication() == null && Objects.nonNull(authUser)) {
                // 创建一个 UsernamePasswordAuthenticationToken 对象,用于表示用户的认证信息。
                // 其中 authUser 是从 Redis 中获取到的用户信息对象,authUser.getAuthorities() 返回用户的权限集合
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                //将创建的认证信息对象设置到当前的安全上下文中
                SecurityContextHolder.getContext().setAuthentication(authentication);
                //刷新token
                redisTemplate.expire(USER_USERNAME_PRE + authUser.getUsername(), EXPIRE_TIME_12_HOURS,
                        TimeUnit.SECONDS);
                redisTemplate.expire(USER_TOKEN_PRE + token, EXPIRE_TIME_12_HOURS, TimeUnit.SECONDS);
            }
        }
        //调用 chain.doFilter(request, response) 继续执行过滤器链中的下一个过滤器
        chain.doFilter(request, response);
    }

}

7、TokenAuthenticationEntryPoint

package com.cx.cxBasic.common.config.authorization.handler;

import cn.hutool.json.JSONUtil;
import com.cx.cxBasic.common.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.cx.cxBasic.common.exception.ResultCode.UNAUTHORIZED;

/**
 * @BelongsProject: cx-basic-framework
 * @Author: LLong
 * @CreateTime: 2023-12-14  11:51
 * @Description: TODO 未登录处理器
 * @Version: 1.0
 */
@Component
public class TokenAuthenticationEntryPoint implements AuthenticationEntryPoint {

	    @Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
	                     AuthenticationException authException) throws IOException {
	    // 设置 HTTP 响应状态码为 200(OK)
	    response.setStatus(200);
	
	    // 设置响应内容类型为 JSON,字符集为 UTF-8
	    response.setContentType("application/json;charset=UTF-8");
	
	    // 将未授权的结果转换为 JSON 并写入响应
	    response.getWriter().print(JSONUtil.toJsonStr(new Result<>(UNAUTHORIZED)));
	
	    // 刷新缓冲区,确保所有数据被写入
	    response.flushBuffer();
	}
}

8、AnonymousAccess

package com.cx.cxBasic.common.config.authorization;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @BelongsProject: cx-basic-framework
 * @Author: LLong
 * @CreateTime: 2023-12-14  11:52
 * @Description: TODO
 * @Version: 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnonymousAccess {
}

9、AuthLoginVO

package com.cx.cxBasic.model.vo.admin;

import com.cx.cxBasic.common.config.authorization.CustomAuthorityDeserializer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.math.BigInteger;
import java.util.Collection;

/**

 * @Author: LLong
 * @CreateTime: 2023-12-12  16:38
 * @Description: TODO token用户登录信息
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthLoginVO implements UserDetails {
    /**
     *  用户ID 主键id
     */
    private BigInteger id;
    /**
     *  用户名
     */
    private String username ;
    /**
     *  用户密码
     */
    private String password ;
    /**
     *  角色代码
     */
    private Integer code;
    /**
     *  角色名
     */
    private String roleName ;
    /**
     *  部门名
     */
    private String deptName ;
    /**
     *  令牌
     */
    private String token ;

    @JsonDeserialize(using = CustomAuthorityDeserializer.class)
    private Collection<? extends GrantedAuthority> authorities;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }
}

10、CustomAuthorityDeserializer

package com.cx.cxBasic.common.config.authorization;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @Author: LLong
 * @CreateTime: 2023-12-13  11:19
 * @Description: TODO GrantedAuthority集合json解析
 * @Version: 1.0
 */
public class CustomAuthorityDeserializer extends JsonDeserializer<List<GrantedAuthority>> {

    @Override
    public List<GrantedAuthority> deserialize(JsonParser jp, DeserializationContext context) throws IOException {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        JsonNode jsonNode = mapper.readTree(jp);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()) {
            JsonNode next = elements.next();
            JsonNode authority = next.get("authority");
            grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
        }
        return grantedAuthorities;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2099169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot+Vue的竞赛报名系统【源码】【最详细运行文档】

SpringBootVue的竞赛报名系统 一、项目简介二、技术选型三、运行步骤1. 后端启动2. 前端启动 四、项目演示登录页管理员登录学生登录源码获取方式 总结 大家好&#xff0c;这里是程序猿代码之路。在现代教育和技术竞赛中&#xff0c;一个高效、稳定的报名系统对于赛事的组织和管…

【Java|Stream流】获取各个数据类型的Stream流

文章目录 1.Stream流介绍2.获取Stream流2.1单列集合2.2双列集合2.3数组2.4零散的数据 3.Stream.of()方法的注意事项 1.Stream流介绍 在 Java 中&#xff0c;Stream 流是一种处理集合数据的高级方式&#xff0c;可以方便地对集合进行各种操作&#xff0c;如过滤、映射、排序、聚…

python:序列1~n的立方之和==序列1~n的和之平方

pip install sympy 或者 Anaconda 3 自带 sympy 点击 【Jupyter Notebook】 这是我最喜欢的代数恒等式之一 from IPython.display import Latex Latex(r"$1^32^33^3\cdotsn^3 (123\cdotsn)^2 $") Latex(r"$\sum_{i1}^n i^3 (\sum_{i1}^n i)^2 $")# 求…

小试牛刀-SOL链创建Token代币

目录 1.编写目的 2.账户结构 3.环境及使用依赖 4.步骤分解 4.1.导入相关依赖 4.2. 初始化变量 4.3. 创建并初始化Mint Account 4.4. 创建并初始化Metadata Account 4.5. 发送创建和初始化mint Account 4.6 铸造代币 5.源码分享 Welcome to Code Blocks blog 本篇文…

视频孪生智慧监所平台,实现监管数据的统一管理和立体直观呈现

针对监所传统方式难以有效管控&#xff1b;监所视频监控相似度极高&#xff0c;难以辨识&#xff0c;工作人员劳动强度大&#xff1b;监所行业涉及的系统众多&#xff0c;缺少统一高效的管理&#xff1b;监所行业对系统应急响应能力、智慧化程度要求高等痛点问题。在智慧监所建…

24数学建模国赛及提供助力(12——存贮论)!!!!

需要资料和助攻的小伙伴们可以文章末尾获取链接&#xff01;&#xff01;&#xff01;&#xff01; 点击链接加入群聊获取资料以及助攻https://qm.qq.com/q/NGl6WD0Bky

免费作图软件推荐,六款工具助你提升设计效率

在现代设计工作中&#xff0c;合适的作图工具能极大地提高工作效率。对于设计师、学生或是爱好者来说&#xff0c;免费的作图软件无疑是一个经济实惠的选择。本文将为大家介绍 6 款免费且功能强大的作图软件&#xff0c;其中包括国内备受欢迎的免费作图软件以及 5 款优秀的国外…

多态,匿名内部类(lambda表达式),集合

多态(polymorphism) 一个演员扮演多个不同角色。可以减少if语句的使用。 概念 具有接口或者继承关系 A extends B A implement C 类型一致&#xff08;IEat&#xff09; 民间说法&#xff1a;父类的引用指向不同的子类对象(不同时刻) 产生不同结果 调用相同方法&#x…

学历不会改变命运但知识一定可以改变命运

一、知识与学历的区别 首先&#xff0c;我们需要区分“知识”与“学历”。学历通常是指一个人通过正规教育体系获得的证书或学位&#xff0c;而知识则是更为宽泛的概念&#xff0c;它包括了一个人通过各种途径获得的信息、技能和理解。学历可能只是知识的一部分&#xff0c;而…

自然语言处理系列五十二》文本分类算法》BERT模型算法原理及文本分类

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 自然语言处理系列五十二文本分类算法》BERT模型算法原理及文本分…

day7 测试知识积累

1.有一个班级表,里面有学号,姓名,学科,分数。找到语文学科分数最高的前10位的姓名(SQL) select 姓名 from 班级表 where 学科=语文 order by 分数 DESC limit 10; 2.有一张年级表,有班级,年级,学生姓名,找到这10名同学所在的班级(SQL) select class from 年级表 wher…

《python语言程序设计》第8章第12题生物信息:找出基因,生物学家使用字母A C T和G构成字符2串建模一个基因组(上)

草稿一、用单一方法遍历文本 9.1代码 genome_text TTATGTTTTAAGGATGGGGCGTTAGTTdef div_word(word_to_judge):len_num len(word_to_judge)save_word ""if len_num % 3 0:print("This word is valid")if save_word.find("ATG") "ATG&qu…

SpringBoot链路追踪②:如何集成?

首先下载Zipkin的jar包&#xff1a;Central Repository: io/zipkin/zipkin-server (maven.org) 根据自己的项目版本。我的版本分别是&#xff1a; <spring-boot.version>2.7.18</spring-boot.version> <spring-cloud.version>2021.0.8</spring-cloud.ve…

Spring理论知识(Ⅳ)——Spring Instrumentation模块

Spring的组成 Spring由20个核心依赖组成&#xff0c;这20个核心依赖可以分为6个核心模块 Spring Instrumentation模块介绍 总的来说&#xff0c;Spring Instrumentation提供了类植入&#xff08;Instrumentation&#xff09;支持和类加载器的实现&#xff0c;可以在特定…

解决报错【ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问。】

1、问题发生 用pip安装时出现报错【ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问。: c:\\programdata\\anaconda3\\lib\\site-packages\\__pycache__\\typing_extensions.cpython-39.pyc Consider using the --user option or check the perm…

多媒体信息共享|基于SprinBoot+vue的多媒体信息共享平台(源码+数据库+文档)

多媒体信息共享平台 目录 基于SprinBootvue的多媒体信息共享平台 一、前言 二、系统设计 三、系统功能设计 系统前台功能模块 后台模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌…

软考计算机软件基础知识总结

目录 前言 计算机软件概述 操作系统 数据库 文件系统 网络协议 中间件 软件构件 应用软件 最后 前言 早期的计算机软件和计算机程序 (Computer Program) 的概念几乎不加区别&#xff0c;后来计算机 软件的概念在计算机程序的基础上得到了延伸。计算机软件是指计算机系…

基于HybridCLR做的一个FlyBird Demo

周末学习了下HybridCLR的原理和用法做了个FlyBrid小demo。记录一下 官网里写的原理&#xff1a; 对于这个我的理解是&#xff1a; Unity引擎的代码使用还是AOT方式。对于项目业务这块打成多个程序集。运行时使用了解释器&#xff0c;解释执行。从而完成热更新。 一。环境安装…

MySQL5.6迁移到DM8

注意&#xff1a; MySQL 5.7 与 MySQL 8.0 的语法有所区别&#xff0c;本文档是将MySQL5.6迁移到DM8。 迁移前准备 源库 数据库信息 统计源端业务库要迁移的数据量、字符编码、归档保留等信息。 内容 说明 备注 数据库架构 单机 节点数 1 数据库版本 MySQL 5.6…

OpenGL/GLUT实践:实现反弹运动的三角形动画与键盘控制(电子科技大学信软图形与动画Ⅱ实验)

源码见GitHub&#xff1a;A-UESTCer-s-Code 文章目录 1 运行效果2 实验过程2.1 环境配置2.2 绘制三角形2.2.1 渲染函数2.2.2 主函数2.2.3 运行结果 2.3 调整窗口大小2.4 简单动画与按键控制2.4.1 简单旋转2.4.2 键盘控制 2.5 窗口反弹动画2.5.1 处理窗口大小变化2.5.2 渲染函数…