Spring Security的账号密码登录+Azure AD的授权登录集成Demo

news2024/12/22 15:31:40

一、项目准备:

1.创建一个Springboot项目。

2.注册一个微软的Azure AD服务,并且注册应用,创建用户。

springboot项目pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/>
    </parent>

    <groupId>com.framework</groupId>
    <artifactId>security-azure-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
        <spring-cloud-azure.version>4.7.0</spring-cloud-azure.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!-- 引入前端模板依赖   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.azure.spring</groupId>
            <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
        </dependency>

        <!--引入jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--数据库链接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- 日志系统 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.azure.spring</groupId>
                <artifactId>spring-cloud-azure-dependencies</artifactId>
                <version>${spring-cloud-azure.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

二、构建SpringSecurityConfig

这里在HttpSecurity需要配置常规登录选项,并且同时使用oauth2Login登录选项。

1.在authorizationManagerBuilder中构建自定义的一个Provider。

2.在httpSecurity构建常规账号密码登录的选项。

3.在httpSecurity构建oauth2login授权登录选项。

4.在httpSecurity构建Oauth2LoginConfigurer,并且实现自定义实现Oauth2UserService,来完成用户角色权限的构建。

5.在httpSecurity添加授权认证成功后的handler实现,用于重定向授权后的登录成功接口。

代码如下:

/**
 * @Author: LongGE
 * @Date: 2023-05-12
 * @Description:
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 在授权成功后查询本地数据库用户以及角色和权限信息。
     */
    @Autowired
    private CustomOidcService customOidcService;

    /**
     * 自定义的provider,用于账号密码登录
     */
    @Autowired
    private CustomDaoAuthenticationProvider customDaoAuthenticationProvider;

    /**
     * 自定义在授权成功后,控制授权登录成功后跳转本地项目的页面和接口,并且也可以用于添加session和cookie
     */
    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    /**
     * 密码校对验证器
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 构建manager认证器
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 添加自定义的provider,通过自定义的provider可以实现不同的账号密码登录
     * @param authenticationManagerBuilder
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(customDaoAuthenticationProvider);
    }

    /**
     * 构建HttpSecurity 认证
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/login/oauth2/code/azure").permitAll()
                .antMatchers("/AuthLoginController/**").permitAll()
                .anyRequest().authenticated()
                .and()
                //构建UsernamePasswordAuthenticationFilter拦截器
                .formLogin()
                .loginPage("/login").permitAll()
                .and()
                //构建OAuth2LoginConfigurer,用于OAuth2Login授权登录
                .oauth2Login()
                .loginPage("/login").permitAll()
                //授权服务器UserInfo端点的配置选项。
                .userInfoEndpoint()
                //添加一个自定义的OAuth2UserService,用于实现授权成功后对用户信息和角色权限信息的封装
                .oidcUserService(customOidcService)
                .and()
                //添加一个Handler,用于授权成功后,对跳转登录成功后的重定向页面进行指向,也可以用于添加授权登录成功的sessionID和Cookie
                .successHandler(customAuthenticationSuccessHandler);
    }

    /**
     * 过滤静态页面和图片信息,不让Filter拦截
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/assets/images/**");
    }
}

三、自定义CustomDaoAuthenticationProvider

自己实现AuthenticationProvider接口,这样可以根据自己传入的不同TAuthenticationToken去执行自己定义Provider,可以更加灵活自主的实现登录业务逻辑。

/**
 * @Author: LongGE
 * @Date: 2023-04-10
 * @Description:
 */
@Component
@Slf4j
public class CustomDaoAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailsServiceImpl customUserDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        CustomUserDetails customUserDetails = (CustomUserDetails) customUserDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
        CustomDaoUsernameToken customDaoUsernameToken = new CustomDaoUsernameToken(customUserDetails,null, customUserDetails.getAuthorities());
        return customDaoUsernameToken;
    }

    /**
     * As a business judgment, built in the controller,
     * the judgment is made here so that you can call the AuthenticationProvider that encapsulates the corresponding one in ProviderManeger
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return CustomDaoUsernameToken.class.isAssignableFrom(authentication);
    }
}

四、自定义CustomDaoUsernameToken

继承AbstractAuthenticationToken抽象类,自己定义一个AuthenticationToken类,这样在登录时候调用authenticate()方法时候传入自己定义的AuthenticationToken就可以,这样ProviderManager类就会自动匹配自定义的Provider去实现登录认证逻辑。

/**
 * @Author: LongGE
 * @Date: 2023-04-10
 * @Description:
 */
public class CustomDaoUsernameToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    private Object credentials;

    public CustomDaoUsernameToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

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


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

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

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated,
                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

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

五、自定义CustomUserDetailsServiceImpl

自定义的登录认证,实现UserDetailService接口,在provider中会调用自定义的CustomUserDetailsServiceImpl类的loadUserByUsername()方法来认证账号是否存在并且查询用户角色以及权限信息,并且封装到了Security的上下文中,后续方法可以直接在上线文中回去这些用户信息。

@Service
public class CustomUserDetailsServiceImpl implements UserDetailsService {

	private static final Logger LOGGER = LoggerFactory.getLogger(CustomUserDetailsServiceImpl.class);

	@Autowired
	private SystemUserDao systemUserDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws BadCredentialsException {

		LOGGER.debug("CustomUserDetailsServiceImpl: " + ":loadUserByUsername()={}", username);

		User user = new User();
		Set<Authority> hasAuthority = new HashSet<>();
		SystemUser systemUser = systemUserDao.queryByUsername(username);
		user.setId(systemUser.getId());
		user.setUsername(username);
		user.setEnabled(true);
		user.setAuthorities(hasAuthority);
		return new CustomUserDetails(user);
	}
}

六、自定义CustomOidcService

在AzureAD授权认证后,返回给我们用户信息,由OAuth2LoginAuthenticationFilter拦截器拦截,调用attemptAuthentication()方法,在此方法中会获取ProviderManager类,在调用ProviderManager的authenticate()方法进行认证,传入的参数是OAuth2LoginAuthenticationToken类型的token,在封装在ProviderManager中只有OidcAuthorizationCodeAuthenticationProvider类满足认证条件,在此provider的authenticate()方法中会调用自定义的CustomOidcService类的loadUser()方法进行认证,传入的参数是OidcUserRequest类型,在这里通过userRequest.getIdToken();方法获取OidcIdToken,这里封装AzureAD中的基础用户信息,通过用户信息去数据库查询用户角色和权限,将角色和权限封装到Security的上下文中,并且也可以封装到redis等缓存中,方便后续使用。

/**
 * @Author: LongGE
 * @Date: 2023-05-15
 * @Description:
 */
@Slf4j
@Service
public class CustomOidcService implements OAuth2UserService<OidcUserRequest, OidcUser> {

    @Autowired
    private SystemUserDao systemUserDao;

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        OidcIdToken idToken = userRequest.getIdToken();
        log.info("打印请求参数: {}",idToken);
        Set<String> authorityStrings = new HashSet<>();
        Set<SimpleGrantedAuthority> authorities = authorityStrings.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toSet());
        SystemUser systemUser = systemUserDao.queryByUsername(userRequest.getIdToken().getPreferredUsername());
        CustomOidcUser customOidcUser = new CustomOidcUser(authorities, idToken, systemUser);
        return customOidcUser;
    }
}

七、自定义CustomAuthenticationSuccessHandler

在第六步认证成功后,AbstractAuthenticationProcessingFilter拦截器,会调用AuthenticationSuccessHandler接口的successfulAuthentication()方法,自定义的CustomAuthenticationSuccessHandler类是实现了这个接口的successfulAuthentication()方法,实现此方法主要是用户在用户通过AzureAD授权登录成功后,可以控制用户去加载登录成功后的浏览页面,并且还需要给前端返回的Response中添加Http请求头中添加cookie,这样以后前端每次访问后端接口,都携带此cookie那么就可以通过拦截器去确认用户是否登录。

/**
 * @Author: LongGE
 * @Date: 2023-05-22
 * @Description:    用户认证成功后处理后续重定向操作的
 * Strategy used to handle a successful user authentication.
 * <p>
 * Implementations can do whatever they want but typical behaviour would be to control the
 * navigation to the subsequent destination (using a redirect or a forward). For example,
 * after a user has logged in by submitting a login form, the application needs to decide
 * where they should be redirected to afterwards (see
 * {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be
 * included if required.
 */
@Service
@Slf4j
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        onAuthenticationSuccess(request, response, authentication);
        chain.doFilter(request, response);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        CustomOidcUser customOidcUser = (CustomOidcUser)authentication.getPrincipal();
        SystemUser user = customOidcUser.getSystemUser();
        // Session ID
        String sessionId = UUID.randomUUID().toString();
        Map<String,Object> tokenClaims = new HashMap<>();
        tokenClaims.put("SessionId", sessionId);

        //Create token
        //Token newAccessToken = tokenProvider.generateAccessToken(user.getUsername(), tokenClaims, authentication, tokenExpirationSec);
        //Enter token log
        //customBaseService.logToken(newAccessToken);

       /* if(user != null && user.getId() != null) {
            //Add Session Id to UserSession DB
            customBaseService.addUserSession(user.getId(), sessionId, request);
            //Add Redis cache with expiration time
            customBaseService.addRedisUserSession(user.getId(), user.getUsername());
        }
        //Set the redirect path and add the token cache to the cookie
        response.addHeader("Set-Cookie", cookieUtil.createAccessTokenCookie(newAccessToken.getTokenValue(),
                newAccessToken.getDuration()).toString());*/
        response.sendRedirect("/index");
    }
}

八、登录页面

登录页面支持简单的账号密码登录,同时也支持AzureAD的授权方式登录。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/" lang="en">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- Tell the browser to be responsive to screen width -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon.png">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>用户登录</h1>
        <!--th:action="@{/AuthLoginController/doLogin}" method="post"-->
        <form id="loginform">
            <div id="divError" class="input-group mb-12 aui-message aui-message-error" style="display: none">
                <span style="color: red" id="errorMessage"></span>
            </div>
            <div class="input-group mb-3">
                <div class="input-group-prepend">
                    <span class="input-group-text" id="basic-addon1">用户名:</span>
                </div>
                <input type="text" class="form-control form-control-lg"
                       placeholder="Username" id="username" name="username"
                       aria-label="username" aria-describedby="basic-addon1" required>
            </div>
            <div class="input-group mb-3">
                <div class="input-group-prepend">
                    <span class="input-group-text" id="basic-addon2">密码:</span>
                </div>
                <input type="password" class="form-control form-control-lg"
                       placeholder="Password" id="password" name="password"
                       aria-label="Password" aria-describedby="basic-addon1" required>
            </div>
            <div class="form-group text-center">
                <div class="col-xs-12 pb-3">
                    <!-- input class="btn btn-block btn-lg btn-info" type="submit" value="Log in" /-->
                    <button id="ldaploginbtn" class="btn btn-block btn-lg btn-info"
                            type="button">LDAP Log in</button>
                </div>
            </div>
        </form>
        <br/>
        <br/>
        <br/>
        <!--<form th:action="@{/oauth2/authorization/uuc}" method="post">
            <input type="submit" value="UUC登录">
        </form>-->
        <br/>
        <br/>
        <br/>
        <form th:action="@{/oauth2/authorization/azure}" method="post">
            <input type="submit" value="AzureAD授权登录">
        </form>
    </body>
    <script src="/assets/libs/jquery/dist/jquery.min.js"></script>
    <script src="/assets/libs/bootstrap/dist/js/bootstrap.min.js"></script>
    <script src="/assets/libs/jquery/dist/jquery.serializejson.js"></script>
    <script src="/js/common/login.js"></script>
</html>

login.js的js代码:

$(document).ready(function() {

	document.getElementById("password").addEventListener("keyup", function(event) {
		if (event.keyCode === 13) {
			$('#loginbtn').click();
			return false;
		}
	});

	//LDAP Login
    $('#ldaploginbtn').click(function() {
        $('#errorMessage').text('');
        $('#divError').hide();

        //Check account password
        let $name=$('#username');
        let $pwd=$('#password');
        // 按钮点击后检查输入框是否为空,为空则找到span便签添加提示
        if ($name.val().length===0 || $name.val() == ("") || $pwd.val().length===0 || $pwd.val() == ("")) {
            $('#errorMessage').text('Please fill in the account password!');
            $('#divError').show();
        }else {
            var formData = $("#loginform").serializeJSON();
            var jsonData = JSON.stringify(formData);

            $.ajax({
                url: "AuthLoginController/doLogin",
                type: 'POST',
                data: jsonData,
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function(data) {
                    if (data.status == "SUCCESS") {
                        console.log("登录成功返回!")
                        window.location.href = data.redirectPath;//"/index";
                    } else {
                        $('#errorMessage').text(data.message);
                        $('#divError').show();
                    }
                },
                error: function(xhr, ajaxOptions, thrownError) {
                    swalexceptionhandler(xhr.status, xhr.responseText);
                }
            });
        }
    });
});

function swalexceptionhandler(status, responseText) {
    if (status == "412" || status == "422") {
        var obj = JSON.parse(responseText);
        var displaymsg = "";
        for (let i = 0; i < obj.errors; i++) {
            displaymsg += obj.errorInfo[i].errCode + ":" + obj.errorInfo[i].errDescription + " (" + obj.errorInfo[i].errField + ")" + "<br>";
        }
        //swal('Validation', displaymsg, 'warning');
    } else {
        //swal('Exception', responseText, 'error');
    }
}

九、登录接口AuthLoginController与LoginController

LoginController:主要加载登录页面和登录成功页面。

AuthLoginController:处理简单的账号密码登录请求逻辑。

代码分别如下:

/**
 * @Author: LongGE
 * @Date: 2023-05-19
 * @Description:
 */
@Controller
@Slf4j
public class LoginController {

    @RequestMapping("/login")
    public String loginHtml(){
        return "login";
    }

    @RequestMapping("/index")
    public String indexHtml() {
        log.info("发送请求违背拦截!");
        return "index";
    }
}
/**
 * @Author: LongGE
 * @Date: 2023-05-12
 * @Description:
 */
@RestController
@RequestMapping("/AuthLoginController")
@Slf4j
public class AuthLoginController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private ServletContext context;

    @PostMapping("/doLogin")
    public ResponseEntity<LoginResponse> auth(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
        log.info("开始登录! username={}, password={}", loginRequest.getUsername(), loginRequest.getPassword());
        Authentication authentication = authenticationManager.authenticate(
                new CustomDaoUsernameToken(loginRequest.getUsername(), loginRequest.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        log.info("登录成功! {}", authentication);
        HttpHeaders responseHeaders = new HttpHeaders();
        String loginPath = context.getContextPath() + "/index";
        LoginResponse loginResponse = new LoginResponse(LoginResponse.SuccessFailure.SUCCESS, "Auth successful. Tokens are created in cookie.", loginPath);
        return ResponseEntity.ok().headers(responseHeaders).body(loginResponse);
    }
}

总结:

附一张授权登录的基础流程图:

 

 

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

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

相关文章

广播风暴的成因以及如何判断、解决

广播风暴&#xff08;broadcast storm&#xff09;简单的讲是指当广播数据充斥网络无法处理&#xff0c;并占用大量网络带宽&#xff0c;导致正常业务不能运行&#xff0c;甚至彻底瘫痪&#xff0c;这就发生了“广播风暴”。一个数据帧或包被传输到本地网段 &#xff08;由广播…

ObjectARX如何判断点和多段线的关系

目录 1 基本思路2 相关知识点2.1 ECS坐标系概述2.2 其他点坐标转换接口2.3 如何获取多段线的顶点ECS坐标 3 实现例程3.1 接口实现3.2 测试代码 4 实现效果 在CAD的二次开发中&#xff0c;点和多段线的关系是一个非常重要且常见的问题&#xff0c;本文实现例程以张帆所著《Objec…

Vue事件大小写驼峰命名导致无法执行问题解决

文章目录 问题解决方案问题大致原因 问题 驼峰命名事件名不会正常执行 <!DOCTYPE html> <html lang""> <head><title>Vue Emit Example</title><script src"../js/vue.js"></script> </head> <body…

港科夜闻|香港科技大学与浪潮集团签署战略合作协议,共同推动技术研发和成果转化...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科技大学与浪潮集团签署战略合作协议&#xff0c;共同推动技术研发和成果转化。根据协议&#xff0c;双方将聚焦云计算、大数据、新一代通信等领域&#xff0c;围绕联合研发、人才培养、研发中心建设和超高清显示等方…

基于车站约束的地铁系统协调客流控制模型与算法

1 文章信息 《Model and algorithm of coordinated flow controlling with station-based constraints in a metro system》是2021年发表在Transportation Research Part E上的一篇文章。 2 摘要 随着城市人口的增长和交通需求的快速增长&#xff0c;世界上许多大城市的地铁系统…

Python3数据分析与挖掘建模(7)使用matplotlib和seaborn画图

1. 可视化分析 1.1 概述 可视化分析是数据分析中重要的一环&#xff0c;它可以帮助我们更直观地理解数据的特征、趋势和关系。在Python中&#xff0c;有多个库可以用于数据可视化&#xff0c;包括matplotlib、seaborn和plotly等。 1.2 常用的可视化方法和对应的库&#xff1…

4.3. 缓冲流

缓冲流是Java I/O中的一个重要概念&#xff0c;它可以提高文件读写的性能。在本节中&#xff0c;我们将详细讨论缓冲流的概念、使用方法以及实例。 缓冲流有两种类型&#xff1a;缓冲字节流和缓冲字符流。缓冲字节流包括BufferedInputStream和BufferedOutputStream&#xff0c…

vue中this.$set的用法

this.$set( target, key, value ) target&#xff1a;要更改的数据源(可以是对象或者数组) key&#xff1a;要更改的具体数据 value &#xff1a;重新赋的值 当我们给对象加了一个属性&#xff0c;在控制台能打印出来&#xff0c;但是却没有更新到视图上时&#xff0c;这个时…

【靶场】双重内网渗透测试场景

文章目录 前言一、开始渗透二、横向移动提交flag总结 前言 使用vulfocus搭建一个内网场景靶场拓扑如下&#xff1a; 入口有两个&#xff0c;一个是think PHP2.x命令执行和5x的命令执行漏洞&#xff0c;后续需要搭建二层隧道进行渗透测试。 一、开始渗透 目标&#xff1a; …

Vue为什么组件销毁后定时器会继续

原因 在 Vue 中&#xff0c;组件销毁后定时器可能会继续运行&#xff0c;这是因为这个框架使用了虚拟 DOM 技术。虚拟 DOM 可以提高渲染效率和性能&#xff0c;但也带来了一些问题。 当我们在 Vue 组件中创建定时器时&#xff0c;实际上是在组件的生命周期方法&#xff08;例如…

OJ练习第124题——叶值的最小代价生成树

叶值的最小代价生成树 力扣链接&#xff1a;1130. 叶值的最小代价生成树 题目描述 给你一个正整数数组 arr&#xff0c;考虑所有满足以下条件的二叉树&#xff1a; 每个节点都有 0 个或是 2 个子节点。 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。 每个非叶节…

Arcgis for javascript 应用开发相关网站推荐(SDK,github社区等)

一、什么是arcgis for javascript ArcGIS for JavaScript是一种用于构建基于Web的GIS应用程序的开发框架。它允许开发人员使用Esri的地图和地理空间数据来构建具有交互性和可视化效果的应用程序。ArcGIS for JavaScript提供了丰富的API和组件&#xff0c;使开发人员可以将地理…

BLCY-6-5-90、、BLCY-6-25-90比例螺纹插装式溢流阀控制器

BLCY-6-5-90、BLCY-6-8-90、BLCY-6-16-90、BLCY-6-25-90比例螺纹插装式溢流阀是螺纹插装式的先导式溢流阀&#xff0c;可以作中小流量液压系统的压力控制阀&#xff0c;配置比例放大器输出电流&#xff0c;根据输入到线圈电流的大小比例控制系统压力。

【Rust 日报】2023-05-28 一个构建在TCP上的聊天工具

tcp-chat&#xff1a;构建在TCP上的简单快速轻量的聊天工具 tcp-chat通过TCP进行通信&#xff0c;该项目的目的是了解并行性和底层网络通信。 前端工具&#xff1a;Solid、Tauri、Vite 后端工具&#xff1a;Rust、Tokio、Serde GitHub: https://github.com/gatomod/tcp-chat ez…

信号链基础

信号链&#xff08;SIGNAL CHAIN&#xff09;&#xff1a;一个系统中信号从输入到输出的路径。 从信号的采集&#xff0c;放大&#xff0c;传输&#xff0c;处理一直到对相应功率器件产生执行的一整套信号流程叫信号链。具体来说&#xff0c;信号链是对从信号采集&#xff08;传…

WebGPU:下一代 Web 图形和计算 API

WebGPU 是一种新兴的 Web 标准&#xff0c;旨在为现代图形和计算应用提供高性能、低功耗的 API。本文将介绍 WebGPU 的背景、特点、用途以及和 WebGL 的对比。 一、背景 随着 Web 技术的不断发展&#xff0c;越来越多的高性能图形和计算应用开始出现在浏览器中。WebGL 是迄今为…

陈丹琦团队提出低内存高效零阶优化器MeZO,单卡A100可训练300亿参数模型

深度学习自然语言处理 原创作者&#xff1a;辰宜 今天下午突然发现了一篇陈丹琦大佬的巨作~ 大家一起来简单瞅瞅。 本文旨在介绍一种用于fine-tuning语言模型&#xff08;LM&#xff09;的低内存优化器——MeZO&#xff0c;内存减少多达12倍。使用单个A100 800G GPU&#xff0c…

中文完整版FL Studio21永久免费升级

集合最新FL基础操作、编曲技巧、混音技巧、乐理基础、声乐演奏等各类内容&#xff0c;比如更高端版本才有的必备原厂插件Pitcher和Sakura&#xff0c;还有智能编曲插件ORB&#xff0c;编曲软件FL Studio21版本更新现已发布&#xff0c;在这次更新中优化了很多功能&#xff0c;但…

基于 Amazon API Gateway 的跨账号跨网络的私有 API 集成

一、背景介绍 本文主要讨论的问题是在使用 Amazon API Gateway&#xff0c;通过 Private Integration、Private API 来完成私有网络环境下的跨账号或跨网络的 API 集成。API 管理平台会被设计在单独的账号中(亚马逊云科技提供的是多租户的环境)&#xff0c;因为客观上不同业务…

Arm推出新一代高性能CPU内核Cortex-X4以及GPU Immortalis-720 GPU

每年差不多这个时候&#xff0c;智能手机芯片背后的大脑 Arm 都会推出高通、联发科等公司用于下一代SoC的构建模块。在 2023 年 Arm 技术日期间&#xff0c;Arm 推出了一系列涵盖高性能和低功耗用例的新 CPU 内核&#xff0c;以及其第五代 GPU&#xff0c;并提供光线追踪图形支…