SpringSecurity集成第三方登录

news2024/12/28 8:39:32

SpringSecurity 集成第三方登录

认证及自定义流程

image-20240403104027920

首先我们提供一个实现了AbstractAuthenticationProcessingFilter抽象类的过滤器,用来代替UsernamePasswordAuthenticationFilter逻辑,然后提供一个AuthenticationProvider实现类代替AbstractUserDetailsAuthenticationProvider或DaoAuthenticationProvider,最后再提供一个UserDetailsService实现类。

1.验证码登录

1.通用过滤器实现–ThirdAuthenticationFilter

这个ThirdAuthenticationFilter过滤器我们可以仿照UsernamePasswordAuthenticationFilter来实现(也实现了AbstractAuthenticationProcessingFilter抽象类),主要是重新定义了attemptAuthentication()方法,这里需要根据“authType”参数值的类别构建不同的AbstractAuthenticationToken,具体实现如下:

    //验证类型,比如Sms,uernamepassword等
    private String authTypeParameter = "authType";
    //对应用户名或手机号等
    private String principalParameter = "principal";
    //对应密码或验证码等
    private String credentialsParameter = "credentials";
    private boolean postOnly = true;

    public ThirdAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login/doLogin", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String authType = request.getParameter(authTypeParameter);
        if(StringUtils.isEmpty(authType)){
            authType = AuthTypeEnum.AUTH_TYPE_DEFAULT.getAuthType();
        }
        String principal = request.getParameter(principalParameter);
        String credentials = request.getParameter(credentialsParameter);
        AbstractAuthenticationToken authRequest = null;
        switch (authType){
            case "sms":
                authRequest = new SmsAuthenticationToken(principal, credentials);
                ((SmsAuthenticationToken)authRequest).setCode((String)request.getSession().getAttribute("code"));
                break;
            case "github":
                authRequest = new GithubAuthenticationToken(principal, credentials);
                break;
            case "default":
                authRequest = new UsernamePasswordAuthenticationToken(principal, credentials);
        }
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));

        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

定义了ThirdAuthenticationSecurityConfig 配置类,我们还需要在SpringSecurity配置类中应用才能生效,具体实现如下:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/error","/login/**","/login/goLogin","/login/doLogin","/login/code","/login/authorization_code").anonymous()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login/goLogin")
            .loginProcessingUrl("/login/doLogin")
            .failureUrl("/login/error")
            .permitAll()
            .successHandler(new QriverAuthenticationSuccessHandler("/index/toIndex"));

	//这里我们省略了一些配置 ……

	//应用前面定义的配置
    http.apply(thirdAuthenticationSecurityConfig);
}

至此,我们定义的通用第三方过滤器就完成了,并且也完成了在SpringSecurity中生效的配置。下面我们就开始分别实现不同类型登录的具体过程。

在ThirdAuthenticationFilter 类的attemptAuthentication()方法中,我们通过authType类型,然后创建对应的Authentication实现来实现不同方式的登录,这里我们主要实现了如下三种方式,我们分别梳理一下。

三、默认的登录过程
  默认的登录过程,即根据用户名密码进行登录,需要使用到UsernamePasswordAuthenticationToken,当“authType”参数为"default"时,这里就会创建UsernamePasswordAuthenticationToken对象,然后后续通过ProviderManager的authenticate()方法,最后就会调用AbstractUserDetailsAuthenticationProvider(DaoAuthenticationProvider)的 authenticate()方法,最终又会调用定义的UserDetailsService实现类。这是默认的过程,这里就不再重复其中的逻辑,除了UserDetailsService实现类需要自己定义,其他都是SpringSecurity提供的实现类。

四、短信验证码登录实现
  短信验证码登录,是最贴近用户名密码登录的一种方式,所以我们完全可以仿照用户名密码这种方式实现。我们这里先梳理一下短信验证码登录的业务逻辑:首先,登录界面输入手机号码,然后再点击“获取验证码”按钮获取短信验证码,然后输入收到的短信验证码,最后点击“登录”按钮进行登录认证。和用户名密码登录相比,短信验证码登录多了一个获取验证码的过程,其他其实都是一样的,我们下面逐步实现短信验证码登录:

@RestController
@RequestMapping("/login")
public class SmsValidateCodeController {
	//生成验证码的实例对象
    @Autowired
    private ValidateCodeGenerator smsCodeGenerator;
    //调用服务商接口,发送短信验证码的实例对象
    @Autowired
    private DefaultSmsCodeSender defaultSmsCodeSender;

    @RequestMapping("/code")
    public String createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
        ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));
        String mobile = (String)request.getParameter("principal");
        request.getSession().setAttribute("code",smsCode.getCode());
        defaultSmsCodeSender.send(mobile, smsCode.getCode());
        System.out.println("验证码:" + smsCode.getCode());
        return "验证码发送成功!";
    }
}

在上述方法中,我们注入了smsCodeGenerator和defaultSmsCodeSender两个实例对象,分别用来生成验证码和发送短信验证码,这个可以根据项目的实际情况进行定义和实现,这里不再贴出其中的实现。同时在createSmsCode()方法中,还有一点需要注意的就是,我们发出去的短信验证码需要进行保存,方便后续登录时进行验证,这个也可以选择很多方法,比如说会话、数据库、缓存等,我这里为了简单,直接存到了session会话中了。

然后,我们前面定义ThirdAuthenticationFilter过滤器时,根据登录方式不同,需要对应的Authentication对象,这里我们还需要创建短信验证登录需要的Authentication类,这里我们可以仿照UsernamePasswordAuthenticationToken类进行编写,实现如下

public class SmsAuthenticationToken  extends AbstractAuthenticationToken {

    //对应手机号码
    private final Object principal;
    //对应手机验证码
    private Object credentials;

    //后台存储的短信验证码,用于验证前端传过来的是否正确
    private String code;

    public SmsAuthenticationToken(String mobile, Object credentials){
        super(null);
        this.principal = mobile;
        this.credentials = credentials;
        this.code = code;
        setAuthenticated(false);
    }

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

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

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

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }
}

在SmsAuthenticationToken 类中,我们增加了一个code属性,其实该属性不是必须的,我这里是为了方便传递存储在session会话中的验证码而添加的,如果使用缓存或数据库进行存储验证码,该属性就可以省略。

在AuthenticationManager的authenticate()方法中,会根据Authentication类型选择AuthenticationProvider对象,所以我们这里自定义短信验证码需要的AuthenticationProvider对象,实现如下:

@Component
public class SmsAuthenticationProvider implements AuthenticationProvider{

    @Autowired
    @Qualifier("smsUserDetailsService")
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsAuthenticationToken token = (SmsAuthenticationToken) authentication;
        String mobile = (String)token.getPrincipal();
        //首先,验证验证码是否正确
        String code = (String)token.getCredentials();
        String sCode = token.getCode();
        if(StringUtils.isEmpty(code) || !code.equalsIgnoreCase(sCode)){
            throw new BadCredentialsException("手机验证码错误(Bad credentials),请重试!");
        }
        //然后,查询对应用户
        UserDetails user = userDetailsService.loadUserByUsername(mobile);
        if (Objects.isNull(user)) {
            throw new InternalAuthenticationServiceException("根据手机号:" + mobile + ",无法获取对应的用户信息!");
        }
        SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials());
        authenticationResult.setDetails(token.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

在SmsAuthenticationProvider 中,supports()方法决定了该实例对象仅支持SmsAuthenticationToken对象的验证。同时,根据authenticate()方法传递参数authentication对象(包括了登录信息:手机号和验证码,session存储的验证码),我们这里session存储的验证码,是因为我们采用了会话存储的方式,如果使用数据库,我们这里就可以通过手机号,去数据库或缓存查询对应的验证码,然后和authentication对象传递过来的验证码进行比对,验证成功,说明登录认证成功,否则登录认证失败。登录成功后,我们就可以调用userDetailsService对象的loadUserByUsername()方法获取登录用户的其他相关信息(权限等),具体实现在自定义的SmsUserDetailsService类中实现,具体如下:

@Component("smsUserDetailsService")
public class SmsUserDetailsService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(SmsUserDetailsService.class);

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1、查询用户信息
        SysUser user = new SysUser();
        user.setMobile(username);
        SysUser qUser = sysUserService.getOne(new QueryWrapper<>(user),true);
        if(qUser == null) {
            logger.info("手机号为”" + username + "“的用户不存在!!!");
            throw new UsernameNotFoundException("手机号为”" + username + "“的用户不存在!!!");
        }
        //2、封装用户角色
        UserRole userRole = sysUserService.getRoleByUserId(qUser.getId());
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId())));
        return new LoginUser(qUser.getUsername(), qUser.getPassword(),authorities);
    } 
}

2.GitHub登录

和短信验证码登录认证相比,Github登录又会有自己的特殊性,我们这里先梳理一下基于Github进行登录验证的大致逻辑:首先,点击Github登录认证按钮,然后会跳转到github登录界面,输入github系统的用户名密码,登录成功,就会跳转到我们自己的系统中的首页。和基于用户名密码的登录方式相比,Github登录不需要类似用户名和密码这样的输入(在自己的系统中),同时又需要根据获取到的github用户信息,换取在自己系统对应的用户信息。具体实现步骤如下:

在github的配置省略

@Controller
@RequestMapping("/login")
public class GithubValidateController {

    @Autowired
    private GithubClientService githubClientService;

    @RequestMapping("/authorization_code")
    public void authorization_code(HttpServletRequest request, HttpServletResponse response, String code) throws ServletRequestBindingException, IOException {
        //github登录验证,并获取access_token
        Map<String,String> resp = githubClientService.queryAccessToken(code);
        //跳转本系统的登录流程,获取用户信息,实现两个系统用户的对接
        String url = "http://localhost:8888/qriver-admin/login/doLogin";
        this.sendByPost(response, url,resp.get("access_token"),"github");
        //this.sendByPost(response, url,"access_token","github");
    }

    public void sendByPost(HttpServletResponse response,String url, String principal, String authType) throws IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
        out.println("<HTML>");
        out.println(" <HEAD><TITLE>Post 方法</TITLE></HEAD>");
        out.println(" <BODY>");
        out.println("<form name=\"submitForm\" action=\"" + url + "\" method=\"post\">");

        out.println("<input type=\"hidden\" name=\"principal\" value=\"" + principal + "\"/>");
        out.println("<input type=\"hidden\" name=\"authType\" value=\"" + authType + "\"/>");
        out.println("</from>");
        out.println("<script>window.document.submitForm.submit();</script> ");
        out.println(" </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();
    }

}

“/login/authorization_code”接口对应了我们在Github中配置的回调函数,即在Github登录验证成功后,就会回调该接口,我们就是就在回调方法中,模拟了用户名密码登录的方式,调用了SpringSecurity登录认证需要的“/login/doLogin”接口。这里,我们通过queryAccessToken()方法根据回调传递的code获取对应的accessToken,然后把accessToken作为登录使用的principal 参数值,之而立不需要传递密码,因为我们经过Github授权,就可以认为完成了登录认证的判断过程了。

其中GithubClientService类,提供了获取accessToken和用户信息的两个方法,具体实现方式如下:

@Service
public class GithubClientService {
	//前面在github中配置时产生的
    private String clientId = "######";
    private String clientSecret = "######";
    private String state = "123";
    private String redirectUri = "http://localhost:8888/qriver-admin/login/authorization_code";

    @Autowired
    private RestTemplate restTemplate;

    @Nullable
    private WebApplicationContext webApplicationContext;
    
	//获取accessToken
    public Map<String, String> queryAccessToken(String code ){
        Map<String, String> map = new HashMap<>();
        map.put("client_id", clientId);
        map.put("client_secret", clientSecret);
        map.put("state", state);
        map.put("code", code);
        map.put("redirect_uri", redirectUri);
        Map<String,String> resp = restTemplate.postForObject("https://github.com/login/oauth/access_token", map, Map.class);
        return resp;
    }
	//获取用户信息
    public Map<String, Object> queryUser(String accessToken){
        HttpHeaders httpheaders = new HttpHeaders();
        httpheaders.add("Authorization", "token " + accessToken);
        HttpEntity<?> httpEntity = new HttpEntity<>(httpheaders);
        ResponseEntity<Map> exchange = restTemplate.exchange("https://api.github.com/user", HttpMethod.GET, httpEntity, Map.class);
        System.out.println("exchange.getBody() = " + exchange.getBody());
        return exchange == null ? null : exchange.getBody();
    }
}

其实,完成了上述的配置和方式后,后续的方式就和短信验证码的逻辑一样了,这里我们简要的再梳理一下。

首先,我们也需要定义一个基于Github登录需要的Authentication实现类,具体实现和前面的SmsAuthenticationToken类似,这里不再重复贴代码了。

然后,我们再定义一个AuthenticationProvider实现类GithubAuthenticationProvider,具体实现如下:

@Component
public class GithubAuthenticationProvider implements AuthenticationProvider{

    @Autowired
    @Qualifier("githubUserDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private GithubClientService githubClientService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        GithubAuthenticationToken token = (GithubAuthenticationToken) authentication;
        String accessToken = (String)token.getPrincipal();
        //根据accessToken 获取github用户信息
        Map<String, Object> userInfo = githubClientService.queryUser(accessToken);
        //然后,根据github用户,查询对应系统用户信息
        UserDetails user = userDetailsService.loadUserByUsername((String)userInfo.get("login"));
        if (Objects.isNull(user)) {
            throw new InternalAuthenticationServiceException("根据accessToken:" + accessToken + ",无法获取对应的用户信息!");
        }
        GithubAuthenticationToken authenticationResult = new GithubAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials());
        authenticationResult.setDetails(token.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return GithubAuthenticationToken.class.isAssignableFrom(authentication);
    }
}


在GithubAuthenticationProvider 类的authenticate()方法中,参数authentication中对应的是Github授权后传递的accessToken值,我们这里需要根据accessToken值换取Github用户信息,这里通过queryUser()方法实现,然后根据github用户名去获取对应的系统用户信息。如果根据github用户名用户获取的系统用户为空,我们可以根据自己的需求,自动生成一个用户或者跳转到注册页面,让用户注册一个页面,这里为了简单,我们直接抛出了一个异常。

关于自定义UserDetailsService实现类,主要需要实现根据github用户名查询对应系统用户的功能

当认证完成后要返回token可以实现AuthenticationSuccessHandler

import org.springframework.security.core.Authentication;  
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
  
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {  
  
    private final JwtTokenProvider jwtTokenProvider; // 假设你有一个JwtTokenProvider类来生成JWT  
  
    public CustomAuthenticationSuccessHandler(JwtTokenProvider jwtTokenProvider) {  
        this.jwtTokenProvider = jwtTokenProvider;  
    }  
  
    @Override  
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {  
        // 生成JWT  
        String token = jwtTokenProvider.generateToken(authentication);  
  
        // 将JWT添加到响应头中  
        response.setHeader("Authorization", "Bearer " + token);  
  
        // 或者将JWT添加到响应体中(取决于你的API设计)  
        // response.getWriter().write(token);  
  
        response.setStatus(HttpServletResponse.SC_OK);  
    }  
}

并在securityconfig中设置

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

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

相关文章

【数据结构】浅谈

✨✨✨专栏&#xff1a;数据结构 &#x1f9d1;‍&#x1f393;个人主页&#xff1a;SWsunlight 目录 一、概念&#xff1a; 二、物理结构&#xff1a; 1、顺序存储结构&#xff1a; 2、链式存储结构&#xff1a; 3、数据索引存储结构: 4、数据散列存储结构&#xf…

数学学习笔记1——二次函数中的数形结合

二次函数中的数形结合 一、解一元二次不等式 基本方法&#xff1a;配方。 x 2 − 4 x 3 < 0 → ( x − 2 ) 2 < 1 → ∣ x − 2 ∣ < 1 → 1 < x < 3 x^2-4x3<0\to(x-2)^2<1\to\lvert x-2\rvert<1\to1<x<3 x2−4x3<0→(x−2)2<1→∣x−…

璩静是为了薅百度羊毛

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 百度副总裁璩静离职了&#xff0c;网传她的年薪是1500万&#xff0c;而璩静在4月24日注册了一个文化传媒公司&#xff0c;大家都认为璩静是在为离职做准备。但松松我认为不是。 我认为&#xff1a;璩静成立新公司是…

【ORACLE战报】2024.4月最新OCP考试喜报.

课程介绍 DBA数据库管理必备认证&#xff1a;ORACLE OCP 19C 教材下载 ORACLE OCP 19C 官方电子教材 ORACLE OCP 12C官方电子教材 题库下载 ORACLE 19C题库 &#xff08;083384题、082362题&#xff09;-2024答案修正版.rar 所有的收获都是默默耕耘的成果 2024.4月【最新考试成…

苍穹外卖项目---------收获以及改进(9-12)

①Spring Task-------实现系统定时任务 概念&#xff1a; 应用场景&#xff1a; 使用步骤&#xff1a; 实现订单超时和前一天派送中的订单的自动任务处理&#xff1a; Component Slf4j public class Mytask {Autowiredprivate OrderServiceimpl orderServiceimpl;/*** 处理订…

parallels desktop19最新免费Mac电脑虚拟机软件

Parallels Desktop是一款运行在Mac电脑上的虚拟机软件&#xff0c;它允许用户在Mac系统上同时运行多个操作系统&#xff0c;比如Windows、Linux等。通过这款软件&#xff0c;Mac用户可以轻松地在同一台电脑上体验不同操作系统的功能和应用程序&#xff0c;而无需额外的硬件设备…

C++入门——命名空间、缺省参数、函数重载、引用、内敛函数、auto关键字

目录 前言 一、什么是C 1.1 C关键字(C98) 二、命名空间 2.1 命名空间定义 1.正常命名空间的定义 2.命名空间的定义可以嵌套 3.同名的命名空间会合并 2.2 命名空间的使用 三、C输入&输出 四、缺省参数 4.1 缺省参数概念 4.2 缺省参数分类 五、函数重载 5.1 …

N5183B是德科技n5183b信号源

181/2461/8938产品概述&#xff1a; 简  述&#xff1a; N5183B 频率范围&#xff1a;9 kHz 至 20 GHz&#xff0c;具有 AM、FM、相位调制功能。N5183B MXG X 系列微波模拟信号发生器拥有 9 kHz 至 40 GHz 的频率覆盖范围&#xff0c;以及接近 PSG 级别的相位噪声性能&…

简洁大气APP下载单页源码

源码介绍 简洁大气APP下载单页源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 效果截图 源码下载 简洁大气APP下载单页源码

kali搭建Vulhub靶场

简单概述 Vulhub是一个面向大众的开源漏洞靶场&#xff0c;借助Docker简单执行两条命令即可编译、运行一个完整的漏洞靶场镜像。旨在让漏洞复现变得更加简单&#xff0c;让安全研究者更加专注于漏洞原理本身。 Docker是一个开源的容器引擎&#xff0c;它有助于更快地交付应用…

LeetCode 105.从前序与中序遍历序列构造二叉树

LeetCode 105.从前序与中序遍历序列构造二叉树 1、题目 题目链接&#xff1a;105. 从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树…

conan2 基础入门(04)-指定编译器(gcc为例)

conan2 基础入门(04)-指定编译器(gcc为例) 文章目录 conan2 基础入门(04)-指定编译器(gcc为例)⭐准备生成profile文件预备文件和Code ⭐使用指令预览正确执行结果可能出现的问题 ⭐具体讲解conancmake ENDsettings.yml ⭐准备 生成profile文件 # 生成默认profile文件&#xf…

深入理解Linux下的网络监控工具:iftop

目录标题 1. 什么是iftop?2. 安装iftop在Debian/Ubuntu上安装在CentOS/RHEL上安装在其他Linux发行版上 3. 使用iftop监控网络流量命令行选项界面说明交互命令 4. 相关参数及说明 在维护和监控Linux服务器时&#xff0c;了解网络流量的细节非常重要。网络监控可以帮助我们诊断延…

面向电商家居行业3D室内场景合成中的空间感知

本文主要介绍了3D场景合成技术在电商领域&#xff0c;尤其是家居家装行业的应用。它解释了如何使用3D场景合成创建逼真的室内设计&#xff0c;让消费者能够交互式地查看和体验产品&#xff0c;提高购物的趣味性和效率。文章提到了两种主要的3D室内场景生成算法&#xff1a;传统…

软考-软件工程

软件工程概述 软件工程指的是应用计算机科学、数学及管理科学等原理&#xff0c;以工程化的原则和方法来解决软件 问题的工程&#xff0c;目的是提高软件生产率、提高软件质量、降低软件成本。 概述&#xff1a; 软件开发模型&#xff1a;指导软件开发的体系 需求分析确定软件…

力扣127.单词接龙讲解

距离上一次刷题已经过去了.........嗯............我数一一下............整整十天&#xff0c;今天再来解一道算法题 由于这段时间准备简历&#xff0c;没咋写博客。。今天回来了&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

ctfshow SSRF 351-358

做题前,需要先学习关于ssrf漏洞的相关知识 小注意: 当使用 file_get_contents() 函数访问远程 URL 时&#xff0c;它会尝试获取该 URL 指向的资源的内容&#xff0c;并将内容以字符串的形式返回。 如果 b.php 文件是一个 PHP 文件&#xff0c;它包含的内容取决于该 PHP 文件…

Visual Studio,第1个hello world,入门C++,分别编译一个可以在Windows和Linux下运行的程序

本人的VxTerm&#xff0c;是在Visual Studio 2022下编写的。 其它的语言工具是不是也可以那么方便的使用&#xff0c;本人并不得而知&#xff0c;至少本人能知道&#xff1a;对于我来说&#xff0c;Visual Studio可以让我觉得C/C语言非常简单&#xff01; 一、安装Visual Stu…

D - Another Sigma Problem(ABC)

思路&#xff1a;我们可以处理一个后缀来记录当前数a[i]需要乘上多少&#xff08;类似于1110这样的&#xff09;&#xff0c;然后对于当前位来说&#xff0c;对答案的贡献还要加上(i - 1) * a[i]&#xff0c;因为a[i]还要做前(i - 1)个数的后缀。 代码&#xff1a; #include &…

基于FPGA的NC图像质量评估verilog实现,包含testbench和MATLAB辅助验证程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 vivado2019.2和matlab2022a测试&#xff0c;结果如下&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 timescale …