6.3 认证授权模块 - 用户认证

news2024/10/1 21:33:10

认证授权模块 - 用户认证

文章目录

  • 认证授权模块 - 用户认证
  • 一、用户认证 基础
    • 1.1 连接数据库认证
      • 1.1.0 用户表 xc_user
      • 1.1.1 分析
      • 1.1.2 安全管理配置 WebSecurityConfig
      • 1.1.3 自定义UserDetailsService
      • 1.1.4 授权服务器配置 AuthorizationServer
    • 1.2 扩展用户身份信息
      • 1.2.1 修改 UserServiceImpl
      • 1.2.2 用户身份信息
    • 1.3 工具类获取用户身份
  • 二、统一认证入口
    • 2.1 认证用户请求参数 AuthParamsDto
    • 2.2 修改 UserServiceImpl
    • 2.3 重写DaoAuthenticationProvider
    • 2.4 修改 WebSecurityConfig 安全配置管理
    • 2.5 统一认证接口 AuthService
      • 2.5.1 XcUserExt 用户扩展信息
      • 2.5.2 修改 UserServiceImpl
      • 2.5.3 统一认证接口
  • 三、部署验证码服务
    • 3.1 连接 Redis
    • 3.2 验证码流程
    • 3.3 验证码服务接口
  • 四、 账号密码登录
    • 4.1 PasswordAuthServiceImpl
    • 4.2 修改 UserServiceImpl
    • 4.3 Feign 接口
      • 4.3.1 CheckCodeClient
      • 4.3.2 CheckCodeClientFactory
      • 4.3.3 开启Feign
    • 4.4 测试
  • 五、微信扫码验证
    • 5.1 介绍
      • 5.1.1 请求获取授权码
      • 5.1.2 通过code获取access_token
      • 5.1.3 通过access_token调用接口
    • 5.2 接入
      • 5.2.1 接入分析
      • 5.1.2 WxLoginController 接口定义
      • 5.2.3 WxAuthServiceImpl
      • 5.2.4 xc_user_role 学生角色关系表
      • 5.2.5 xc_role 角色表

一、用户认证 基础

依然是xuecheng-auth模块

实现如下图所示的流程

我们要实现的其实就是下图中圈出来的部分,最重要的就是统一认证入口

image-20240125233912169

认证所需要的用户信息存储在用户中心数据库,现在需要将认证服务连接数据库查询用户信息

1.1 连接数据库认证

1.1.0 用户表 xc_user

写在auth工程中

wx_unionid是在微信开放平台上唯一的id

张三扫描微信二维码登录后会有一个unionid

李四扫描微信二维码登录后会有一个unionid

其实相当于用户id了

image-20240126000533169

image-20240126000554935

实体类

@Data
@TableName("xc_user")
public class XcUser implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;

    private String username;

    private String password;

    private String salt;

    private String name;
    
    private String nickname;
    
    private String wxUnionid;
    
    private String companyId;
    /**
     * 头像
     */
    private String userpic;

    private String utype;

    private LocalDateTime birthday;

    private String sex;

    private String email;

    private String cellphone;

    private String qq;

    /**
     * 用户状态
     */
    private String status;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;


}

1.1.1 分析

一般认证就是用户输入账号和密码,判断用户是否存在和判断密码是否正确,有时候可能还会需要验证码

登录界面

http://www.51xuecheng.cn/sign.html

image-20240125233537887

认证用户的流程如下图所示

  1. 一个请求过来之后首先到我们SpringSecurity框架认证管理器AuthenticationManager
  2. DaoAuthenticationProvider就是用来查询数据库的,会通过loadUserByUsername()获取用户信息,并封装成一个UserDetails类型的对象

loadUserByUsername方法是UserDetailsService接口的方法

简单来说:用户提交账号和密码由DaoAuthenticationProvider调用UserDetailsService的loadUserByUsername()方法获取UserDetails用户信息

image-20240125234652894

  • UserDetailsService接口内容
public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

我们只要实现UserDetailsService 接口查询数据库得到用户信息返回UserDetails 类型的用户信息即可,框架自己调用loadUserByUsername()方法拿到用户信息之后执行逻辑如下图所示

image-20240125235612727

  • 查询DaoAuthenticationProvider的源代码如下

img

  • UserDetails用户信息接口
public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

1.1.2 安全管理配置 WebSecurityConfig

下面的代码是在内存中配置的用户信息,将其删掉,因为我们要从数据库中查询

//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
    //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
    manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
    return manager;
}

配置密码加密方式

    @Bean
    public PasswordEncoder passwordEncoder() {
        //密码为明文方式
        //return NoOpPasswordEncoder.getInstance();
        
        //密码为加密之后
        return new BCryptPasswordEncoder();
    }

其他配置

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
            .anyRequest().permitAll()//其它请求全部放行
            .and()
            .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
}

@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

1.1.3 自定义UserDetailsService

写在auth工程中

下面这篇文章和此处获取UserDetails对象的方式不同,可能是因为包不同的原因

SpringSecurity - 基于数据库认证与授权

在目前1.1.3所使用的security包中无法使用下面的代码

SecurityUser securityUser = new SecurityUser(sysUser);
@Component
public class UserServiceImpl implements UserDetailsService {

    //注入,将来查询对象
    @Autowired
    XcUserMapper xcUserMapper;

    /**
     * @param s 其实就是输入的username(账号)
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1.根据username账号查询数据库
        // 因为账号是不可能重复的,直接selectOne即可
        LambdaQueryWrapper<XcUser> lqw = new LambdaQueryWrapper<>();
        lqw.eq(XcUser::getUsername, s);
        XcUser xcUser = xcUserMapper.selectOne(lqw);

        // 2.查询不到用户,返回null即可,SpringSecurity框架会自动抛出异常“用户不存在”
        if (xcUser == null) {
            return null;
        }
        // 3.如果查到了用户并查询到正确的密码,将用户信息封装成UserDetails类型数据返回,SpringSecurity框架会比对密码是否正确,我们不用比对密码
        String password = xcUser.getPassword();

        UserDetails userDetails = User.withUsername(s)
                //用户密码
                .password(password)
                //用户权限,暂时先不写
                .authorities("").build();
        return userDetails;
    }
}

1.1.4 授权服务器配置 AuthorizationServer

将客户端秘钥改成密文,不再是明文“XcWebApp”

    //客户端详情服务
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()// 使用in-memory存储
                .withClient("XcWebApp")// client_id
//                .secret("XcWebApp")//客户端密钥
                .secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥
                .resourceIds("xuecheng-plus")//资源列表
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .scopes("all")// 允许的授权范围
                .autoApprove(false)//false跳转到授权页面
                //客户端接收授权码的重定向地址
                .redirectUris("http://www.51xuecheng.cn")
        ;
    }

1.2 扩展用户身份信息

用户表中存储了用户的账号、手机号、email,昵称、qq等信息,UserDetails接口只返回了username、密码等信息

如果只返回一个“user_name”字段是不够用的

image-20240126003835774

SpringSecurity中UserDetails接口如下所示

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

如何扩展Spring Security的用户身份信息呢

在认证阶段DaoAuthenticationProvider会调用UserDetailService查询用户的信息,这里是可以获取到齐全的用户信息的。

由于JWT令牌中用户身份信息来源于UserDetails,UserDetails中仅定义了username为用户的身份信息,

这里有两个思路

第一是可以扩展UserDetails,使之包括更多的自定义属性

第二也可以扩展username的内容 ,比如存入json数据内容作为username的内容。

相比较而言,方案二比较简单还不用破坏UserDetails的结构,我们采用方案二

1.2.1 修改 UserServiceImpl

写在auth工程中

    /**
     * @param s 其实就是输入的username(账号)
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1.根据username账号查询数据库
        // 因为账号是不可能重复的,直接selectOne即可
        LambdaQueryWrapper<XcUser> lqw = new LambdaQueryWrapper<>();
        lqw.eq(XcUser::getUsername, s);
        XcUser xcUser = xcUserMapper.selectOne(lqw);

        // 2.查询不到用户,返回null即可,SpringSecurity框架会自动抛出异常“用户不存在”
        if (xcUser == null) {
            return null;
        }
        // 3.如果查到了用户并查询到正确的密码,将用户信息封装成UserDetails类型数据返回,SpringSecurity框架会比对密码是否正确,我们不用比对密码
        String password = xcUser.getPassword();

        //用户权限,如果不加报Cannot pass a null GrantedAuthority collection
        String[] authorities = {"p1"};

        xcUser.setPassword(""); // 为了安全考虑,专JSON前吧密码做空
        String userString = JSON.toJSONString(xcUser);
        UserDetails userDetails = User.withUsername(userString)
                //用户密码
                .password(password)
                //用户权限,暂时先不写
                .authorities(authorities).build();
        return userDetails;
    }

1.2.2 用户身份信息

user_name存储了用户信息的json格式,在资源服务中就可以取出该json格式的内容转为用户对象去使用

image-20240126004715370

1.3 工具类获取用户身份

下边编写一个工具类在各个微服务中去使用,获取当前登录用户的对象

比如我们在content模块要获取用户身份,那我们就把工具类放在content模块下

/**
 * @description 获取当前用户身份工具类
 */
@Slf4j
public class SecurityUtil {

    public static XcUser getUser() {
        try {
            // getContext() 获取上下文对象,getAuthentication()拿到认证信息,getPrincipal()拿到身份信息
            Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principalObj instanceof String) {
                //取出用户身份信息
                String principal = principalObj.toString();
                //将json转成对象
                XcUser user = JSON.parseObject(principal, XcUser.class);
                return user;
            }
        } catch (Exception e) {
            log.error("获取当前登录用户身份出错:{}", e.getMessage());
            e.printStackTrace();
        }

        return null;
    }


    // 内部类 因为XcUser是在auth服务中,并不在content服务中,所以在这里我们使用了一个内部类
    // 其实是完全一样的
    @Data
    public static class XcUser implements Serializable {

        private static final long serialVersionUID = 1L;

        private String id;

        private String username;

        private String password;

        private String salt;

        private String name;

        private String nickname;

        private String wxUnionid;

        private String companyId;
        /**
         * 头像
         */
        private String userpic;

        private String utype;

        private LocalDateTime birthday;

        private String sex;

        private String email;

        private String cellphone;

        private String qq;

        /**
         * 用户状态
         */
        private String status;

        private LocalDateTime createTime;

        private LocalDateTime updateTime;
    }
}

使用

@ApiOperation("修改课程接口")
@PutMapping("/course")
public CourseBaseInfoDto getCourseBaseById(@RequestBody EditCourseDto editCourseDto) {
    // getContext() 获取上下文对象,getAuthentication()拿到认证信息,getPrincipal()拿到身份信息
    //SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    SecurityUtil.XcUser user = SecurityUtil.getUser();
    System.out.println(user.getUsername());
    //机构id先写死,后面授权认证的时候后会改过来
    Long companyId = 1232141425L;
    return courseBaseInfoService.updateCourseBase(companyId, editCourseDto);
}

二、统一认证入口

其实也就是登录界面

image-20240128001509732

2.1 认证用户请求参数 AuthParamsDto

写在auth工程中

image-20240128002325609

用户名密码登录、短信登录等方式都是用下面实体类作为请求参数

/**
 * @description 认证用户请求参
 */
@Data
public class AuthParamsDto {

    private String username; //用户名
    private String password; //域  用于扩展
    private String cellphone;//手机号
    private String checkcode;//验证码
    private String checkcodekey;//验证码key
    private String authType; // 认证的类型   password:用户名密码模式类型    sms:短信模式类型
    private Map<String, Object> payload = new HashMap<>();//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId
}

2.2 修改 UserServiceImpl

其实就是将参数“s”,转成了一个

@Component
public class UserServiceImpl implements UserDetailsService {

    //注入,将来查询对象
    @Autowired
    XcUserMapper xcUserMapper;

    /**
     * @param s 传入的请求认证的参数是AuthParamDtoJSON串,此时不再是输入的username(账号)
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 将传入的JSON转成AuthParamsDto对象
        AuthParamsDto authParamsDto = null;
        try {
            authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
        } catch (Exception e) {
            throw new RuntimeException("请求认证的参数不符合要求");
        }
        
        String username = authParamsDto.getUsername();
        // 1.根据username账号查询数据库
        // 因为账号是不可能重复的,直接selectOne即可
        LambdaQueryWrapper<XcUser> lqw = new LambdaQueryWrapper<>();
        lqw.eq(XcUser::getUsername, username);
        XcUser xcUser = xcUserMapper.selectOne(lqw);

        // 2.查询不到用户,返回null即可,SpringSecurity框架会自动抛出异常“用户不存在”
        if (xcUser == null) {
            return null;
        }
        // 3.如果查到了用户并查询到正确的密码,将用户信息封装成UserDetails类型数据返回,SpringSecurity框架会比对密码是否正确,我们不用比对密码
        String password = xcUser.getPassword();

        //用户权限,如果不加报Cannot pass a null GrantedAuthority collection
        String[] authorities = {"p1"};

        xcUser.setPassword(""); // 为了安全考虑,专JSON前吧密码做空
        String userString = JSON.toJSONString(xcUser);
        UserDetails userDetails = User.withUsername(userString)
                //用户密码
                .password(password)
                //用户权限,暂时先不写
                .authorities(authorities).build();
        return userDetails;
    }

}

修改前的请求方式

### 密码模式
POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=t1&password=111111

修改后的请求方式

### 密码模式,请求AuthParamsDto参数
POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username={"username":"t1","password":"111111","authType":"password"}

2.3 重写DaoAuthenticationProvider

我们原先在UserServiceImpl中返回了一个UserDetails对象,框架会自动帮我们校验密码是否正确

但是我们除了用户名及密码登录外,还有短信验证码登录、微信登录等,这些登录方式框架是帮我们完成不了的,所以我们需要重写一下

简单来说,不是所有的登录都需要验证密码,短信登录的时候就不需要验证密码

怎么实现所说的业务要求呢

按照下图所示流程图,我们重写DaoAuthenticationProvider即可

image-20240128003912871

/**
 * 继承DaoAuthenticationProvider的目的就是重写其校验密码的方法
 *  因为我们统一了认证的入口,有一些认证方式不需要校验密码
 */
@Slf4j
@Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {

    /**
     *  重写校验密码的方式
     * @param userDetailsService
     */
    @Autowired
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        super.setUserDetailsService(userDetailsService);
    }


    //屏蔽密码对比,设置为空就行了
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

    }

}

2.4 修改 WebSecurityConfig 安全配置管理

@Autowired
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(daoAuthenticationProviderCustom);
}

2.5 统一认证接口 AuthService

在2.5 完成后,我们要实现下面这个流程结构

image-20240128011645789

2.5.1 XcUserExt 用户扩展信息

其实就是用户的权限信息

/**
 * @description 用户扩展信息
 */
@Data
public class XcUserExt extends XcUser {
    //用户权限
    List<String> permissions = new ArrayList<>();
}

2.5.2 修改 UserServiceImpl

// 注入Spring上下文对象
@Autowired
ApplicationContext applicationContext;
/**
 * @param s 传入的请求认证的参数是AuthParamDtoJSON串,此时不再是输入的username(账号)
 */
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
  // 将传入的JSON转成AuthParamsDto对象
        AuthParamsDto authParamsDto = null;
        try {
            authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
        } catch (Exception e) {
            throw new RuntimeException("请求认证的参数不符合要求");
        }

        // 判断登录的类型(认证类型,有password、wx)
        String authType = authParamsDto.getAuthType();
        String beanName = authType +"_authservice";
        // 取出指定的bean
        AuthService authService = applicationContext.getBean(beanName, AuthService.class);
        // 调用统一认证方法完成认证
        authService.execute(authParamsDto);
    
    
    // 省略下面的代码
    ............................
}

2.5.3 统一认证接口

/**
 *  统一认证接口:判断账号是否存在、密码是否正确
 */
public interface AuthService {
    /**
     * @description 认证方法
     * @param authParamsDto 认证参数
     * @return com.xuecheng.ucenter.model.po.XcUser 用户信息
     */
    XcUserExt execute(AuthParamsDto authParamsDto);


}

三、部署验证码服务

此模块也是一个微服务,checkcode模块

验证码可以防止恶性攻击

比如:XSS跨站脚本攻击、CSRF跨站请求伪造攻击,一些比较复杂的图形验证码可以有效的防止恶性攻击

为了保护系统的安全在一些比较重要的操作都需要验证码

image-20240128142912112

验证码的类型也有很多:图片、语音、手机短信验证码等

本项目创建单独的验证码服务为各业务提供验证码的生成、校验等服务

3.1 连接 Redis

  • 在nacos中配置redis-dev.yaml配置文件
spring: 
  redis:
    host: 192.168.101.65
    port: 6379
    password: redis
    database: 0
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 0
    timeout: 10000
    #redisson:
      #配置文件目录
      #config: classpath:singleServerConfig.yaml

image-20240128144054418

  • checkcode服务模块引入redis-dev.yaml配置文件
spring:
  application:
    name: checkcode
  cloud:
    nacos:
      server-addr: 192.168.101.65:8848
      discovery:
        namespace: dev
        group: xuecheng-plus-project
      config:
        namespace: dev
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: swagger-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true
          - data-id: logging-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true
          - data-id: redis-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true
  profiles:
    active: dev

3.2 验证码流程

  • 首先申请验证码
### 申请验证码
POST {{checkcode_host}}/checkcode/pic

返回值如下图所示

分别返回的是验证码图片的key值和验证码图片的base64值

{
  "key": "checkcode:ee3f4d9308534cbf9fe4672b34787d30",
  "aliasing": "data:image/png;base64,iVBORw0KGgoAAAA......"
}
  • 校验认证码
### 校验验证码
POST {{checkcode_host}}/checkcode/verify?key=checkcode:ee3f4d9308534cbf9fe4672b34787d30&code=fbl4

下面是验证码使用流程图示

image-20240128145407033

3.3 验证码服务接口

/**
 * @author Mr.M
 * @version 1.0
 * @description 验证码服务接口
 * @date 2022/9/29 18:39
 */
@Api(value = "验证码服务接口")
@RestController
public class CheckCodeController {

    @Resource(name = "PicCheckCodeService")
    private CheckCodeService picCheckCodeService;


    @ApiOperation(value = "生成验证信息", notes = "生成验证信息")
    @PostMapping(value = "/pic")
    public CheckCodeResultDto generatePicCheckCode(CheckCodeParamsDto checkCodeParamsDto) {
        return picCheckCodeService.generate(checkCodeParamsDto);
    }

    @ApiOperation(value = "校验", notes = "校验")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name", value = "业务名称", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "key", value = "验证key", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "code", value = "验证码", required = true, dataType = "String", paramType = "query")
    })
    @PostMapping(value = "/verify")
    public Boolean verify(String key, String code) {
        Boolean isSuccess = picCheckCodeService.verify(key, code);
        return isSuccess;
    }
}

四、 账号密码登录

image-20240128151151371

4.1 PasswordAuthServiceImpl

统一认证接口AuthService的实现类PasswordAuthServiceImpl

在此实现类中实现账号和密码登录方式

AuthService接口会有多个实现类,下面是实现类PasswordAuthServiceImpl

/**
 * 处理用户名密码登录验证
 */
@Service("password_authservice")
public class PasswordAuthServiceImpl implements AuthService {

    @Autowired
    XcUserMapper xcUserMapper;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    CheckCodeClient checkCodeClient;

    @Override
    public XcUserExt execute(AuthParamsDto authParamsDto) {
        // 1.账号
        String username = authParamsDto.getUsername();

        // 2.校验验证码(校验认证码的服务在checkcode模块,利用feign向checkcode发送请求)

        String checkcode = authParamsDto.getCheckcode();
        String checkcodekey = authParamsDto.getCheckcodekey();
        if (StringUtils.isEmpty(checkcode) || StringUtils.isEmpty(checkcodekey)){
            throw new RuntimeException("请输入验证码");
        }
        Boolean verify = checkCodeClient.verify(checkcodekey, checkcode);
        if (verify == null || !verify){
            throw new RuntimeException("验证码输入错误");
        }
        // 3.校验账号是否存在
        // 3.1 根据username账号查询数据库
        // 因为账号是不可能重复的,直接selectOne即可
        LambdaQueryWrapper<XcUser> lqw = new LambdaQueryWrapper<>();
        lqw.eq(XcUser::getUsername, username);
        XcUser xcUser = xcUserMapper.selectOne(lqw);
        // 3.2 查询不到用户,返回null即可
        if (xcUser == null) {
            throw new RuntimeException("账号不存在");
        }

        // 4.验证密码是否正确
        // 用户真正的密码
        String password = xcUser.getPassword();
        // 用户输入的密码
        String passwordForm = authParamsDto.getPassword();
        // 校验密码
        boolean matches = passwordEncoder.matches(passwordForm, password);
        if (!matches) {
            throw new RuntimeException("用户账号或密码错误");
        }

        // 5.这个地方到授权后会再改,先这么写着
        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(xcUser, xcUserExt);
        return xcUserExt;
    }
}

4.2 修改 UserServiceImpl

@Component
public class UserServiceImpl implements UserDetailsService {

    //注入,将来查询对象
    @Autowired
    XcUserMapper xcUserMapper;

    @Autowired
    ApplicationContext applicationContext;

    /**
     * @param s 传入的请求认证的参数是AuthParamDtoJSON串,此时不再是输入的username(账号)
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1.将传入的JSON转成AuthParamsDto对象
        AuthParamsDto authParamsDto = null;
        try {
            authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
        } catch (Exception e) {
            throw new RuntimeException("请求认证的参数不符合要求");
        }

        // 2.判断登录的类型(认证类型,有password、wx)
        String authType = authParamsDto.getAuthType();
        String beanName = authType + "_authservice";
        // 取出指定的bean
        AuthService authService = applicationContext.getBean(beanName, AuthService.class);

        // 3.调用统一认证方法完成认证
        XcUserExt xcUserExt = authService.execute(authParamsDto);

        // 4.封装XcUserExt为UserDetails类型
        UserDetails userDetails = this.getUserPrincipal(xcUserExt);
        return userDetails;
    }

    /**
     * 将XcUserExt数据封装成UserDetails数据
     *
     * @param xcUserExt 用户id,主键
     * @return com.xuecheng.ucenter.model.po.XcUser 用户信息
     * @description 查询用户信息
     */
    public UserDetails getUserPrincipal(XcUserExt xcUserExt) {
        String password = xcUserExt.getPassword();
        //用户权限,如果不加报Cannot pass a null GrantedAuthority collection
        String[] authorities = {"p1"};
        xcUserExt.setPassword(""); // 为了安全考虑,专JSON前吧密码做空
        String userString = JSON.toJSONString(xcUserExt);
        UserDetails userDetails = User.withUsername(userString)
                //用户密码
                .password(password)
                //用户权限,暂时先不写
                .authorities(authorities).build();
        return userDetails;
    }

}

4.3 Feign 接口

远程调用checkcode服务校验验证码

4.3.1 CheckCodeClient

// value 服务名
// fallbackFactory 降级方法执行逻辑
@FeignClient(value = "checkcode", fallbackFactory = CheckCodeClientFactory.class)
@RequestMapping("/checkcode")
public interface CheckCodeClient {

    @PostMapping(value = "/verify")
    public Boolean verify(@RequestParam("key") String key, @RequestParam("code") String code);

}

4.3.2 CheckCodeClientFactory

/**
 * 编写降级方法执行逻辑
 */
@Slf4j
@Component
public class CheckCodeClientFactory implements FallbackFactory<CheckCodeClient> {
    @Override
    public CheckCodeClient create(Throwable throwable) {
        return new CheckCodeClient() {

            @Override
            public Boolean verify(String key, String code) {
                log.debug("调用验证码服务熔断异常:{}", throwable.getMessage());
                return null;
            }
        };
    }
}

4.3.3 开启Feign

启动类添加如下注解

@EnableFeignClients(basePackages={"com.xuecheng.ucenter.service.feignclient"})

4.4 测试

点击登录

image-20240128161256700

登录成功后进入到下图所示界面

image-20240128161350576

五、微信扫码验证

微信扫码登录基于OAuth2协议的授权码模式

接口文档:

https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

点击微信图标

image-20240128161918799

进行扫码注册

image-20240128161939086

5.1 介绍

接口文档:微信开放平台

image-20240128162254967

根据文档的指示,很显然我们使用的这种方式,仔细阅读一下文档就好了

image-20240128162710653

5.1.1 请求获取授权码

第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在 PC 端打开以下链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

若提示“该链接无法访问”,请检查参数是否填写错误

如redirect_uri的域名与审核时填写的授权域名不一致或 scope 不为snsapi_login。

image-20240128175528529

返回说明

用户允许授权后,将会重定向到redirect_uri的网址上,并且带上 code 和state参数

redirect_uri?code=CODE&state=STATE

若用户禁止授权,则不会发生重定向。

登录一号店网站应用 https://test.yhd.com/wechat/login.do 打开后,一号店会生成 state 参数,跳转到 https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a415e#wechat_redirect

微信用户使用微信扫描二维码并且确认登录后,PC端会跳转到 https://test.yhd.com/wechat/callback.do?code=CODE&state=3d6be0a40sssssxxxxx6624a415e

为了满足网站更定制化的需求,我们还提供了第二种获取 code 的方式,支持网站将微信登录二维码内嵌到自己页面中,用户使用微信扫码授权后通过 JS 将code返回给网站。

JS微信登录主要用途:网站希望用户在网站内就能完成登录,无需跳转到微信域下登录后再返回,提升微信登录的流畅性与成功率。 网站内嵌二维码微信登录 JS 实现办法:

步骤1:在页面中先引入如下 JS 文件(支持https):

http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js

步骤2:在需要使用微信登录的地方实例以下 JS 对象:

var obj = new WxLogin({
 self_redirect:true,
 id:"login_container", 
 appid: "", 
 scope: "", 
 redirect_uri: "",
  state: "",
 style: "",
 href: ""
 });

image-20240128175755027

5.1.2 通过code获取access_token

通过 code 获取access_token

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

image-20240128175825146

返回说明

{ 
"access_token":"ACCESS_TOKEN", 
"expires_in":7200, 
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID", 
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

参数说明

image-20240128175855765

{"errcode":40029,"errmsg":"invalid code"}

5.1.3 通过access_token调用接口

获取access_token后,进行接口调用,有以下前提

  • access_token有效且未超时
  • 微信用户已授权给第三方应用帐号相应接口作用域(scope)

对于接口作用域(scope),能调用的接口有以下

image-20240128175952288

其中snsapi_base属于基础接口,若应用已拥有其它 scope 权限,则默认拥有snsapi_base的权限。使用snsapi_base可以让移动端网页授权绕过跳转授权登录页请求用户授权的动作,直接跳转第三方网页带上授权临时票据(code),但会使得用户已授权作用域(scope)仅为snsapi_base,从而导致无法获取到需要用户授权才允许获得的数据和基础功能。 接口调用方法可查阅《微信授权关系接口调用指南》

获取用户信息接口文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html

接口地址

http请求方式: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

image-20240128180042089

响应

{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"

}

说明

参数            说明
openid        普通用户的标识,对当前开发者帐号唯一
nickname        普通用户昵称
sex            普通用户性别,1为男性,2为女性
province        普通用户个人资料填写的省份
city            普通用户个人资料填写的城市
country        国家,如中国为CN
headimgurl        用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
privilege        用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
unionid          用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。

5.2 接入

5.2.1 接入分析

接入微信扫码登录的流程如下

image-20240128180306575

本项目认证服务需要做哪些事

1、需要定义接口接收微信下发的授权码。

2、收到授权码调用微信接口申请令牌。

3、申请到令牌调用微信获取用户信息

4、获取用户信息成功将其写入本项目用户中心数据库。

5、最后重定向到浏览器自动登录

5.1.2 WxLoginController 接口定义

说明

用户扫码与授权都是在前端和微信进行交互的

前端在和微信对接扫码时,会设置一个重定向地址,这个地址就是后端某个接口的请求地址,微信就会向这个地方发送授权码,后端接口获取到授权码后就可以完成很多操作

当后端接口收到来自微信的请求后,便可以完成一系列“重定向、授权码、申请令牌”等等一系列操作,最终实现用户的登录

image-20240128181206728

    /**
     * 微信回调此接口,向服务传送一个授权码
     * <p>
     * 在此接口内我们要:远程调用微信令牌,拿到令牌查询用户信息,将用户信息写入本项目数据库
     *
     * @param code  授权码
     * @param state 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf 攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加 session 进行校验
     * @return
     * @throws IOException
     */
    @RequestMapping("/wxLogin")
    public String wxLogin(String code, String state) throws IOException {
        log.debug("微信扫码回调,code:{},state:{}", code, state);
        //请求微信申请令牌,拿到令牌查询用户信息,将用户信息写入本项目数据库
        XcUser xcUser = wxAuthService.wxAuth(code);

        //暂时硬编写,目的是调试环境
        xcUser.setUsername("t1");
        if (xcUser == null) {
            return "redirect:http://www.51xuecheng.cn/error.html";
        }
        String username = xcUser.getUsername();

        // 重定向到登录界面自动登录  (注意!authType的值是wx)
        return "redirect:http://www.51xuecheng.cn/sign.html?username=" + username + "&authType=wx";
    }

5.2.3 WxAuthServiceImpl

在编写之前先注入这个Bean

@Bean
RestTemplate restTemplate(){
    RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    return  restTemplate;
}

定义一个WxAuthService接口

/**
 * @description 微信扫码认证
 */
@Slf4j
@Service("wx_authservice")
public class WxAuthServiceImpl implements AuthService, WxAuthService {

    @Autowired
    XcUserMapper xcUserMapper;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    XcUserRoleMapper xcUserRoleMapper;

    @Autowired
    WxAuthServiceImpl currentProxy;

    // 微信appid
    String appid = "wx17655f8047b85150";
    // 微信App秘钥
    String secret = "68918d65287802a19b1905cbda7eaa93";

    @Override
    public XcUserExt execute(AuthParamsDto authParamsDto) {
        //账号
        String username = authParamsDto.getUsername();
        XcUser user = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
        if (user == null) {
            //返回空表示用户不存在
            throw new RuntimeException("账号不存在");
        }
        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(user, xcUserExt);
        return xcUserExt;
    }

    /**
     * 微信扫码认证:
     * 1.申请令牌
     * 2.携带令牌查询用户信息
     * 3.保存用户信息到数据库
     *
     * @param code 微信下发的授权码
     * @return
     */
    @Override
    public XcUser wxAuth(String code) {
        // 1.申请令牌
        Map<String, String> accessTokenMap = getAccess_token(code);
        //令牌
        String accessToken = accessTokenMap.get("access_token");
        //openid
        String openid = accessTokenMap.get("openid");

        // 2.携带令牌查询用户信息
        Map<String, String> userInfo = this.getUserInfo(accessToken, openid);

        // 3.保存用户信息到数据库
        // 避免事物失效(一个非事物方法调用事物方法)
        XcUser xcUser = currentProxy.addWxUser(userInfo);

        return xcUser;
    }

    /**
     * 申请令牌
     * 通过code获取access_token
     * https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
     *
     * @param code 微信下发的授权码
     * @return 根据文档发现,返回值是一个JSON信息
     * {
     * "access_token":"ACCESS_TOKEN",
     * "expires_in":7200,
     * "refresh_token":"REFRESH_TOKEN",
     * "openid":"OPENID",
     * "scope":"SCOPE",
     * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     */
    private Map<String, String> getAccess_token(String code) {
        String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
        String url = String.format(url_template, appid, secret, code);
        // 参数三:请求参数设置为null,因为我们拼接在url后面了 参数四:返回值类型,执行String类型就行
        ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, null, String.class);

        //获取响应结果
        String result = exchange.getBody();
        Map<String, String> map = JSON.parseObject(result, Map.class);
        return map;
    }

    /**
     * 携带令牌查询用户信息
     * http请求方式: GET
     * https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
     *
     * @param access_token 令牌
     * @param openid       返回值实例:
     *                     {
     *                     "openid":"OPENID",
     *                     "nickname":"NICKNAME",
     *                     "sex":1,
     *                     "province":"PROVINCE",
     *                     "city":"CITY",
     *                     "country":"COUNTRY",
     *                     "headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
     *                     "privilege":[
     *                     "PRIVILEGE1",
     *                     "PRIVILEGE2"
     *                     ],
     *                     "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
     *                     }
     */
    private Map<String, String> getUserInfo(String access_token, String openid) {
        String wxUrl_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
        //请求微信地址
        String wxUrl = String.format(wxUrl_template, access_token, openid);

        log.info("调用微信接口申请access_token, url:{}", wxUrl);

        ResponseEntity<String> exchange = restTemplate.exchange(wxUrl, HttpMethod.GET, null, String.class);

        //防止乱码进行转码
        // 因为返回值格式是8859-1,我们转换成UTF-8,解决乱码问题
        String result = new String(exchange.getBody().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        Map<String, String> map = JSON.parseObject(result, Map.class);
        return map;
    }

    @Transactional
    public XcUser addWxUser(Map userInfo_map) {
        //wx_unionid是在微信开放平台上唯一的id
        //张三扫描微信二维码登录后会有一个unionid,李四扫描微信二维码登录后会有一个unionid
        String unionid = userInfo_map.get("unionid").toString();
        //根据unionid查询数据库
        XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getWxUnionid, unionid));
        if (xcUser != null) {
            //说明查询到对应的用户了
            return xcUser;
        }
        // 没有查询到用户,新增用户
        String userId = UUID.randomUUID().toString();
        xcUser = new XcUser();
        xcUser.setId(userId);
        xcUser.setWxUnionid(unionid);
        //记录从微信得到的昵称
        xcUser.setNickname(userInfo_map.get("nickname").toString());
        xcUser.setUserpic(userInfo_map.get("headimgurl").toString());
        xcUser.setName(userInfo_map.get("nickname").toString());
        xcUser.setUsername(unionid);
        xcUser.setPassword(unionid);
        xcUser.setUtype("101001");//学生类型
        xcUser.setStatus("1");//用户状态
        xcUser.setCreateTime(LocalDateTime.now());
        xcUserMapper.insert(xcUser);

        // 设置默认角色为 学生 角色
        XcUserRole xcUserRole = new XcUserRole();
        // 主键
        xcUserRole.setId(UUID.randomUUID().toString());
        xcUserRole.setUserId(userId);
        xcUserRole.setRoleId("17");//学生角色
        // 操作的xc_user_role学生角色关系表
        xcUserRoleMapper.insert(xcUserRole);
        return xcUser;
    }

}

5.2.4 xc_user_role 学生角色关系表

image-20240129011625818

5.2.5 xc_role 角色表

image-20240129011658310

image-20240129011646134

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

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

相关文章

【JavaScript 漫游】【022】事件模型

文章简介 本篇文章为【JavaScript 漫游】专栏的第 022 篇文章&#xff0c;对 JavaScript 中事件模型相关的知识点进行了总结。 监听函数 浏览器的事件模型&#xff0c;就是通过监听函数&#xff08;listener&#xff09;对事件做出反应。事件发生后&#xff0c;浏览器监听到…

【栈与队列】用队列实现栈、用栈实现队列

【栈与队列】用队列实现栈、用栈实现队列 一、232. 用栈实现队列二、225. 用队列实现栈 一、232. 用栈实现队列 简单 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&…

树的基本概念和结构

目录 树的概念和结构 树的相关概念 树的特点 树的表示 树的基本应用 树的概念和结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合 &#x1f4cc; 把它叫做树是因为它看起来像一棵倒挂的树&#x…

网络原理——HTTPS

HTTPS是 在HTTP的基础上&#xff0c;引入了一个加密层&#xff08;SSL)。 1. 为什么需要HTTPS 在我们使用浏览器下载一些软件时&#xff0c;相信大家都遇到过这种情况&#xff1a;明明这个链接显示的是下载A软件&#xff0c;点击下载时就变成了B软件&#xff0c;这种情况是运…

ui设计:利用即使设计设计出漂亮样式

目录 一、基本操作 二、具体介绍 6-1 填充图片 6-2 填充色 6-3 图标 右边栏基础设置 右边栏导出​编辑 一、基本操作 二、具体介绍 6-1 填充图片 选择其一图片填充 6-2 填充色 6-3 图标 右边栏基础设置 右边栏导出

程序员的护城河是什么?最终走向……?

程序员未来会大量失业&#xff0c;就是因为社会需求少&#xff0c;导致开发者岗位减少&#xff0c;人力资源过剩所导致。Android刚开始的零几年非常火热&#xff0c;是个人都要。到如今的内卷&#xff0c;高级开发都拿着中低程序员的薪资。这是因为头部大厂形成标准化&#xff…

【一篇教程读懂Vray渲染器】3DMAX中玻璃酒杯Vray渲染教程

本教程试图用最简单的例子&#xff0c;给大家讲解在3DMAX中如何使用Vray渲染器进行渲染。 酒杯建模&#xff1a; 1.在前视图中用样条线画出酒杯的侧轮廓线。 2.应用“车削”修改器&#xff0c;完成杯子建模。 编辑材质&#xff1a; 3.将当前渲染器设置为Vray渲染器。 …

面试经典150题【31-40】

文章目录 面试经典150题【31-40】76.最小覆盖字串36.有效的数独54.螺旋矩阵48.旋转图像73.矩阵置零289.生命游戏383.赎金信205.同构字符串290.单词规律242.有效的字母异位词 面试经典150题【31-40】 76.最小覆盖字串 基本思路很简单&#xff0c;就是先移动右边到合适位置。再移…

docker容器配置mysql5.7主从复制

介绍 本文将通过docker创建3个mysql数据库容器&#xff0c;实现数据库主从复制功能&#xff0c;三个数据库容器分别为主库mysql-master:3307&#xff0c;从库mysql-slave-01:3308&#xff0c;mysql-slave-02:3309。使用的是mysql5.7版本 1. 拉取mongo镜像 docker pull mysql…

并发编程线程安全性之可见性有序性

可见性 可见性: 就是说一个线程对共享变量的修改&#xff0c;另一个线程能够立刻看到 通俗点说&#xff0c;就是两个线程共享一个变量&#xff0c;无论哪一个线程修改了这个变量&#xff0c;另外一个线程都能够立刻看到上一个线程对这个变量的修改 产生线程安全问题的原因 计…

支付流程的理解

开发指引-JSAPI支付 | 微信支付商户平台文档中心 业务流程图&#xff1a; 首先&#xff0c;从业务流程&#xff0c;当有人问你的时候&#xff0c;反问&#xff1a;公司有没有确认跟支付宝合作还是跟微信合作&#xff0c;看有没有签订协议&#xff0c;有的话&#xff0c;要确认…

C# OpenCvSharp Tracker 目标追踪

目录 效果 项目 代码 下载 C# OpenCvSharp Tracker 目标追踪 效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using OpenCvSharp.Tracking; using System; using System.Drawing; using System.Reflection; using System.Windows.Forms; namespace C…

iconfont的组件化使用方法(SVG)

目录 一、需求描述二、操作步骤1.在iconfont中选择项目需要使用的图标2.在项目中创建iconfont.js3.创建svgIcon组件 一、需求描述 将iconfont图标库选择的图标以SVG的形式引入项目并通过组件化的形式在项目中引用可控制图标的大小和颜色 二、操作步骤 1.在iconfont中选择项目…

【论文笔记之 YIN】YIN, a fundamental frequency estimator for speech and music

本文对 Alain de Cheveigne 等人于 2002 年在 The Journal of the Acoustical Society of America 上发表的论文进行简单地翻译。如有表述不当之处欢迎批评指正。欢迎任何形式的转载&#xff0c;但请务必注明出处。 论文链接&#xff1a;http://audition.ens.fr/adc/pdf/2002_…

单片机精进之路-5矩阵键盘扫描

如下图&#xff0c;先在p3口输出0xfe&#xff0c;再读取p3口的电平&#xff0c;如果没有按键按下&#xff0c;temp & 0xf0还是0xf0&#xff0c;如果又第一个键按下&#xff0c;temp & 0xf0还是0xee&#xff0c;其他按键由此类推可得。 //4*4键盘检测程序,按下键后相应…

【笔记】【电子科大 离散数学】 2.命题

文章目录 数理逻辑定义 命题定义不是命题的例子 原子命题和复合命题定义约定 命题联结词否定联结词定义例子真值表 合取联结词定义例子真值表 析取联结词定义例子 蕴含联结词定义例子真值表 等价联结词定义例子真值表 命题符号化及其应用速查表格优先级复合命题符号化布尔检索演…

golang学习1,dea的golang-1.22.0

参考&#xff1a;使用IDEA配置GO的开发环境备忘录-CSDN博客 1.下载All releases - The Go Programming Language (google.cn) 2.直接next 3.window环境变量配置 4.idea的go插件安装 5.新建go项目找不到jdk解决 https://blog.csdn.net/ouyang111222/article/details/1361657…

docker存储驱动

目录 一、写时复制和用时分配 二、联合文件系统 2.1、aufs ​编辑 2.2、分层的问题 2.3、overlay 2.4 文件系统区别 三、容器跑httpd案例 3.1、案例1&#xff1a;端口映射 3.2、案例2&#xff1a;制作httpd应用镜像 3.3、案例3&#xff1a;docker数据卷挂载 3.4、案…

sqllabs的order by注入

当我们在打开sqli-labs的46关发现其实是个表格&#xff0c;当测试sort等于123时&#xff0c;会根据列数的不同来进行排序 我们需要利用这个点来判断是否存在注入漏洞&#xff0c;通过加入asc 和desc判断页面有注入点 1、基于使用if语句盲注 如果我们配合if函数&#xff0c;表达…

《Docker 简易速速上手小册》第4章 Docker 容器管理(2024 最新版)

文章目录 4.1 容器生命周期管理4.1.1 重点基础知识4.1.2 重点案例&#xff1a;启动并管理 Python Flask 应用容器4.1.3 拓展案例 1&#xff1a;调试运行中的容器4.1.4 拓展案例 2&#xff1a;优雅地停止和清理容器 4.2 容器数据管理与持久化4.2.1 重点基础知识4.2.2 重点案例&a…