Spring Security基于jwt实现权限校验

news2024/11/27 20:40:23

一 引言

在基于springsecurity和jwt实现的单体项目token认证中我实现了基于jwt实现的认证,本文在此基础上继续实现权限认证
在这里插入图片描述

  • 用户认证成功后携带jwt发起请求,请求被AuthenticationFilter拦截到,进行jwt的校验
  • jwt校验成功后,调用JwtAuthenticationProvider从jwt中获得权限信息,加载到Authcation中
  • 将Authcation加载安全上下文SecurityContextHolder
  • FilterSecurityInterceptor从上下文中获得用户权限信息,根据校验规则进行用户数据的权限校验

二 代码实现

用户认证成功生成jwt时将权限信息加载到jwt中

package com.xlcp.xlcpdemo.auth.token;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.RegisteredPayload;
import cn.hutool.jwt.signers.AlgorithmUtil;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.xlcp.xlcpdemo.auth.common.AccessToken;
import com.xlcp.xlcpdemo.auth.common.AccessTokenType;
import com.xlcp.xlcpdemo.auth.common.AuthProperties;
import com.xlcp.xlcpdemo.auth.core.AccessTokenManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;

import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * @author likun
 * @date 2022年07月12日 13:48
 */
@Slf4j
public class JwtAccessTokenManager implements AccessTokenManager {
    private final AuthProperties authProperties;

    private final JWTSigner jwtSigner;
    // 省略....
    @Override
    public AccessToken createToken(Authentication authentication) {
        AccessToken accessToken = new AccessToken();
        accessToken.setTokenType(AccessTokenType.JWT.name());
        accessToken.setExpireInTimeMills(authProperties.getExpireInTimeMills());
        HashMap<String, Object> payloads = new HashMap<String, Object>();
        payloads.put(RegisteredPayload.AUDIENCE, authentication.getName());
        payloads.put(RegisteredPayload.JWT_ID, IdUtil.fastUUID());
        DateTime expiredAt = DateUtil.offset(new Date(), DateField.MILLISECOND, Convert.toInt(authProperties.getExpireInTimeMills()));
        payloads.put(RegisteredPayload.EXPIRES_AT, expiredAt);
        // todo 数据库查询权限信息
        List permissions = CollUtil.newArrayList("ROLE_BUYER","ROLE_SELLER","user_find_account");
        payloads.put("authDetails", permissions);
        String token = JWTUtil.createToken(payloads, this.jwtSigner);
        accessToken.setAccessToken(token);
        return accessToken;
    }

    
}

定义JwtAuthenticationProviderJwtAuthenticationToken用于认证成功从jwt中解析jwt中的权限信息

package com.xlcp.xlcpdemo.auth.core;

import cn.hutool.jwt.JWT;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

import java.util.List;

/**
 * @author likun
 * @date 2022年12月01日 12:25
 */
public class JwtAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
        String token = jwtAuthenticationToken.getToken();
        JWT jwt = JWT.create().parse(token);
        Object authDetails = jwt.getPayload("authDetails");
        Object aud = jwt.getPayload("aud");
        List<GrantedAuthority> permissions;
        if (authDetails!=null&&authDetails instanceof List){
            List<String> auths = (List<String>) authDetails;
            permissions=AuthorityUtils.createAuthorityList(auths.toArray(new String[0]));
        }else {
            permissions = AuthorityUtils.createAuthorityList("");
        }
        return new JwtAuthenticationToken(aud,null,permissions);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
    }
}
package com.xlcp.xlcpdemo.auth.core;

import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/**
 * @author likun
 * @date 2022年12月01日 11:51
 */
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
    private  Object principal;

    private Object credentials;

    @Getter
    private String token;

    public JwtAuthenticationToken(Object principal,Object credentials,Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal= principal;
        this.credentials= credentials;
        setAuthenticated(true);
    }

    public JwtAuthenticationToken(String token){
        super(null);
        this.token=token;
        setAuthenticated(false);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

jwt校验成功后解析jwt并加载到安全上下文中

package com.xlcp.xlcpdemo.auth.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.RegisteredPayload;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xlcp.xlcpdemo.auth.common.AuthProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;
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.Set;

import static com.xlcp.xlcpdemo.entity.PtUser.ACCOUNT;

/**
 * @author likun
 * @date 2022年07月12日 15:14
 */
@Slf4j
public class AuthenticationFilter extends OncePerRequestFilter {

    private static final String BEARER = "bearer";

    private final AuthProperties authProperties;
    private final AccessTokenManager accessTokenManager;
    private final AntPathMatcher antPathMatcher;
    private final AuthenticationManager authenticationManager;

    public AuthenticationFilter(AuthProperties authProperties, AccessTokenManager accessTokenManager, AntPathMatcher antPathMatcher,AuthenticationManager authenticationManager){
        this.authProperties=authProperties;
        this.accessTokenManager=accessTokenManager;
        this.antPathMatcher=antPathMatcher;
        this.authenticationManager=authenticationManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 判断当前请求是否为忽略的路径
        Set<String> ignorePaths = authProperties.getIgnorePaths();
        if (CollUtil.isNotEmpty(ignorePaths)){
            for (String ignorePath : ignorePaths) {
                if (antPathMatcher.match(ignorePath,request.getRequestURI())){
                    filterChain.doFilter(request, response);
                    return;
                }
            }
        }
        // token校验
        String bearerToken = request.getHeader(Header.AUTHORIZATION.getValue());

        if (StrUtil.isBlank(bearerToken)){
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            throw new InsufficientAuthenticationException("unauthorized request.");
        }

        final String accessToken = bearerToken.trim().substring(BEARER.length()).trim();
        boolean valid = false;
        try {
            valid = accessTokenManager.verify(accessToken);
        } catch (Exception e) {
            log.warn("verify access token [{}] failed.", accessToken);
            throw new InsufficientAuthenticationException("invalid access token + [ " + accessToken + " ].");
        }
        if (!valid) {
            throw new InsufficientAuthenticationException("invalid access token + [ " + accessToken + " ].");
        }

        final String account = request.getParameter(ACCOUNT);
        if (StringUtils.isBlank(account)) {
            SetAuthentication(accessToken);
            filterChain.doFilter(request, response);
            return;
        }
        //校验是否本人
        final String audience = JWT.of(accessToken).getPayload(RegisteredPayload.AUDIENCE).toString();
        if (!account.equalsIgnoreCase(audience)) {
            throw new AccessDeniedException("invalid account. parameter [ " + account + " ]. account in token [ " + audience + " ].");
        }
        SetAuthentication(accessToken);
        filterChain.doFilter(request, response);

    }
    // 解析jwt并加载到安全上下文中
    private void SetAuthentication(String accessToken) {
        JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(accessToken);
        Authentication authenticate = authenticationManager.authenticate(jwtAuthenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authenticate);
    }
}

自定义权限不足返回异常处理

public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        R<Object> result = R.failed("无访问权限");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.getWriter().write(JSONUtil.toJsonStr(result));
    }
}

完成相应的配置
在这里插入图片描述

在这里插入图片描述

二 权限访问

2.1 理论基础

Spring Security是一个功能强大且高度可定制的 身份认证访问控制 框架,它是保护基于spring应用程序的事实标准。

权限访问:就是 给用户角色添加角色权限,使得不同的用户角色只能访问特定的接口资源,对于其他接口无法访问

2.2 权限分类

根据业务的不同将权限的控制分为两类,一类是 To-C简单角色的权限控制 ,一类是 To-B基于RBAC数据模型的权限控制

  • To-C简单角色的权限控制

例如 买家和卖家,这两者都是单独的个体,一般来说都是只有一种独立的角色,比如卖家角色:ROLE_SELLER,买家角色:ROLE_BUYER。这类一般比较粗粒度的将角色划分,且角色比较固定,角色拥有的权限也是比较固定,在项目启动的时候就固定了。

  • To-B基于RBAC数据模型的权限控制

例如 PC后台的管理端,能登录的是企业的人员,企业人员可以有不同的角色,角色的权限也可以比较随意地去改变,比如总经理角色可以访问所有资源,店铺管理人员只能访问店铺和卖家相关信息,会员管理人员可以访问买家相关信息等等,这时候就可以使用基于RBAC数据模型结合Spring Security的访问控制来实现权限方案。这类一般角色划分较细,角色的权限也是上线后在PC端可任意配置

在我们的日常开发中一般用得比较多的是第二种

2.3 To-C:简单角色的权限控制

定义相应的接口

@RestController
@RequestMapping("/buyer")
public class BuyerController {

    /**
     * 买家下订单
     *
     * @return
     */
    @GetMapping("/order:create")
    public String receiveOrder() {
        return "买家下单啦!";
    }

    /**
     * 买家订单支付
     *
     * @return
     */
    @GetMapping("/order:pay")
    public String deliverOrder() {
        return "买家付款了!";
    }
}

@RestController
@RequestMapping("/seller")
public class SellerController {

    /**
     * 卖家接单
     *
     * @return
     */
    @GetMapping("/order:receive")
	@Secured("ROLE_SELLER")
    public String receiveOrder() {
        return "卖家接单啦!";
    }

    /**
     * 卖家订单发货
     *
     * @return
     */
    @GetMapping("/order:deliver")
    @Secured("ROLE_SELLER")
    public String deliverOrder() {
        return "卖家发货啦!";
    }
}

我们要做到的是,买家角色只拥有买家接口权限,卖家角色只拥有卖家接口权限。而关于配置角色权限有两种实现方式,一种是在核心配置类中统一配置(买家角色演示),还有一种是在接口上以注解的方式配置(卖家角色演示)。

2.3.1 统一配置

在核心配置类(WebSecurityConfig)中,统一配置买家角色权限,角色名称是 ROLE_BUYER,拥有访问 /buyer/** 接口的权限。

protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .authorizeRequests()
                .antMatchers("/buyer/**").hasRole("BUYER")
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.addFilterBefore(authenticationFilter(accessTokenManager()), UsernamePasswordAuthenticationFilter.class);
    }

2.3.2 注解方式

可以使用注解的方式配置接口所能访问的角色,比如卖家端两个接口配置了 ROLE_SELLER 角色才能访问

@RestController
@RequestMapping("/seller")
public class SellerController {

    /**
     * 卖家接单
     *
     * @return
     */
    @GetMapping("/order:receive")
	@Secured("ROLE_SELLER")
    public String receiveOrder() {
        return "卖家接单啦!";
    }

    /**
     * 卖家订单发货
     *
     * @return
     */
    @GetMapping("/order:deliver")
    @Secured("ROLE_SELLER")
    public String deliverOrder() {
        return "卖家发货啦!";
    }
}

@Secured、@RolesAllowed、@PreAuthorize 注解都可以达到这样的效果,所有注解能发挥有效的前提是需要在核心配置类加上注解 @EnableGlobalMethodSecurity,然后在此注解上启用对应的注解配置方式,注解才能生效,否则无法起作用,比如要使 @Secured 注解生效需要配置@EnableGlobalMethodSecurity(securedEnabled = true)
在这里插入图片描述

注解能否生效和启用注解的属性对应关系如下,简单解释就是要使接口上的注解生效,就需要在核心过滤器配置注解 @EnableGlobalMethodSecurity,然后启用注解对应的属性,就是将属性值设为true。

生效注解启用注解的属性核心配置器上注解配置
@SecuredsecuredEnabled@EnableGlobalMethodSecurity(securedEnabled = true)
@RolesAllowedjsr250Enabled@EnableGlobalMethodSecurity(jsr250Enabled= true)
@PreAuthorizeprePostEnabled@EnableGlobalMethodSecurity(prePostEnabled = true)

2.3.3 测试

只设置ROLE_BUYER角色
在这里插入图片描述
买家能正常访问
在这里插入图片描述
卖家无访问权限
在这里插入图片描述

三 To-B:基于RBAC数据模型的权限控制

RBAC数据模型

  • 全称:Role-Based Access Control(基于角色的访问控制)
  • 一般会有五个表组成,三张主体表(用户、角色、权限),两张关联表(用户-角色、角色-权限)
    在这里插入图片描述

3.1 案例

首先关于RBAC的数据模型大家应该都很熟悉,这里不再创建,即不会涉及到存储。其实这一类相对上面那类区别在于这类的权限不是固定的,需要实时的重新查询出来,再进行判断请求是否有权访问,所以判断是否有权访问的逻辑需要自己完善,写好之后再配置进框架中即可。

申明权限校验基础接口

public interface PermissionService {
    /**
     * 判断是否拥有权限
     * @param permissions
     * @return
     */
    boolean hasPermission(String... permissions);
}

@Component("pms")
public class PermissionServiceImpl implements PermissionService{
    @Override
    public boolean hasPermission(String... permissions) {
        if (ArrayUtil.isEmpty(permissions)){
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
                .anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
    }
}

在相应的接口上申明相应的权限

在这里插入图片描述

开启相应权限支持

在这里插入图片描述

用户认证时查询数据库 加载不同的权限

在这里插入图片描述

没有权限查询结果

在这里插入图片描述

有权限时查询结果

在这里插入图片描述

在这里插入图片描述

四 权限表达式

上面.permitAll()、.hasRole()、.access()表示权限表达式,而权限表达式实际上都是 Spring中强大的Spel表达式,如下还有很多可以使用的权限表达式以及和Spel表达式的转换关系

权限表达式(ExpressionUrlAuthorizationConfigurer)说明Spel表达式Spel表达式实际执行方法(SecurityExpressionOperations)
permitAll()表示允许所有,永远返回truepermitAllpermitAll()
denyAll()表示拒绝所有,永远返回falsedenyAlldenyAll()
anonymous()当前用户是anonymous时返回trueanonymousisAnonymous()
rememberMe()当前用户是rememberMe用户时返回truerememberMeisRememberMe()
authenticated()当前用户不是anonymous时返回trueauthenticatedisAuthenticated()
fullyAuthenticated()当前用户既不是anonymous也不是rememberMe用户时返回truefullyAuthenticatedisFullyAuthenticated()
hasRole(“BUYER”)用户拥有指定权限时返回truehasRole(‘ROLE_BUYER’)hasRole(String role)
hasAnyRole(“BUYER”,“SELLER”)用于拥有任意一个角色权限时返回truehasAnyRole (‘ROLE_BUYER’,‘ROLE_BUYER’)hasAnyRole(String… roles)
hasAuthority(“BUYER”)同hasRolehasAuthority(‘ROLE_BUYER’)hasAuthority(String role)
hasAnyAuthority(“BUYER”,“SELLER”)同hasAnyRolehasAnyAuthority (‘ROLE_BUYER’,‘ROLE_BUYER’)hasAnyAuthority(String… authorities)
hasIpAddress(‘192.168.1.0/24’)请求发送的Ip匹配时返回truehasIpAddress(‘192.168.1.0/24’)hasIpAddress(String ipAddress),该方法在WebSecurityExpressionRoot类中
access(“@rbacService.hasPermission(request, authentication)”)可以自定义Spel表达式@rbacService.hasPermission (request, authentication)hasPermission(request, authentication) ,该方法在自定义的RbacServiceImpl类中

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

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

相关文章

一个分布在多次Softmax后,会趋于相同

本文其实是我在知乎上无意中翻到的一条提问&#xff1a;softmax到底有哪些作用&#xff1f;&#xff0c;其中苏剑林大佬关于第四个问题的回复&#xff0c;给我产生了一些思考。为什么一个分布在多次Softmax之后&#xff0c;每个值会趋于相同&#xff1f;例如[1,100]在大约10次S…

[附源码]JAVA毕业设计高校心理咨询预约系统(系统+LW)

[附源码]JAVA毕业设计高校心理咨询预约系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

新建esp32的vscode工程的三种方式

普通的idf工程在vscode下直接打开的时候&#xff0c;会发现很多头文件都是报错的&#xff0c;一些函数/类型等的定义也无法找到&#xff0c;比较影响阅读&#xff1a; 因此在vscode上开发esp32的时候&#xff0c;最好为这个工程提供vscode的支持&#xff0c;以下是三种实现的…

商务部研究院信用所、启信宝联合发布《中国商务信用发展指数报告(2022)》

近期&#xff0c;商务部国际贸易经济合作研究院信用研究所与合合信息全资子公司上海生腾数据科技有限公司&#xff08;简称“生腾数据”&#xff09;联合发布了《中国商务信用发展指数报告&#xff08;2022&#xff09;》&#xff08;简称《报告》&#xff09;。为准确反映中国…

NLP中的对抗训练(附PyTorch实现)

对抗样本的基本概念 要认识对抗训练&#xff0c;首先要了解"对抗样本"&#xff0c;它首先出现在论文Intriguing properties of neural networks之中。简单来说&#xff0c;它是指对于人类来说"看起来"几乎一样&#xff0c;但对于模型来说预测结果却完全不…

[附源码]JAVA毕业设计公务用车管理智慧云服务监管平台(系统+LW)

[附源码]JAVA毕业设计公务用车管理智慧云服务监管平台&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff0…

[附源码]Python计算机毕业设计SSM蓝色港湾房产交易与租赁系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

最近基于深度学习大火的AIGC将会抢原创工作者的饭碗?

NLG | CLIP | Diffusion Model GAN | AIGC | Stable Diffusion 随着CLIP、DALLE、Diffusion Model、Magic3D、Stable Diffusion等技术的快速发展&#xff0c;AIGC在全球各大科技巨头间可谓是高频词汇&#xff0c;连带着AI这个老生常谈的话题也一并火热起来。 去年三月&#xf…

【R】R包MethylCal安装问题解决 Rtools is required to build R packages

文章目录写在前面问题描述解决过程【1】安装INLA【2】安装Rtools写在前面 吐槽一番&#xff1a; 一般情况下&#xff0c;不是在万不得已&#xff0c;真心不想用R&#xff0c;最让人望而却步的就是包的安装问题&#xff0c;动不动就出现版本不兼容问题&#xff0c;或者下载这个…

Chapter9.5:线性系统的状态空间分析与综合考研参考题

此系列属于胡寿松《自动控制原理题海与考研指导》(第三版)习题精选&#xff0c;仅包含部分经典习题&#xff0c;需要完整版习题答案请自行查找&#xff0c;本系列属于知识点巩固部分&#xff0c;搭配如下几个系列进行学习&#xff0c;可用于期末考试和考研复习。 自动控制原理(…

Java本地高性能缓存的几种实现方式

Java缓存技术可分为远端缓存和本地缓存&#xff0c;远端缓存常用的方案有著名的redis和memcache&#xff0c;而本地缓存的代表技术主要有HashMap&#xff0c;Guava Cache&#xff0c;Caffeine和Encahche。本篇博文仅覆盖了本地缓存&#xff0c;且突出探讨高性能的本地缓存。 本…

美信监控易:网络管理之链路专线管理

专线通常是指运营商为企事业单位提供的专用网络线路&#xff0c;用于满足其业务需求。专线管理可以提供对专线基础信息的维护&#xff0c;以及性能数据的监测能力。通过系统自动地、周期性地执行专线测试&#xff0c;获取指标数据&#xff0c;实现专线连通性、性能数据的全面感…

图数据结构之邻接链表Adjacency List(Python版)

前面学过两种图的数据结构&#xff0c;有兴趣的可以查阅&#xff1a;图数据结构之字典实现(Python版)https://blog.csdn.net/weixin_41896770/article/details/128125901 图数据结构之邻接矩阵Adjacency Matrix(Python版)https://blog.csdn.net/weixin_41896770/article/detai…

中文Stable Diffusion模型太乙使用教程

中文Stable Diffusion模型太乙使用教程 太乙模型介绍 在线体验地址: Stable Diffusion 太乙模型&#xff0c;首个开源的中文Stable Diffusion模型&#xff0c;基于0.2亿筛选过的中文图文对训练。 生成内容一直被视为 AI 领域中最具有挑战性的能力&#xff0c;最近大火的 AI 绘…

文献管理软件Zotero的安装和使用

文章目录前言一、Zotero简介二、安装与使用1、账号注册2、软件安装3、插件安装4、关联账户设置5、坚果云扩充&#xff08;WebDAV&#xff09;6、保存路径设置7、与Connected Papers联动8、参考文献的引用前言 随着阅读文献数量的增加&#xff0c;感觉一个好用的文献管理工具必…

08【SpringMVC的放行规则】

文章目录二、SpringMVC的放行规则2.1 SpringMVC提供的拦截规则2.2 缺省Servlet放行2.3 resources放行2.4 Handler放行2.5 放行规则小结二、SpringMVC的放行规则 2.1 SpringMVC提供的拦截规则 *.form&#xff1a;代表以*.form结尾的后缀请求都会进入springmvc管/&#xff1a;代…

Vue2.0开发之——Vue基础用法-axios(29)

一 概述 axios-使用axios发起基本的Get请求axios-结合async和await调用axiosaxios-使用解构赋值axios-基于axios.get和axios.post发起请求 二 axios-使用axios发起基本的Get请求 2.1 axios介绍 axios(发音&#xff1a;艾克C奥斯)是前端圈最火的、专注于数据库请求的库 在后面…

Linux---awk

Linux三剑客之一awk 简单介绍一下awk的用法 再谈三剑客 grep awk sed 三个并称Linux的三剑客 awk:适合编辑,处理匹配到的文本内容 grep:擅长单纯的查找或匹配文本内容 链接: Linux—grep sed:适合格式化文本内容&#xff0c;对文本进行复杂处理 链接: Linux—sed 文章目录Lin…

如何选择合适的香港物理服务器?

所有企业在部署自己的网络业务时&#xff0c;要有目标&#xff0c;正确的技术&#xff0c;尤其是服务器&#xff0c;可以帮助他们实现这些目标。比如&#xff0c;国内站长开展大型外贸业务又想要国内访问速度快&#xff0c;可以选择合适的香港物理服务器来解决这个问题。那么&a…

天舟系列货运飞船介绍

天舟系列货运飞船是由中国空间技术研究院研制的一款货运飞船&#xff0c;其主要任务是在我国空间站建造及运营期间进行物资运输补给。 天舟系列货运飞船主要用于对中国空间站在轨运行期间&#xff0c;。天舟系列货运飞船包括天舟一号、天舟二号、天舟三号、天舟四号、天舟五号等…