动力节点springsecurity笔记-SpringSecurity 集成thymeleaf

news2024/10/5 22:18:36

15 SpringSecurity 集成thymeleaf

此项目是在springsecurity-12-database-authorization-method 的基础上进行
复制springsecurity-12-database-authorization-method 并重命名为springsecurity-13-thymeleaf

15.1 添加thymeleaf依赖

|

org.springframework.boot

spring-boot-starter-thymeleaf

15.2 修改application.yml

加入thymeleaf的配置

| spring:

thymeleaf:

cache: false # 不使用缓存check-template: true  # 检查thymeleaf模板是否存在

15.3 idea 添加thymeleaf模板

【File】—》【Settings…】

模板名称thymeleaf ,扩展名html,具体内容如下:

|

#[[$Title$]]#       

#[[ E N D END END]]#

简要说明:

#[[ T i t l e Title Title]]# #[[ E N D END END]]# 这两处的作用是,当你新建一个模板页面时,在标签中输入标题内容后,只需要点击回车键,光标就会直接跳到内,省去了你挪动鼠标,或者挪动方向键的步骤,也可以给你节省一点点时间。<strong>新版****idea可能有点问题,实验一下Enable Live Template</strong>

15.4 新建LoginController

| @Controller

@RequestMapping(“/login”)

public class LoginController {

_/**

* 跳转到登陆页面
     */

_@RequestMapping(“/toLogin”)

public String toLogin(){

return “login”;

}

}

15.5 创建thymeleaf文件login.html

在templates下面创建login.html,使用模板创建

|

用户登陆

登录页面

用户名:

密码:

登录

15.7 修改安全配置文件WebSecurityConfig

修改后如下:

| @EnableGlobalMethodSecurity(prePostEnabled = true)

//@Configuration

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

@Override

protected void configure(HttpSecurity http) throws Exception {

//设置登陆方式
        http.formLogin()//使用用户名和密码的登陆方式
                .usernameParameter(“uname”) //页面表单的用户名的name

.passwordParameter(“pwd”)//页面表单的密码的name

.loginPage(“/login/toLogin”) //自己定义登陆页面的地址
                .loginProcessingUrl(“/login/doLogin”)//配置登陆的url

.successForwardUrl(“/index/toIndex”) //登陆成功跳转的页面
                .failureForwardUrl(“/login/toLogin”)//登陆失败跳转的页面
                .permitAll(); //放行和登陆有关的url,别忘了写这个
        //配置退出方式
        http.logout()

.logoutUrl(“/logout”)

.logoutSuccessUrl(“/login/toLogin”)

.permitAll();/放行和退出有关的url,别忘了写这个

//配置路径拦截 的url的匹配规则
        http.authorizeRequests()

//任何路径要求必须认证之后才能访问
                .anyRequest().authenticated();

// 禁用csrf跨站请求攻击  后面可以使用postman工具测试,注意要禁用csrf
        http.csrf().disable();

}

}

15.8 创建IndexController

| @Controller

@RequestMapping(“/index”)

public class IndexController {

_/**

* 登录成功后进入主页
     */

_@RequestMapping(“/toIndex”)

public String toIndex(){

return “main”;

}

}

15.9 创建thymeleaf文件main.html

在templates下面创建main.html

|

系统首页

系统首页

查询学生


添加学生


更新学生


删除学生


导出学生





退出


15.10 修改Studentcontroller

修改后如下:

| @Controller

@Slf4j

@RequestMapping(“/student”)

public class StudentController {

@GetMapping(“/query”)

@PreAuthorize(“hasAuthority(‘student:query’)”)

public String queryInfo(){

return “user/query”;

}

@GetMapping(“/add”)

@PreAuthorize(“hasAuthority(‘student:add’)”)

public String addInfo(){

return “user/add”;

}

@GetMapping(“/update”)

@PreAuthorize(“hasAuthority(‘student:update’)”)

public String updateInfo(){

return “user/update”;

}

@GetMapping(“/delete”)

@PreAuthorize(“hasAuthority(‘student:delete’)”)

public String deleteInfo(){

return “user/delete”;

}

@GetMapping(“/export”)

@PreAuthorize(“hasAuthority(‘student:export’)”)

public String exportInfo(){

return “/user/export”;

}

}

15.11 在templates/user下面创建学生管理的各个页面

创建export.html

|

系统首页-学生管理

系统首页-学生管理-导出

返回


创建query.html

|

系统首页-学生管理

系统首页-学生管理-查询

返回


创建add.html

|

系统首页-学生管理

系统首页-学生管理-新增

返回


创建update.html

|

系统首页-学生管理

系统首页-学生管理-更新

返回


创建delete.html

|

系统首页-学生管理

系统首页-学生管理-删除

返回


15.12 创建403页面

在static/error下面创建403.html

|

403

403:你没有权限访问此页面

去首页

15.13 启动测试

注意:如果出现404问题,一般不出现这个问题

        spring-boot-starter-parent        org.springframework.boot         2.6.13                   

15.14 当用户没有某权限时,页面不展示该按钮(简单看下即可)

上一讲里面我们创建的项目里面是当用户点击页面上的链接请求到后台之后没有权限会跳转到403,那么如果用户没有权限,对应的按钮就不显示出来,这样岂不是更好吗?
我们接着上一个项目来改造
引入下面的依赖

|

org.thymeleaf.extras

thymeleaf-extras-springsecurity5

修改main.html即可

|

xmlns:sec=“http://www.thymeleaf.org/extras/spring-security”>

系统首页

系统首页

查询用户


添加用户


更新用户


删除用户


导出用户





退出


重新启动登录后查看效果

16 springsecurity 集成图片验证码

以前因为我们自己写登陆的方法可以在自己的登陆方法里面去接收页面传过来的code,再和session里面正确的code进行比较 。

16.1 概述

上一讲里面我们集成了thymeleaf实现在页面链接的动态判断是否显示,那么在实际开发中,我们会遇到有验证码的功能,那么如何处理呢?
复制上一个工程springsecurity-13-thymeleaf ,修改名字为springsecurity-14-captcha

16.2 原理、存在问题、解决思路

Springsecurity的过滤器链

我们知道Spring Security是通过过滤器链来完成了,所以它的解决方案是创建一个过滤器放到Security的过滤器链中,在自定义的过滤器中比较验证码

16.3 添加依赖(用于生成验证码)

|

cn.hutool

hutool-all

5.3.9

16.4 添加一个获取验证码的接口

| @Controller

@Slf4j

public class CaptchaController {

@GetMapping(“/code/image”)

public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {

//创建一个验证码
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 2, 20);

//放到session中
        // 为什么要重构?重构的快捷键是啥?
        String captchaCode=circleCaptcha.getCode();

log.info(“生成的验证码为:{}”,captchaCode);

request.getSession().setAttribute(“LOGIN_CAPTCHA_CODE”,captchaCode);

ImageIO.write(circleCaptcha.getImage(),“JPEG”,response.getOutputStream());

}

}

16.5 创建验证码过滤器

| @Component

@Slf4j

public class ValidateCodeFilter extends OncePerRequestFilter {

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

validateCode(request, response,filterChain);
    }

// 验证码校验
    private void validateCode(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws IOException, ServletException {

String enterCaptchaCode = request.getParameter(“code”);

HttpSession session = request.getSession();

String captchaCodeInSession = (String) session.getAttribute(“LOGIN_CAPTCHA_CODE”);

log.info(“用户输入的验证码为:{},session中的验证码为:{}”,enterCaptchaCode,captchaCodeInSession);

//移除错误信息
        session.removeAttribute(“captchaCodeErrorMsg”);

if (!StringUtils.hasText(captchaCodeInSession)) {

session.removeAttribute(“LOGIN_CAPTCHA_CODE”);

}

if (!StringUtils.hasText(enterCaptchaCode) || !StringUtils.hasText(captchaCodeInSession) || !enterCaptchaCode.equalsIgnoreCase(captchaCodeInSession)) {

//说明验证码不正确,返回登陆页面
            session.setAttribute(“captchaCodeErrorMsg”, “验证码不正确”);//重定向
            response.sendRedirect(“/login/toLogin”);

}else{

filterChain.doFilter(request,response);

}

}    @Override    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {        //如果不是登陆请求,直接放行,不走过滤器        return !request.getRequestURI().equals(“/login/doLogin”);    }

}

16.6 修改WebSecurityConfig(重点)

| @EnableGlobalMethodSecurity(prePostEnabled = true)

@Slf4j

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource

private ValidateCodeFilter validateCodeFilter;

@Override
    _/**

* Security的http请求配置
     *

* **@param **http

* **@throws **Exception

*/

_@Override

protected void configure(HttpSecurity http) throws Exception {
        //设置登陆方式
        http.formLogin()//使用用户名和密码的登陆方式
                .usernameParameter(“uname”) //页面表单的用户名的name

.passwordParameter(“pwd”)//页面表单的密码的name

.loginPage(“/login/toLogin”) //自己定义登陆页面的地址
                .loginProcessingUrl(“/login/doLogin”)//配置登陆的url

.successForwardUrl(“/index/toIndex”) //登陆成功跳转的页面
                .failureForwardUrl(“/login/toLogin”)//登陆失败跳转的页面
                .permitAll(); // 这个不要忘了
        //配置退出方式
        http.logout()

.logoutUrl(“/logout”)

.logoutSuccessUrl(“/login/toLogin”)

.permitAll();

//配置路径拦截 的url的匹配规则 ,放行请求获取验证码的路径
     http.authorizeRequests().antMatchers(“/code/image”).permitAll()

//任何路径要求必须认证之后才能访问
                .anyRequest().authenticated();

// 禁用csrf跨站请求,注意不要写错了,因为前端页面没有传token,所以要禁用
        http.csrf().disable();

// 配置登录之前添加一个验证码的过滤器      http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class);

}_
    _@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}

16.7 修改login.html

添加验证码表单元素和图片

|

用户登陆

登录页面

用户名:

密码:

验证码:

username

登录

16.8 测试登录

故意输入错误验证码

16.9 使用debug模式,查看一下自定义过滤器执行流程

17 Base64 和JWT学习

见《base64及jwt学习文档.doc》

18 JWT+Spring Security+redis+mysql 实现认证

18.1 新建工程

复制工程springsecurity-12-database-authorization-method,改名字为
springsecurity-16-jwt-authentication
注意这个工程已经有认证功能和基于方法授权的功能了。
下面咱们看下如何设置使用jwt进行认证登录。

18.2 添加jwt依赖

|

com.auth0

java-jwt

3.11.0

18.3 application.yml 中配置密钥

| jwt:

secretKey: mykey

18.4 jwt功能类

com.powernode.util包下创建

| @Component

@Slf4j

public class JwtUtils {

//算法密钥
    @Value(“${jwt.secretKey}”)

private String jwtSecretKey;

_/**

* 创建jwt

*

* **@param **userInfo 用户信息
     * **@param **authList 用户权限列表
     * **@return **返回jwt(JSON WEB TOKEN)
     */

_public String createToken(String userInfo, List authList) {

//创建时间
        Date currentTime = new Date();

//过期时间,5分钟后过期
        Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));

//jwt 的header信息
        Map<String, Object> headerClaims = new HashMap<>();

headerClaims.put(“type”, “JWT”);

headerClaims.put(“alg”, “HS256”);

//创建jwt

return JWT.create()

.withHeader(headerClaims) // 头部
                .withIssuedAt(currentTime) //已注册声明:签发日期,发行日期
                .withExpiresAt(expireTime) //已注册声明 过期时间
                .withIssuer(“thomas”)  //已注册声明,签发人
                .withClaim(“userInfo”, userInfo) //私有声明,可以自己定义
                .withClaim(“authList”, authList) //私有声明,可以自定义
                .sign(Algorithm.HMAC256(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥
//        HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
    }

_/**

* 验证jwt的签名,简称验签
     *

* **@param **token 需要验签的jwt

* **@return **验签结果
     */

_public boolean verifyToken(String token) {

//获取验签类对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();

try {

//验签,如果不报错,则说明jwt是合法的,而且也没有过期
            DecodedJWT decodedJWT = jwtVerifier.verify(token);

return true;

} catch (JWTVerificationException e) {

//如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)
            log.error(“验签失败:{}”, token);

e.printStackTrace();

}

return false;

}

_/**

* 获取用户id

*

* **@param **token jwt

* **@return **用户id

*/

_public String getUserInfo(String token) {

//创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();

try {

//验签
            DecodedJWT decodedJWT = jwtVerifier.verify(token);

//获取payload中userInfo的值,并返回
            return decodedJWT.getClaim(“userInfo”).asString();

} catch (JWTVerificationException e) {

e.printStackTrace();

}

return null;

}

_/**

* 获取用户权限
     *

* **@param **token

* **@return

***/

_public List getUserAuth(String token) {

//创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();

try {

//验签
            DecodedJWT decodedJWT = jwtVerifier.verify(token);

//获取payload中的自定义数据authList(权限列表),并返回
            return decodedJWT.getClaim(“authList”).asList(String.class);

} catch (JWTVerificationException e) {

e.printStackTrace();

}

return null;

}

}

18.5 添加响应类

com.powernode.vo包中

| @Data

@AllArgsConstructor

@NoArgsConstructor

@Builder

public class HttpResult implements Serializable {

private Integer code; //响应码
    private String msg; //响应消息
    private Object data; //响应对象

}

18.6 修改SecurityUser类

加入一个获取SysUser的方法,后面会用到这个方法

| public SysUser getSysUser() {

return sysUser;

}

18.7 新建认证成功处理器

| _/**

* 认证成功处理器,当用户登录成功后,会执行此处理器
 */

_@Component

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

//使用此工具类进行序列化
    @Resource

private ObjectMapper objectMapper;

@Resource

private JwtUtils jwtUtils;

@Override

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

response.setCharacterEncoding(“UTF-8”);

response.setContentType(“text/html;charset=utf-8”);

//从认证对象中获取认证用户信息
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());

List authorities = (List) securityUser.getAuthorities();

//_这可以改成stream流
        _List authList=new ArrayList<>();

for (SimpleGrantedAuthority authority : authorities) {

authList.add(authority.getAuthority());

}//使用stream流1,使用lambda表达式
List test = authorities.stream().map(

a -> {

return a.getAuthority();

}

).collect(Collectors.toList());

System.out.println("test = " + test);

//使用stream流2

List test111 = authorities.stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.toList());

System.out.println("test111 = " + test111);
        // 创建jwt

String token = jwtUtils.createToken(userInfo,authList);

//返回给前端token

HttpResult httpResult = HttpResult.builder().code(200).msg(“OK”).data(token).build();

PrintWriter writer = response.getWriter();

writer.write(objectMapper.writeValueAsString(httpResult));

writer.flush();

}

}

18.8 新建jwt过滤器,用于检查token等

com.powernode.filter包中新建类

| _/**

* 定义一次性请求过滤器
 */

_@Component

@Slf4j

public class JwtCheckFilter extends OncePerRequestFilter {

@Resource

private ObjectMapper objectMapper;

@Resource

private JwtUtils jwtUtils;

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

//获取请求头中的Authorization

String authorization = request.getHeader(“Authorization”);

//如果Authorization为空,那么不允许用户访问,直接返回
        if (!StringUtils.hasText(authorization)) {

printFront(response, “没有登录!”);

return;

}

//Authorization 去掉头部的Bearer 信息,获取token值
        String jwtToken = authorization.replace("Bearer ", “”);

//验证签名(简称验签)
        boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);

//验签不成功
        if (!verifyTokenResult) {

printFront(response, “jwtToken 已过期”);

return;

}

//从payload中获取userInfo

String userInfo = jwtUtils.getUserInfo(jwtToken);

//从payload中获取授权列表
        List userAuth = jwtUtils.getUserAuth(jwtToken);

//创建登录用户
        SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);

SecurityUser securityUser = new SecurityUser(sysUser);

//设置权限
        List authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());

securityUser.setAuthorityList(authList);

//创建用户名密码token
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser

, null, authList);

//通过安全上下文设置认证信息
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);

//继续访问相应的url等
        filterChain.doFilter(request, response);

}

private void printFront(HttpServletResponse response, String message) throws IOException {

response.setCharacterEncoding(“UTF-8”);

response.setContentType(“application/json;charset=utf-8”);

PrintWriter writer = response.getWriter();

HttpResult httpResult =HttpResult.builder().code(-1).msg(message).build();

writer.print(objectMapper.writeValueAsString(httpResult));

writer.flush();

}    @Override    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {        //如果是登陆请求,直接放行,不走过滤器        return request.getRequestURI().equals(“/login”);    }

}

18.9修改 web安全配置类WebSecurityConfig

| @EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Resource

private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

@Resource

private JwtCheckFilter jwtCheckFilter;
    @Override

protected void configure(HttpSecurity http) throws Exception {

http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);

http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();
http.authorizeRequests()

.mvcMatchers(“/student/**”).hasAnyAuthority(“student:query”,“student:update”)
                .anyRequest().authenticated(); //任何请求均需要认证(登录成功)才能访问//禁用跨域请求保护
        http.csrf().disable();

//禁用session

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

}

@Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

}

18.10 启动测试

先登录系统,获取页面上返回的token,然后使用postman 在请求头中携带token发送请求即可。

使用curl 访问

curl -H “Authorization:Bearer token” localhost:8080/student/query

18.11 测试后的问题

18.11.1 实现用户退出的问题

问题:因为JWT无状态,如果要实现退出功能无法实现。
解决办法:
使用redis
步骤:
① 登陆成功之后把生成JWT存到redis中

keyvalue
logintoken:jwt认证信息authentication

② 用户退出时,从redis中删除该token
③ 用户每次访问时,先校验jwt是否合法,如果合法再从redis里面取出logintoken:jwt判断这个jwt还存不存在,如果不存在就说明用户已经退出来,就返回未登陆。

18.11.2 启动redis并使用客户端工具连接到redis

18.11.3 复制工程

复制springsecurity-16-jwt-authentication 成springsecurity-16-jwt-authentication-redis工程

18.11.4 加入redis依赖

|

org.springframework.boot

spring-boot-starter-data-redis

18.11.5 配置redis信息

| spring:

** ****redis:

host: 192.168.43.33

port: 6379

database: 0

password: 666666**

18.11.6 修改认证成功处理器

添加依赖注入

| @Resource

private StringRedisTemplate stringRedisTemplate;

代码中加入

stringRedisTemplate.opsForValue().set(“logintoken:”+token,objectMapper.writeValueAsString(authentication),30, TimeUnit.MINUTES);

18.11.7 新建用户退出成功处理器

| _/**

* 退出成功处理器,用户退出成功后,执行此处理器
 */

_@Component

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

//使用此工具类的对象进行序列化操作
    @Resource

private ObjectMapper objectMapper;

@Resource

private StringRedisTemplate stringRedisTemplate;

@Override

public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

//从请求头中获取Authorization信息
        String authorization = request.getHeader(“Authorization”);

//如果授权信息为空,返回前端
        if(null==authorization){

response.setCharacterEncoding(“UTF-8”);

response.setContentType(“application/json;charset=utf-8”);

HttpResult httpResult=HttpResult.builder().code(-1).msg(“token不能为空”).build();

PrintWriter writer = response.getWriter();

writer.write(objectMapper.writeValueAsString(httpResult));

writer.flush();

return;

}

//如果Authorization信息不为空,去掉头部的Bearer字符串
        String token = authorization.replace("Bearer ", “”);

//redis中删除token,这是关键点
        stringRedisTemplate.delete(“logintoken:”+token);

response.setCharacterEncoding(“UTF-8”);

response.setContentType(“application/json;charset=utf-8”);

HttpResult httpResult=HttpResult.builder().code(200).msg(“退出成功”).build();

PrintWriter writer = response.getWriter();

writer.write(objectMapper.writeValueAsString(httpResult));

writer.flush();

}

}

配置用户成功退出处理器
修改WebSecurityConfig
依赖注入

| @Resource

private MyLogoutSuccessHandler myLogoutSuccessHandler;

添加代码

http.logout().logoutSuccessHandler(myLogoutSuccessHandler);http.csrf().disable(); //注意:禁用跨域请求保护 要不然logout不能访问

18.11.8 修改jwtcheckfilter

添加依赖注入

| @Resource

private StringRedisTemplate stringRedisTemplate;

代码中加入

| //        从redis中获取token

String tokenInRedis = stringRedisTemplate.opsForValue().get(“logintoken:” + jwtToken);

if(!StringUtils.hasText(tokenInRedis)){

printFront(response, “用户已退出,请重新登录”);

return;

}

18.11.9 启动程序并登录测试

登录后查看redis中是否存储了token

18.11.10 使用jwt token访问/student/query

使用postman测试,发现可以正常访问

18.11.11 使用postman 退出系统


注意携带token,才能退出啊
注意:要禁用跨域请求保护,要不然使用postman无法访问logout端点

http.csrf().disable(); //禁用跨域请求保护

18.11.12 再次使用token访问/student/query

现象:发现已经不能正常访问了
原因:虽然token本身并没有过期,但是redis中已经删除了该token,所以不能正常访问了
使用curl 访问

curl -H “Authorization:Bearer token” localhost:8080/student/query

18.11.13 使用debug模式,理解整个流程

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

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

相关文章

h5逻辑_解决h5页面嵌入ios兼容性问题

安全区域 如下图所示&#xff5e; 蓝色部分为安全区域。处于安全区域内的内容不受圆角、齐刘海、小黑条的影响。 若是将h5页面嵌入app中&#xff0c;就需要进行适配—> 让h5页面展示在安全区域内。 tips: 安全区域是在ios11之后并且是iPhoneX及以上机型才有的。 因此我们只…

【微信小程序】详解behaviors,如何使用behaviors

一&#xff0c;behaviors 1.1什么是 behaviors&#xff1f; behaviors 是小程序中&#xff0c; 用于实现组件间代码共享的特性 &#xff0c;类似于 Vue.js 中的 “mixins”。 1.2behaviors 的工作方式 每个 behavior 可以包含一组 属性、数据、生命周期函数和方法 。组件引…

【PE】inline hook的实现

【PE】inline hook的实现 hook思路 最基本的5字节的hook思路如下&#xff0c;有了这个思路&#xff0c;可以用更多的方式进行hook 通过修改目标函数开头的5个字节为jmp …&#xff0c;劫持程序执行流跳转过去之后&#xff0c;再把API开头5字节改回来&#xff08;UnHook&…

Servlet 入门到精通(六)

上一篇博客的传送门&#xff1a;Servlet 入门到精通&#xff08;五&#xff09; Servlet Filter 又称 Servlet 过滤器&#xff0c;它是在 Servlet 2.3 规范中定义的&#xff0c;是 Servlet 中的一个组件&#xff0c;是设计模式中责任链模式的一种经典实现。能够对 Servlet 容器…

SpringBoot启动自动执行sql脚本

在开发当中我们每次发布服务都需要手动执行脚本&#xff0c;然后重启服务&#xff0c;而SpringBoot有服务启动自动执行sql脚本的功能的&#xff0c;可以为我们省去手动执行脚本的这一步&#xff0c;只需要部署新的服务即可。 这个功能是SpringBoot自带的不需要引入额外的依赖&a…

Spring Boot 提取内存密码

访问 /actuator/heapdump 下载内存&#xff0c;提取密码 select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("password"))

学习系统编程No.21【进程间通信之共享内存】

引言&#xff1a; 北京时间&#xff1a;2023/4/16/21:53&#xff0c;刚刚把新文章发出去&#xff0c;开完班会回来&#xff0c;本来上篇博客在昨天就能发的&#xff0c;昨天下午打了一下午的羽毛球之后&#xff0c;饭都没吃&#xff0c;躺在床上&#xff0c;准备睡觉&#xff…

Spring五大类注解 || Bean的更简单存储

目录 前言&#xff1a; 五大类注解 Controller Service Repository Component Configuration JavaEE标准分层 阿里分层结构 BeanName命名规则 方法注解 Bean 注入方式取Bean 属性注入 Setter注入 构造方法注入 Resource 前言&#xff1a; 使用Spring容器&…

Linux-驱动开发-基础温习

一、裸机开发和驱动开发的区别&#xff1a; 裸机开发&#xff1a;底层&#xff08;相对于linux来说&#xff09;&#xff0c;库 二、linux驱动开发-根据各种框架进行开发 1、 外设比较多&#xff0c;资源多&#xff0c;资料非常少&#xff0c;官方的SDK;直接操作寄存器不显示…

Python 基础(十):元组

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、声明元组二、访问元组三、修改元组变量四、遍历元组五、切片六、常用函数和方法6.…

SpringBoot实现导出Excel功能

1 问题背景 需求要做一个导出excel的功能 2 前言 本篇着重阐述后端怎么实现&#xff0c;前端实现的部分只会粗略阐述。该实现方案是经过生产环境考验的&#xff0c;不是那些拿来练手的小demo。本文阐述的方案可以借鉴用来做毕设或者加到自己玩的项目中去。 3 实现思路 后端查询…

103. 二叉树的锯齿形层序遍历【191】

难度等级&#xff1a;中等 上一篇算法&#xff1a; 104. 二叉树的最大深度【75】 力扣此题地址&#xff1a; 103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;Leetcode&#xff09; 1.题目&#xff1a;103. 二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其…

p65 内网安全-域环境工作组局域网探针方案

数据来源 基本概念 DMZ区域&#xff1a;称为“隔离区”&#xff0c;也称‘’非军事化区/停火区” 工作组&#xff08;Work Group&#xff09;是局域网中的一个概念。它是最常见最简单最普通的资源管理模式&#xff0c;就是将不同的电脑按功能分别列入不同的组中&#xff0c;以…

完美解决丨except NameError:

示例如下&#xff1a; try: print(xx) except: print(xx is not defined) print(continue) 解决办法 第一种解决办法&#xff1a; try: print(xx) except NameError: print(xx is not defined) print(continue) 第二种解决办法&#xff1a; print(xx) if xx in locals() e…

camunda工作流user task如何使用

在Camunda中使用User Task通常需要以下步骤&#xff1a; 1、创建User Task&#xff1a;使用BPMN 2.0图形化设计器&#xff08;如Camunda Modeler&#xff09;&#xff0c;将User Task元素拖到流程图中&#xff0c;并为任务命名&#xff0c;指定参与者&#xff08;用户或用户组…

第二章 Maven 核心程序解压和配置

第一节 Maven核心程序解压与配置 1、Maven 官网地址 首页&#xff1a; Maven – Welcome to Apache Maven(opens new window) 下载页面&#xff1a; Maven – Download Apache Maven(opens new window) 下载链接&#xff1a; 具体下载地址&#xff1a;https://dlcdn.apac…

算法:(力扣)(牛客)打印螺旋矩阵题

手撕螺旋矩阵 题目思路解题 题目 描述&#xff1a;给定一个m x n大小的矩阵&#xff08;m行&#xff0c;n列&#xff09;&#xff0c;按螺旋的顺序返回矩阵中的所有元素。数据范围&#xff1a;0 \le n,m \le 100≤n,m≤10&#xff0c;矩阵中任意元素都满足 |val| \le 100∣val…

makefile 规则的覆盖

makefile 中经常会使用规则的覆盖&#xff0c;同样一个target 可能有多个prerequisites&#xff0c;这种依赖关系可以放到一起&#xff0c;也可以分开指定。 例1&#xff1a; test1:echo "test111"test2:echo "test222"test3:echo "test333"he…

vsync-app 不稳定导致抖动

问题描述&#xff1a;跟对比机器对比uc 浏览器新闻页滑动场景&#xff0c;出现抖动 1、trace 看是没有丢帧&#xff0c;对比看送帧buffer 给到 SF 步调不够一致&#xff0c;从间隔较大的两个 送帧buffer看&#xff0c;发现vsync-app 时间比正常的要长3ms 左右&#xff0c;vsync…

网络交换机端口管理工具

如今&#xff0c;企业或组织级网络使用数百个交换机端口作为其 IT 基础架构的一部分来实现网络连接。这使得交换机端口管理成为日常网络管理任务的一部分。传统上&#xff0c;网络管理员必须依靠手动网络交换机端口管理技术来跟踪交换机及其端口连接状态。这种手动任务弊大于利…