谷粒学院——第二十一章、spring security详细

news2024/11/27 6:25:34

一、Spring Security介绍

1、Spring Security简介

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个核心功能是“认证”和“授权”,一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是 SpringSecurity 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
通俗点讲就是系统判断用户是否有权限去做某些事情。

2、同款产品对比

2.1、Spring Security

Spring 技术栈的组成部分。
https://spring.io/projects/spring-security
通过提供完整可扩展的认证和授权支持保护你的应用程序。
SpringSecurity 特点:
⚫ 和 Spring 无缝整合。
⚫ 全面的权限控制。
⚫ 专门为 Web 开发而设计。
◼旧版本不能脱离 Web 环境使用。
◼新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
⚫ 重量级。

2.2、 Shiro

Apache 旗下的轻量级权限控制框架。
特点:
⚫ 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
的互联网应用有更好表现。
⚫ 通用性。
◼好处:不局限于 Web 环境,可以脱离 Web 环境使用。
◼缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。
Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。
自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
因此,一般来说,常见的安全管理技术栈的组合是这样的:
• SSM + Shiro
• Spring Boot/Spring Cloud + Spring Security
以上只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的

三、Spring Security实现权限

要对Web资源进行保护,最好的办法莫过于Filter
要想对方法调用进行保护,最好的办法莫过于AOP。
Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。
image.png
如图所示,一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。
这里面我们只需要重点关注两个过滤器即可:UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。
说明:Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security!这个框架的使用方式就是对这些过滤器和组件进行扩展。

1、Spring Security入门

我们在现有的项目基础上做集成,Spring Security权限控制部分也是公共模块,后续哪个service服务模块需要,直接引入即可。
后续我们的Spring Cloud微服务项目可能就基于该权限系统开发,因此我们要做好技术扩展。

1.1、创建spring-security模块

在common模块下创建spring_security公共模块,创建方式如:service_util模块

1.2、添加依赖

redis后面我们会用到

<!-- Spring Security依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1.3、添加Redis配置文件

spring:
  redis:
    host: 192.168.152.100
    port: 6379
    database: 0
    timeout: 1800000
    password:
    jedis:
      pool:
        max-active: 20 #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 5    #最大空闲
        min-idle: 0     #最小空闲

1.4、添加配置类

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

1.5、service-system模块引入

在service_acl引入权限模块,将依赖添加到pom.mxl文件

<dependency>
  <groupId>com.atguigu</groupId>
  <artifactId>spring_security</artifactId>
  <version>1.0</version>
</dependency>

2、用户认证

用户认证流程:
image.png

2.1、用户认证核心组件

我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念:当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的,这个不难理解,要是当前登录用户都不能确认了,那A下了一个订单,下到了B的账户上这不就乱套了。这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。
我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取Authentication,SecurityContext就是我们的上下文对象!这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolder原理非常简单,就是使用ThreadLocal来保证一个线程中传递同一个对象!
现在我们已经知道了Spring Security中三个核心组件:
1、Authentication:存储了认证信息,代表当前登录用户
2、SeucirtyContext:上下文对象,用来获取Authentication
3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext
Authentication中是什么信息呢:
1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
2、Credentials:用户凭证,一般是密码
3、Authorities:用户权限

2.2、用户认证

Spring Security是怎么进行用户认证的呢?
AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。
Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
2.2.1、认证接口分析

AuthenticationManager的校验逻辑非常简单:
根据用户名先查询出用户对象(没有查到则抛出异常)将用户对象的密码和传递过来的密码进行校验,密码不匹配则抛出异常。
这个逻辑没啥好说的。重点是这里每一个步骤Spring Security都提供了组件:
1、是谁执行 根据用户名查询出用户对象 逻辑的呢?用户对象数据可以存在内存中、文件中、数据库中,你得确定好怎么查才行。这一部分就是交由UserDetialsService 处理,该接口只有一个方法loadUserByUsername(String username),通过用户名查询用户对象,默认实现是在内存中查询。
2、那查询出来的 用户对象 又是什么呢?每个系统中的用户对象数据都不尽相同,咱们需要确认我们的用户数据是啥样的才行。Spring Security中的用户数据则是由UserDetails 来体现,该接口中提供了账号、密码等通用属性。
3、对密码进行校验大家可能会觉得比较简单,if、else搞定,就没必要用什么组件了吧?但框架毕竟是框架考虑的比较周全,除了if、else外还解决了密码加密的问题,这个组件就是PasswordEncoder,负责密码加密与校验。
我们可以看下AuthenticationManager校验逻辑的大概源码:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    ...省略其他代码

    // 传递过来的用户名
    String username = authentication.getName();
// 调用UserDetailService的方法,通过用户名查询出用户对象UserDetail(查询不出来UserDetailService则会抛出异常)
UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(username);
String presentedPassword = authentication.getCredentials().toString();

// 传递过来的密码
String password = authentication.getCredentials().toString();
// 使用密码解析器PasswordEncoder传递过来的密码是否和真实的用户密码匹配
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
    // 密码错误则抛出异常
    throw new BadCredentialsException("错误信息...");
}

// 注意哦,这里返回的已认证Authentication,是将整个UserDetails放进去充当Principal
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails,
                                                                                     authentication.getCredentials(), userDetails.getAuthorities());
return result;

...省略其他代码
}

UserDetialsService、UserDetails、PasswordEncoder,这三个组件Spring Security都有默认实现,这一般是满足不了我们的实际需求的,所以这里我们自己来实现这些组件!

2.2.3、加密器PasswordEncoder

加密我们项目采取MD5加密
操作模块:spring_security模块
自定义加密处理组件:CustomMd5PasswordEncoder

/**
* <p>
* 密码处理
* </p>
*
*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

    public DefaultPasswordEncoder() {
        this(-1);
    }
    
    public DefaultPasswordEncoder(int strength) {

    }

    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}
2.2.4、用户对象UserDetails

该接口就是我们所说的用户对象,它提供了用户的一些通用属性,源码如下:

public interface UserDetails extends Serializable {
    /**
* 用户权限集合(这个权限对象现在不管它,到权限时我会讲解)
*/
    Collection<? extends GrantedAuthority> getAuthorities();
    /**
* 用户密码
*/
    String getPassword();
    /**
* 用户名
*/
    String getUsername();
    /**
* 用户没过期返回true,反之则false
*/
    boolean isAccountNonExpired();
    /**
* 用户没锁定返回true,反之则false
*/
    boolean isAccountNonLocked();
    /**
* 用户凭据(通常为密码)没过期返回true,反之则false
*/
    boolean isCredentialsNonExpired();
    /**
* 用户是启用状态返回true,反之则false
*/
    boolean isEnabled();
}

实际开发中我们的用户属性各种各样,这些默认属性可能是满足不了,所以我们一般会自己实现该接口,然后设置好我们实际的用户实体对象。实现此接口要重写很多方法比较麻烦,我们可以实现Spring Security提供的org.springframework.security.core.userdetails类,该类实现了UserDetails接口帮我们省去了重写方法的工作:
操作模块:spring_security模块
添加SecurityUser对象

@Data
    @Slf4j
    public class SecurityUser implements UserDetails {

        //当前登录用户
        private transient User currentUserInfo;

        //当前权限
        private List<String> permissionValueList;

        public SecurityUser() {
        }

        public SecurityUser(User user) {
            if (user != null) {
                this.currentUserInfo = user;
            }
        }

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for (String permissionValue : permissionValueList) {
                if (StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }

            return authorities;
        }

        @Override
        public String getPassword() {
            return currentUserInfo.getPassword();
        }

        @Override
        public String getUsername() {
            return currentUserInfo.getUsername();
        }

        @Override
        public boolean isAccountNonExpired() {
            return true;
        }

        @Override
        public boolean isAccountNonLocked() {
            return true;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }

        @Override
        public boolean isEnabled() {
            return true;
        }
    }

2.2.5、业务对象UserDetailsService

该接口很简单只有一个方法:

public interface UserDetailsService {
    /**
* 根据用户名获取用户对象(获取不到直接抛异常)
*/
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

我们实现该接口,就完成了自己的业务
操作模块:service_acl
添加UserDetailsServiceImpl类,实现UserDetailsService接口

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    /***
     * 根据账号获取用户信息
     * @param username:
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中取出用户信息
        User user = userService.selectByUsername(username);

        // 判断用户是否存在
        if (null == user) {
            throw new UsernameNotFoundException("用户名不存在!");
        }
        // 返回UserDetails实现类
        com.atguigu.security.entity.User curUser = new com.atguigu.security.entity.User();
        BeanUtils.copyProperties(user, curUser);

        List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId());
        SecurityUser securityUser = new SecurityUser(curUser);
        securityUser.setPermissionValueList(authorities);
        return securityUser;
    }

}

AuthenticationManager校验所调用的三个组件我们就已经做好实现了!

2.2.6、自定义用户认证接口

注意:这里的登录接口要和前端保持一致

//登录过滤器
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        //指定登录接口及提交方式--这里是登录的入口!!!
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));
    }

    //获取用户名和密码 认证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            //获取当前登录的用户
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //登录成功
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        //获取认证对象(自定义)
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        //根据用户名生成token
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        //将用户的权限数据存入redis
        redisTemplate.opsForValue().set("gulixy:"+user.getCurrentUserInfo().getUsername(),
                user.getPermissionValueList(), 30, TimeUnit.MINUTES);

        ResponseUtil.out(res, R.ok().data("token", token));
    }


    //登陆失败
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}

添加工具类:ResponseUtil
添加模块:common_util

public class ResponseUtil {

    public static void out(HttpServletResponse response, Result r) {
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2.2.7、认证解析token

因为用户登录状态在token中存储在客户端,所以每次请求接口请求头携带token, 后台通过自定义token过滤器拦截解析token完成认证并填充用户信息实体。

//认证解析过滤器
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("=================" + req.getRequestURI());
        //路径中含有 admin 则不拦截
        if (!req.getRequestURI().contains("admin")) {
            //放行
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }

        if (authentication != null) {
            //将 用户信息 设置为上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            //根据token获取 用户名
            String userName = tokenManager.getUserFromToken(token);

            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get("gulixy:"+userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            if (permissionValueList != null) {
                for (String permissionValue : permissionValueList) {
                    if (StringUtils.isEmpty(permissionValue)) continue;
                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                    authorities.add(authority);
                }
            }

            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}
2.2.8、配置用户认证

修改_TokenWebSecurityConfig_配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private TokenManager tokenManager;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private RedisTemplate redisTemplate;

    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
                                  TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and()
                .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
                .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }

    /**
     * 密码处理
     *
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
        );
//        web.ignoring().antMatchers("/*/**");
    }
}
2.2.9、获取认证后的用户信息
@RestController
@RequestMapping("/admin/acl/index")
public class IndexController {
    //登录接口在  TokenLoginFilter类  中

    @Autowired
    private IndexService indexService;

    //根据token获取用户信息
    @GetMapping("info")
    public R info() {
        //从上下文中 获取当前登录用户的用户名
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        Map<String, Object> userInfo = indexService.getUserInfo(username);
        return R.ok().data(userInfo);
    }
}

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

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

相关文章

年度征文|2022年「博客之星」,花落谁家? 大家来竞猜吧

一年一度的「博客之星」大赛如火如荼地进行着&#xff0c;大家都忙着评分、发帖、回帖.....今天发现我在分组的排名只有40多名&#xff0c;基本上算是放弃了。但是&#xff0c;看到本文的朋友&#xff0c;还是可以帮我拉拉票的&#xff01;请点开链接给个五星评分&#xff1a; …

CentOS服务器署Springboot的java项目最简单操作步骤

CentOS服务器署Springboot的java项目最简单操作步骤 准备工作 1.首先本地有一个能跑起来正常的 java 项目的 jar 包; 2.有一个前端项目, 可以仅是一个 index.html 文件; 3.需要备案好的域名 (可选, 否则只能 ip 访问) 4.购买阿里云或者腾讯云等等任意 CentOS 服务器一个 1. 获…

(机器学习深度学习常用库、框架|Pytorch篇)第(待定)节:卷积神经网络CNN中一些经典网络结构写法

文章目录一&#xff1a;LeNet-5二&#xff1a;AlexNet三&#xff1a;VGG四&#xff1a;ResNet五&#xff1a;MobileNetV1六&#xff1a;InceptionNet一&#xff1a;LeNet-5 LeNet-5&#xff1a;LeNet-5是一个较简单的卷积神经网络。下图显示了其结构&#xff1a;输入的二维图像…

ansible通过多种方法配置yum源仓库

目录 1.挂载本地光盘到/mnt ​2.配置yum源仓库文件通过多种方式实现 仓库1 &#xff1a;Name: RH294_BaseDescription&#xff1a; RH294 base softwareBase urt: file:///mnt/BaseOS不需要验证钦件包 GPG 签名启用此软件仓库 ​编辑仓库 2:Name: RH294_StreamDescription &…

第四十二篇 nextTick

在前面封装swiper组件当中&#xff0c;通过许多种方式方法&#xff0c;其一从mounted初始化过早转到updated后出现初始化重复&#xff0c;再者通过设置key值和使用v-if控制swiper组件&#xff0c;然后通过Vue.diretive自定义指令的方式来封装swiper组件&#xff0c;那么本篇的n…

排序算法之快速排序

目录 排序算法介绍 快速排序 算法流程 算法实现 python C 快排为什么快 算法优化 基准数优化 python C 尾递归优化 python C 排序算法介绍 《Hello算法》是GitHub上一个开源书籍&#xff0c;对新手友好&#xff0c;有大量的动态图&#xff0c;很适合算法初学者自…

Struts2框架标签

Struts2框架标签1、前言2、UI标签2.1、表单标签2.1、非表单标签3、通用标签4、例子4.1、实体类User4.2、控制器UserAction4.3、配置文件struts.xml4.4、页面users.jsp4.5、测试1、前言 Struts2有丰富的tag标签可以使用&#xff0c;即Struts2的标签库&#xff0c;如果能够灵活运…

彻底理解动态规划:编辑距离

本篇的题目非常经典&#xff0c;几乎是面试必备&#xff0c;即&#xff0c;编辑距离问题&#xff0c;edit distance&#xff1b; 给定两个字符串word1以及word2&#xff0c;返回将word1转为word2需要的最少步骤&#xff0c;在每一步中你可以针对字符串word1进行以下操作&#…

技术开发115

技术开发115 业务内容&#xff1a; 拖车用辅助脚、拖车用零件类、特殊车辆用车轴Sub。Assy产品、面向汽车产业的生产设备、面向建设机械的零部件类、面向汽车产业的检查夹具 公司简介&#xff1a; 董事长&#xff1a;佐藤安弘 资本金&#xff1a;4500万日元 员工数&#x…

shell第五天练习

题目&#xff1a; 1、编写函数&#xff0c;实现打印绿色OK和红色FAILED&#xff0c;判断是否有参数&#xff0c;存在为Ok&#xff0c;不存在为FAILED 2、编写函数&#xff0c;实现判断是否无位置参数&#xff0c;如无参数&#xff0c;提示错误 3、编写函数实现两个数字做为参数…

聊聊前端安全之CSRF

本文作者为奇舞团前端开发工程师一、什么是CSRFCSRF&#xff08;Cross Site Request Forgery&#xff0c;跨站域请求伪造&#xff09;&#xff0c;通常缩写为 CSRF。CSRF攻击是攻击者通过伪装成受信任用户向服务器发起各种请求&#xff0c;达到欺骗服务器接收并执行指令&#x…

【数据结构】顺序表

【本篇内容】线性表顺序表线性表线性表是n个具有相同特性的数据元素的有限序列。常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串......线性表在逻辑上是线性结构&#xff0c;但是在物理结构上并一定是连续的&#xff0c;线性表在物理上存储时&#xff0c;通常是以数组…

保证原子性的几种方式,你都知道吗???

1. 前言 今天主要是从实战 浅谈原理的角度来说下&#xff0c;并发线程下原子性的几种处理方式&#xff0c;好了废话不多说了&#xff0c;接下来让我们看看吧 2. 开始 在开始之前需要提问下大家, 代码i ; 能保持原子性吗&#xff1f;&#xff1f;&#xff1f; 是不是一句话就执…

C++——命名空间,输入输出,缺省参数

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;数据结构——二叉树 &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;补充C语言…

Java 诊断利器 Arthas JVM命令

一、jvm 相关命令介绍 命令说明dashboard当前系统的实时数据面板getstatic查看类的静态属性heapdumpdump java heap, 类似 jmap 命令的 heap dump 功能jvm查看当前 JVM 的信息logger查看和修改 loggermbean查看 Mbean 的信息memory查看 JVM 的内存信息ognl执行 ognl 表达式per…

【Android春招】Android基础day2

一、填空题 1&#xff0e;除了开启开发者选项之外&#xff0c;还需打开手机上的 _ 开关&#xff0c;然后才能在手机上调试App。 USB调试 2&#xff0e;App开发的两大技术路线包括 _和混合开发。 原生开发 3&#xff0e;App工程的编译配置文件名为 _。 build.gradle 4&#xff0…

挂载光盘,配置yum源并且安装http软件包 ansible(4)

目录 一、挂载本地光盘到/mnt 二、配置yum源 一、挂载本地光盘到/mnt 第一步&#xff1a; 使用mount模块 注&#xff1a;fstype代表文件格式 二、配置yum源 第一种方法&#xff1a; 使用yum_repository模块 检验是否有对应文件在受控主机node1受控主机&#xff1a;node2受控…

Zipkin数据持久化配置

上一篇我们了解了Zipkin的基础知识以及Zipkin的服务端搭建。 在使用过程中很多同学发现了他的秘密&#xff0c;Zipkin模式将数据保存在内存中&#xff0c;当我们重启后&#xff0c;追踪数据便会丢失。其实&#xff0c;Zipkin也支持将追踪数据保存到MySql或者ES中。 持久化到M…

大数据基础平台搭建-(四)HBbase集群HA+Zookeeper搭建

大数据基础平台搭建-&#xff08;四&#xff09;HBbase集群HAZookeeper搭建 大数据平台系列文章&#xff1a; 1、大数据基础平台搭建-&#xff08;一&#xff09;基础环境准备 2、大数据基础平台搭建-&#xff08;二&#xff09;Hadoop集群搭建 3、大数据基础平台搭建-&#xf…

Linux学习笔记——Nginx安装部署

5.3、Nginx安装部署 5.3.1、简介 Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理Web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。 同Tomcat一样&#xff0c;Nginx可以托管用户编写的WEB应用程序成为可访问的网页服务&#xff0c;同时也可以作为…