springboot JWT 搭建授权服务器

news2024/11/15 9:34:11

目录

0 基本介绍

0.1 课程视频

0.2 架构逻辑图

0.2.1 登录JWT与授权服务器交互

0.2.2 登录成功后JWT与gateway-server交互 路由限制

1 JWT私钥公钥

1.1 安装git ->win系统右键 -> git bash here

1.2 生成私钥jks文件

1.3 用私钥jks文件解析出公钥

1.4 保存 BEGIN PUBLIC KEY到txt文件

2 搭建授权服务器

 2.1 加依赖

2.2 yml配置文件

2.3 AuthorizationServerConfig 

2.3.1 添加第三方的客户端

2.3.2 配置验证管理器

2.3.2.1 jwtTokenStore

2.3.2.2 私钥jks 放入resources文件夹

2.3.2.3 JwtAccessTokenConverter ->用私钥jks

2.3.2.4 转化成json格式

2.3.2.3 完整代码

2.4 WebSecurityConfig

2.4.1 AuthenticationManager 授权管理器 Bean

2.4.2 UserDetailsService 用户Bean

2.4.3 PasswordEncoder 密码加密器Bean

2.4.4 安全 跨域

2.4.5 WebSecurityConfig完整代码

3 postman 测试

3.1 测试获取token

3.2 解析token

4 网关gateway-server服务器 验证token是否存在

4.1 加依赖

4.2 yml 配置文件

4.3 过滤器 filter

4.3.1 判断该接口是否需要token

4.3.2 从请求头里读取token 

4.3.3 如果没有token 给用户响应错误

4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在

4.3.5 完整代码

5 postman 测试


0 基本介绍

0.1 课程视频

https://www.bilibili.com/video/BV173411P7M2?p=30&spm_id_from=pageDriver&vd_source=ff8b7f852278821525f11666b36f180a

0.2 架构逻辑图

0.2.1 登录 JWT 授权服务器 Redis

0.2.2 登录成功后JWT与gateway-server交互 路由限制

1 JWT私钥公钥

1.1 安装git ->win系统右键 -> git bash here

1.2 生成私钥jks文件

keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange
-keystore coinexchange.jks -validity 365 -storepass coinexchange

1.3 用私钥jks文件解析出公钥

keytool -list -rfc --keystore coinexchange.jks | openssl x509 -inform pem
-pubkey

1.4 保存 BEGIN PUBLIC KEY到txt文件

2 搭建授权服务器

 2.1 加依赖

<!--        服务的发现和注册-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--        OAuth2.0-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
<!--        web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

2.2 yml配置文件

server:
  port: 9999
spring:
  application:
    name: authorization-server
  cloud:
    nacos:
      discovery:
        server-addr: nacos-server:8848

2.3 AuthorizationServerConfig 

2.3.1 添加第三方的客户端

@Autowired
    private PasswordEncoder passwordEncoder; // 密码加密器 
/** 必须配一 ClientDetailsServiceConfigurer
     * 添加第三方的客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //先配置到内存测试 之后配置到数据库使用
        clients.inMemory() //使用in-memory存储
                .withClient("coin-api") // 第三方客户端的名称 client_id
                .secret(passwordEncoder.encode("coin-secret")) //  第三方客户端的密钥 加密
                .scopes("all") //第三方客户端的授权范围 // 允许的授权范围
                .authorizedGrantTypes("password","refresh_token") // token 过期时间
                .accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
                .refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
                .and()
                .withClient("inside-app")
                .secret(passwordEncoder.encode("inside-secret"))
                .authorizedGrantTypes("client_credentials")
                .scopes("all")
                .accessTokenValiditySeconds(7 * 24 *3600)
        ;
        super.configure(clients);
    }

2.3.2 配置验证管理器

2.3.2.1 jwtTokenStore

 private TokenStore jwtTokenStore() {
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        return jwtTokenStore;
    }

2.3.2.2 私钥jks 放入resources文件夹

2.3.2.3 JwtAccessTokenConverter ->用私钥jks

public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();

        // 加载我们的私钥 // 要生成一个 放在resources中
        ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
        tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
        return tokenConverter;
    }

2.3.2.4 转化成json格式

@Autowired
    private AuthenticationManager authenticationManager;
@Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
    @Autowired
    private UserDetailsService userDetailsService;
/**必须配2 AuthorizationServerEndpointsConfigurer
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 验证管理器
                .userDetailsService(userDetailsService)
                 .tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
                 .tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储

        super.configure(endpoints);
    }

   

2.3.2.3 完整代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.web.bind.annotation.CrossOrigin;

@EnableAuthorizationServer // 开启授权服务器的功能
@Configuration
@CrossOrigin // @allowOriginsPattens
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 点进去有三个方法 就是要配的三个

    @Autowired
    private PasswordEncoder passwordEncoder; // 密码加密器

    @Autowired
    private AuthenticationManager authenticationManager;

//    @Qualifier("userServiceDetailsServiceImpl")
    @Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
    @Autowired
    private UserDetailsService userDetailsService;



    /** 必须配一 ClientDetailsServiceConfigurer
     * 添加第三方的客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //先配置到内存测试 之后配置到数据库使用
        clients.inMemory() //使用in-memory存储
                .withClient("coin-api") // 第三方客户端的名称 client_id
                .secret(passwordEncoder.encode("coin-secret")) //  第三方客户端的密钥 加密
                .scopes("all") //第三方客户端的授权范围 // 允许的授权范围
                .authorizedGrantTypes("password","refresh_token") // token 过期时间
                .accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
                .refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
                .and()
                .withClient("inside-app")
                .secret(passwordEncoder.encode("inside-secret"))
                .authorizedGrantTypes("client_credentials")
                .scopes("all")
                .accessTokenValiditySeconds(7 * 24 *3600)
        ;
        super.configure(clients);
    }

    /**必须配2 AuthorizationServerEndpointsConfigurer
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 验证管理器
                .userDetailsService(userDetailsService)
                .tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
                .tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储

        super.configure(endpoints);
    }

    private TokenStore jwtTokenStore() { // TokenStore接口 内存储存适合一台授权服务器,内存不能在多个服务器共享数据 -> redis 共享token
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        return jwtTokenStore;
    }

    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();

        // 加载我们的私钥 // 要生成一个 放在resources中
        ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
        tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
        return tokenConverter;
    }

}

2.4 WebSecurityConfig

2.4.1 AuthenticationManager 授权管理器 Bean

@Bean // AuthorizationServerConfig 中的授权管理器
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();

2.4.2 UserDetailsService 用户Bean

//    @Bean //  没有搭建amdmin服务器 用假数据测试
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
//        inMemoryUserDetailsManager.createUser(user);
//        return inMemoryUserDetailsManager;
//    }

2.4.3 PasswordEncoder 密码加密器Bean

@Bean // 密码加密器
    public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
        return new BCryptPasswordEncoder();
    }

2.4.4 安全 跨域

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable(); // 跨域
        http.authorizeRequests().anyRequest().authenticated();

2.4.5 WebSecurityConfig完整代码

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import java.util.Arrays;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable(); // 跨域
        http.authorizeRequests().anyRequest().authenticated(); // 授权
    }

    @Bean // AuthorizationServerConfig 中的授权管理器
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


//    @Bean //  没有搭建amdmin服务器 用假数据测试
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
//        inMemoryUserDetailsManager.createUser(user);
//        return inMemoryUserDetailsManager;
//    }

    /**
     * 密码加密
     *
     * @return
     */
    @Bean // 密码加密器
    public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
        return new BCryptPasswordEncoder();
    }

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

3 postman 测试

3.1 测试获取token

3.2 解析token

https://jwt.io

4 网关gateway-server服务器 验证token是否存在

4.1 加依赖

        <!--因为授权服务器判断token 要通过网关 所以redis添加到网关服务器-->
        <dependency> 
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

4.2 yml 配置文件

  redis:
    host: redis-server
    port:
    password: 

4.3 过滤器 filter

4.3.1 判断该接口是否需要token

@Value("${no.require.urls:/admin/login,/user/gt/register}")
    private Set<String> noRequireTokenUris ;
private boolean isRequireToken(ServerWebExchange exchange) {
        String path = exchange.getRequest().getURI().getPath();
        if(noRequireTokenUris.contains(path)){
            return false ; // 不需要token
        }
        if(path.contains("/kline/")){
            return false ;
        }
        return Boolean.TRUE ;
    }

4.3.2 从请求头里读取token 

private String getUserToken(ServerWebExchange exchange) {
        String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        return token ==null ? null : token.replace("bearer ","") ;
    }

4.3.3 如果没有token 给用户响应错误

private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("Content-Type","application/json"); // 相应类型 json数据
        response.setStatusCode(HttpStatus.UNAUTHORIZED) ; // 相应码 401 
        JSONObject jsonObject = new JSONObject(); // 创建json对象
        jsonObject.put("error","NoAuthorization") ;
        jsonObject.put("errorMsg","Token is Null or Error") ;
        DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes()); // 响应数据
        return response.writeWith(Flux.just(wrap)) ; // 转为流式?Flux.just(wrap)
    }

4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在

@Autowired
    private StringRedisTemplate redisTemplate ;
@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1 : 该接口是否需要token 才能访问
        if(!isRequireToken(exchange)){
            return chain.filter(exchange) ;// 不需要token ,直接放行
        }
        // 2: 取出用户的token
        String token = getUserToken(exchange) ;
        // 3 判断用户的token 是否有效
        if(StringUtils.isEmpty(token)){
            return buildeNoAuthorizationResult(exchange) ;
        }
        Boolean hasKey = redisTemplate.hasKey(token);
        if(hasKey!=null && hasKey){
            return chain.filter(exchange) ;// token有效 ,直接放行
        }
        return buildeNoAuthorizationResult(exchange) ;
    }

4.3.5 完整代码

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Set;

@Component // 放到容器
public class JwtCheckFilter implements GlobalFilter, Ordered {

    @Autowired
    private StringRedisTemplate redisTemplate ;

    @Value("${no.require.urls:/admin/login,/user/gt/register}")
    private Set<String> noRequireTokenUris ;
    /**
     * 过滤器拦截到用户的请求后做啥
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1 : 该接口是否需要token 才能访问
        if(!isRequireToken(exchange)){
            return chain.filter(exchange) ;// 不需要token ,直接放行
        }
        // 2: 取出用户的token
        String token = getUserToken(exchange) ;
        // 3 判断用户的token 是否有效
        if(StringUtils.isEmpty(token)){
            return buildeNoAuthorizationResult(exchange) ;
        }
        Boolean hasKey = redisTemplate.hasKey(token);
        if(hasKey!=null && hasKey){
            return chain.filter(exchange) ;// token有效 ,直接放行
        }
        return buildeNoAuthorizationResult(exchange) ;
    }

    /**
     * 给用户响应一个没有token的错误
     * @param exchange
     * @return
     */
    private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("Content-Type","application/json"); // 相应json数据
        response.setStatusCode(HttpStatus.UNAUTHORIZED) ;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("error","NoAuthorization") ;
        jsonObject.put("errorMsg","Token is Null or Error") ;
        DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
        return response.writeWith(Flux.just(wrap)) ;
    }

    /**
     * 从 请求头里面获取用户的token
     * @param exchange
     * @return
     */
    private String getUserToken(ServerWebExchange exchange) {
        String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        return token ==null ? null : token.replace("bearer ","") ; // 去掉bearer
    }

    /**
     * 判断该 接口是否需要token
     * @param exchange
     * @return
     */
    private boolean isRequireToken(ServerWebExchange exchange) {
        String path = exchange.getRequest().getURI().getPath();
        if(noRequireTokenUris.contains(path)){
            return false ; // 不需要token
        }
        if(path.contains("/kline/")){
            return false ;
        }
        return Boolean.TRUE ;
    }


    /**
     * 拦截器的顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

5 postman 测试

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

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

相关文章

造型简约的机箱,安装简单兼容性好,安钛克P20C体验

我们准备组装一台新主机的时候&#xff0c;机箱确实很重要&#xff0c;它决定了主机的整体风格和兼容性。我比较喜欢用中塔机箱&#xff0c;像是上个月我新装的主机&#xff0c;用的就是安钛克P20C&#xff0c;这款机箱的设计很简约&#xff0c;而且还有多种版本可选&#xff0…

C++——模板进阶

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;类型模板参数&#x1f337;非类型模板参数&#x1f337;模板的特化&#x1f338;引例&#x1f338;函数模板的特化&#x1f338;类模板特化&#x1f33c;全特化 &#x1f338;偏特化&#x1f33c;部分特化&am…

【下载器篇】IDM下载器个性化设置

【下载器篇】IDM下载器个性化设置 IDM个性化设置—【蘇小沐】 文章目录 【下载器篇】IDM下载器个性化设置1.实验环境 &#xff08;一&#xff09;下载类型扩展UA默认值 &#xff08;二&#xff09;工具栏样式&#xff08;改风格&#xff09;3D Style样式 &#xff08;三&#…

2023.4.23 自注意力机制

一般都是单向量输入&#xff0c;但是如果多向量输入应该如何处理呢&#xff1f;引出自注意力机制 多向量输入可能会有多种输出&#xff0c;如果输入n个向量&#xff0c;输出n个向量表明这是sequence labeling&#xff0c;比如对于一个英文句子&#xff0c;每一个单词都判断是什…

c++11 标准模板(STL)(std::priority_queue)(二)

适配一个容器以提供优先级队列 std::priority_queue 定义于头文件 <queue> template< class T, class Container std::vector<T>, class Compare std::less<typename Container::value_type> > class priority_queue; priority_queu…

Windows下编译UHD

1.安装Visual Studio 2019,下载地址https://download.csdn.net/download/qq_36314864/87719209 2.安装cmake,下载地址https://download.csdn.net/download/qq_36314864/87719747 安装完成后记得C:\Program Files\cmake-3.22.1-windows-x86_64\bin添加到环境变量里面,或者安…

数据结构修炼:链表习题讲解!!!

题一&#xff1a;移除链表元素 我们可以看出这道题是让我们删除特定数据&#xff0c;我们可以用双指针来解这道题&#xff1a; 如果首元素为val&#xff0c;那么cur和head一起后移&#xff1a; 如果没有碰到val&#xff0c;那么就会cur后移&#xff0c;并且提前将cur传给perv&a…

如何避免美国ASP主机服务器崩溃和故障?

在当今数字化时代&#xff0c;网站是一个公司展示其业务的主要方式之一。因此&#xff0c;公司的在线业务应该始终保持高可用性和可靠性。ASP主机服务器是一种用于托管网站的服务器&#xff0c;其特点是可靠性高。但是&#xff0c;即使是最可靠的服务器也会遭受故障或崩溃。在本…

漏刻有时数据可视化大屏引导页设计(php原生开发、主背景图片更换、标题设置)

文章目录 1.引入外部js库2.HTML排版3.项目配置文件4.菜单图标自动匹配5.php与html混排6.CSS样式表7.添加/编辑信息8.生成配置文件 在制作数据可视化大屏时&#xff0c;尤其是在触摸屏演示时&#xff0c;需要开发和设计对应的数据大屏引导页。基于常见场景&#xff0c;单独开发数…

Unity API详解——Object类

Object类是Unity中所有对象的基类&#xff0c;例如GameObject、Component、Material、Shader、Texture、Mesh、Font等都是Object的子类。本博客介绍Object类的一些实例方法和静态方法。 一、Object类实例方法 在Object类中&#xff0c;涉及的实例方法主要有GetInstanceID方法…

Java基础学习(10)

Java基础学习 一、JDK8时间类1.1 Zoneld时区1.2 Instant时间戳1.3 ZonedDateTime1.4 DateTimeFormatter1.5 日历类时间表示1.6 工具类1.7 包装类JDK5提出的新特性Integer成员方法 二、集合进阶2.1 集合的体系结构2.1.1 Collection 2.2collection的遍历方式2.2.1 迭代器遍历2.2.…

RecycleView与TabLayout联动展示更多功能列表页面的实现

一.前言 对于更多功能页面&#xff0c;使用RecycleView与TabLayout联动方式实现是比较常见的&#xff0c;先上效果图&#xff08;请大佬们忽略gif的水印&#xff09; 单独使用TabLayout和RecycleView都是比较容易的&#xff0c;这里就不做举例了&#xff1b;gif中的列表实际上…

权限控制导入到项目中

在项目中应用 进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑&#xff0c;因为用户信息、权限信息、菜单信息、角色信息、关联信息等都保存在这7张表中&#xff0c;也就是这些表中的数据是进行认证和授权的依据。所以在真正进行认证和授权之前需要对这些数据进行…

( “树” 之 BST) 501. 二叉搜索树中的众数 ——【Leetcode每日一题】

二叉查找树&#xff08;BST&#xff09;&#xff1a;根节点大于等于左子树所有节点&#xff0c;小于等于右子树所有节点。 二叉查找树中序遍历有序。 ❓501. 二叉搜索树中的众数 难度&#xff1a;简单 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root…

Leetcode每日一题——“合并两个有序数组”

各位CSDN的uu们你们好呀&#xff0c;又到小雅兰的愉快题解时候啦&#xff0c;今天&#xff0c;我们的题目内容是合并两个有序数组&#xff0c;下面&#xff0c;让我们进入合并两个有序数组的世界吧 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,0,0,0], m 3, nums2 [2,…

C++内联/构造函数详解

内联函数 宏&#xff1a; 宏的优缺点&#xff1f; 优点&#xff1a; 1.增强代码的复用性。 2.提高性能。 缺点&#xff1a; 1.展开后会使得代码长度变长&#xff0c;使可执行程序变大 2.不方便调试宏。&#xff08;因为预编译阶段进行了替换&#xff09; 3.导致代码可读性差…

Python 查看数据常用函数

Python 查看数据常用函数&#xff08;以 iris 数据集为例&#xff09; 1、查看前后几行数据&#xff1a;head 和 tail2、查看数据基本信息&#xff1a;info3、查看数据统计信息&#xff1a;describe 查看数据可以用很多函数&#xff0c;这里就挑选几个最常用的进行简单展示&…

SpringBoot自动配置原理、手写一个xxx-spring-boot-starter

SpringBoot的自动配置是&#xff1a;当项目中使用了一个第三方依赖&#xff0c;如何将第三方依赖中的Bean加载到Spring的IOC容器中&#xff0c;我们就可以做到无需额外的配置&#xff0c;直接使用第三方jar中的Bean。 SpringBoot的理念是“约定大于配置”&#xff0c;只要按照S…

【下载器篇】IDM下载记录分析(简)

【下载器篇】IDM下载记录分析&#xff08;简&#xff09; IDM下载记录分析-未完待续—【蘇小沐】 文章目录 【下载器篇】IDM下载记录分析&#xff08;简&#xff09;1.实验环境 &#xff08;一&#xff09;IDM下载记录分析-未完待续临时文件夹下载痕迹 总结 1.实验环境 系统版…

【内网渗透】春秋云镜Intitle WP

前言 第一次正式接触内网渗透的东西&#xff0c;写的很新手&#xff0c;也适合新手观看&#xff0c;有问题可以私信或评论&#xff0c;接下来会持续更新 信息收集 拿到地址先nmap扫端口 没什么发现&#xff0c;直接访问80端口&#xff0c;看到图标知道是thinkphp 第一台Th…