一:唠嗑

  • 鼓捣了两天的Spring Security,踩了不少坑。如果你在学Spring Security,恰好又是使用的Spring Boot,那么给我点个赞吧!这篇博客将会让你了解Spring Security的各种坑!
  • 阅读前说一下,这篇博客是我一字一字打出来的,转载务必注明出处哦!
  • 另外,本文已授权微信公众号“后端技术精选”独家发布

二:开始

1.准备

  • Spring boot 1.5
  • Mysql 5.7
  • 导入依赖
  1. <!-- Web工程 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!-- 数据库相关 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-data-jpa</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>mysql</groupId>
  13. <artifactId>mysql-connector-java</artifactId>
  14. </dependency>
  15. <!-- security 核心 -->
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-security</artifactId>
  19. </dependency>
  20. <!-- thymeleaf 模板-->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  24. </dependency>
  25. <!-- 可以在HTML使用sec标签操作Security -->
  26. <dependency>
  27. <groupId>org.thymeleaf.extras</groupId>
  28. <artifactId>thymeleaf-extras-springsecurity4</artifactId>
  29. </dependency>

2.开启Security并配置

  1. package cn.zyzpp.security.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.security.authentication.AuthenticationProvider;
  5. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  6. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  7. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  8. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  9. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. /**
  12. * Create by yster@foxmail.com 2018/6/10/010 18:07
  13. */
  14. @EnableWebSecurity
  15. public class MySerurityConfig extends WebSecurityConfigurerAdapter {
  16. /*自己实现下面两个接口*/
  17. @Autowired
  18. private AuthenticationProvider authenticationProvider;
  19. @Autowired
  20. private UserDetailsService userDetailsService;
  21. @Override
  22. protected void configure(HttpSecurity http) throws Exception {
  23. http.authorizeRequests()
  24. .antMatchers("/", "/signIn").permitAll()//所有人都可以访问
  25. .antMatchers("/leve/1").hasRole("VIP1") //设置访问角色
  26. .antMatchers("/leve/2").hasRole("VIP2")
  27. .antMatchers("/leve/3").hasAuthority("VIP2")//设置访问权限
  28. .anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问
  29. .and()
  30. .formLogin()//开启自动配置的授权功能
  31. .loginPage("/login") //自定义登录页(controller层需要声明)
  32. .usernameParameter("username") //自定义用户名name值
  33. .passwordParameter("password") //自定义密码name值
  34. .failureUrl("/login?error") //登录失败则重定向到此URl
  35. .permitAll() //登录页都可以访问
  36. .and()
  37. .logout()//开启自动配置的注销功能
  38. .logoutSuccessUrl("/")//注销成功后返回到页面并清空Session
  39. .and()
  40. .rememberMe()
  41. .rememberMeParameter("remember")//自定义rememberMe的name值,默认remember-Me
  42. .tokenValiditySeconds(604800);//记住我的时间/秒
  43. }
  44. /*定义认证规则*/
  45. @Override
  46. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  47. /* 保存用户信息到内存中
  48. auth.inMemoryAuthentication()
  49. .withUser("张三").password("123456").roles("VIP1")
  50. .and()
  51. .withUser("李四").password("123456").roles("VIP2");
  52. */
  53. /*自定义认证*/
  54. auth.authenticationProvider(authenticationProvider);
  55. auth.userDetailsService(userDetailsService);//不定义的话rememberMe报错
  56. }
  57. /*忽略静态资源*/
  58. @Override
  59. public void configure(WebSecurity web) {
  60. web.ignoring().antMatchers("/resources/static/**");
  61. }
  62. }

讲一下:

  • 我们基本不会把用户信息保存在内存中,所以我们自定义认证方法。这里我推荐阅读 认证(Authentication)与源码解读 了解。
  • 自定义认证也有两种方法,第一是注入DaoAuthenticationProvider(org.springframework.security.authentication.dao)
  1. @Bean
  2. public DaoAuthenticationProvider daoAuthenticationProvider(){
  3. DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
  4. daoAuthenticationProvider.setUserDetailsService(userDetailsService);//获取用户信息
  5. daoAuthenticationProvider.setPasswordEncoder(new Md5PasswordEncoder());//MD5加密
  6. daoAuthenticationProvider.setSaltSource(new SaltSource() { //加盐
  7. @Override
  8. public Object getSalt(UserDetails user) {
  9. return user.getUsername();
  10. }
  11. });
  12. return daoAuthenticationProvider;
  13. }
  • 然后改一下设置
  1. auth.authenticationProvider(authenticationProvider);
  • 这种方法我并不推荐,因为我们把密码错误的异常交给了Security底层去抛出,然而抛出的消息只是Bad credentials 这样的消息提示你会需要?

  • 所以我们使用第二种方法,如下:


3.自定义AuthenticationProvider接口实现类

  1. package cn.zyzpp.security.config;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.security.authentication.AuthenticationProvider;
  4. import org.springframework.security.authentication.DisabledException;
  5. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6. import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
  7. import org.springframework.security.core.Authentication;
  8. import org.springframework.security.core.userdetails.UserDetails;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * Create by yster@foxmail.com 2018/6/21/021 15:53
  12. * Authentication 是一个接口,用来表示用户认证信息的
  13. */
  14. @Component
  15. public class MyAuthenticationProvider implements AuthenticationProvider{
  16. @Autowired
  17. private MyUserDetailsService userDetailsService;
  18. @Override
  19. public Authentication authenticate(Authentication authentication){
  20. //1.获取用户输入的用户名 密码
  21. String username = authentication.getName();
  22. String password = (String) authentication.getCredentials();
  23. //2.关于MD5加密:
  24. //因为我们是自定义Authentication,所以必须手动加密加盐而不需要再配置。
  25. password = new Md5PasswordEncoder().encodePassword(password,username);
  26. //3.由输入的用户名查找该用户信息,内部抛出异常
  27. UserDetails user = userDetailsService.loadUserByUsername(username);
  28. //4.密码校验
  29. if (!password.equals(user.getPassword())) {
  30. throw new DisabledException("---->UserName :" + username + " password error!");
  31. }
  32. return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
  33. }
  34. @Override
  35. public boolean supports(Class<?> aClass) {
  36. return (UsernamePasswordAuthenticationToken.class
  37. .isAssignableFrom(aClass));
  38. }
  39. }

讲一下:

  • 这里说Security的一个坑:
  • 相信你也看到了有的教程上说抛出UsernameNotFoundException 用户找不到,BadCredentialsException 坏的凭据,但这两个类都是继承自AuthenticationException抽象类,当你抛出这俩异常时,Security底层会捕捉到你抛出的异常,如图:

  • 看到了吧,AuthenticationException异常并不会被抛出,debug调式一下,你就会感受到它的曲折历程,相当感人!然后莫名其妙的被换掉了,而且无解。

  • 没错,你没看错,AccountStatusException异常被直接抛出了,这正是我们需要的;有的同学可能想到了自定义异常,但我们是结合Security框架,要按人家的规则来,不信你试试。
  • 附一些常用异常
  1. /*
  2. AuthenticationException常用的的子类:(会被底层换掉,不推荐使用)
  3. UsernameNotFoundException 用户找不到
  4. BadCredentialsException 坏的凭据
  5. AccountStatusException用户状态异常它包含如下子类:(推荐使用)
  6. AccountExpiredException 账户过期
  7. LockedException 账户锁定
  8. DisabledException 账户不可用
  9. CredentialsExpiredException 证书过期
  10. */

4.自定义UserDetailsService接口实现类

  1. package cn.zyzpp.security.config;
  2. import cn.zyzpp.security.entity.Role;
  3. import cn.zyzpp.security.service.UserService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.security.authentication.DisabledException;
  6. import org.springframework.security.core.GrantedAuthority;
  7. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  8. import org.springframework.security.core.userdetails.User;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. import org.springframework.stereotype.Component;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. /**
  15. * 进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,
  16. * 其中包括用户名、密码和所拥有的权限等。
  17. * Create by yster@foxmail.com 2018/6/21/021 15:56
  18. */
  19. @Component
  20. public class MyUserDetailsService implements UserDetailsService {
  21. @Autowired
  22. private UserService userService;
  23. /*
  24. * 采坑笔记:
  25. * new SimpleGrantedAuthority("...")时
  26. * 加前戳是Role,通过hasRole()获取,用来认证角色;
  27. * 不加前戳是Authoritiy,通过hasAuthority()获取,用来鉴定权限;
  28. * 总结:加前戳是角色,不加前戳是权限。此前戳只用于本类。
  29. */
  30. String role_ = "ROLE_";
  31. @Override
  32. public UserDetails loadUserByUsername(String username) {
  33. //1.业务层根据username获取该用户
  34. cn.zyzpp.security.entity.User user = userService.findUserByUserName(username);
  35. if (user == null) {
  36. //这里我们不抛出UsernameNotFoundException因为Security会把我们抛出的该异常捕捉并换掉;
  37. //这里要明确Security抛出的异常无法被ControllerAdvice捕捉到,无法进行统一异常处理;
  38. //而我们只需要打印正确的异常消息即可,Security自动把异常添加到HttpServletRequest或HttpSession中
  39. throw new DisabledException("---->UserName :" + username + " not found!");
  40. }
  41. //2.从业务层获取用户权限并转为Authorities
  42. List<GrantedAuthority> authorities = new ArrayList<>();
  43. for (Role role : user.getRoleList()) {
  44. authorities.add(new SimpleGrantedAuthority(role.getName()));//设置权限
  45. authorities.add(new SimpleGrantedAuthority(role_ + role.getName()));//设置角色
  46. }
  47. //3.返回Spring定义的User对象
  48. return new User(username, user.getPassword(), authorities);
  49. }
  50. }

讲一下:

  • 我们在保存用户信息到内存中时是这样的
  1. auth.inMemoryAuthentication()
  2. .withUser("张三")
  3. .password("123456")
  4. .roles("ROLE_VIP1")
  5. .authorities("VIP1")
  • 角色和权限是分开设置的,但我们在自定义时只有权限设置,
  1. authorities.add(new SimpleGrantedAuthority("权限名"));
  • 定义以后你会发现这真真真…的是权限,不是角色,联想到上面Security的角色和权限其实是不同的,我想我应该是错过了什么?
  • 然后翻看Security源码:

  • 翻译过来:如果调用hasRole(“ADMIN”)或hasRole(“ROLE_ADMIN”)
    方法时,当Role前缀为”ROLE_”(默认)时将使用ROLE_ADMIN角色。

  • 而我们在把用户信息保存到内存时,底层是这样的:

  • 解读一下就是在调用.roles("ROLE_VIP1")方法注册Role时,先通过role.startsWith("ROLE_")断言输入的角色名是否是"ROLE_"开头的,如果不是,补充"RELE_"前戳。

  • 所以,Security解决角色和权限分开的依据就是是否含有"ROLE_"前戳,该默认前戳也是可以自己修改的。
  • ok,继续我们的Security学习之路。

5.获取Security登录异常信息

  1. package cn.zyzpp.security.controller;
  2. ...
  3. import cn.zyzpp.security.service.UserService;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpSession;
  6. /**
  7. * Create by yster@foxmail.com 2018/6/10/010 18:35
  8. */
  9. @Controller
  10. public class MyController {
  11. @Autowired
  12. UserService userService;
  13. @Autowired
  14. HttpSession session;
  15. @Autowired
  16. HttpServletRequest request;
  17. /*ModelMap的Key*/
  18. final String ERROR = "error";
  19. /**
  20. * 自定义登录页并进行异常信息提示
  21. * 需要在Security中设置
  22. */
  23. @RequestMapping(value = "/login")
  24. public String login(ModelMap modelMap){
  25. /*
  26. security的AuthenticationException异常自动保存在request或session中
  27. 官方默认保存在Session,但我们自定义过多。我测试是在request中。
  28. 所以在html页面还需要搭配th:if="${param.error!=null}"检查Url是否有参数error
  29. */
  30. String key = WebAttributes.AUTHENTICATION_EXCEPTION;
  31. if (session.getAttribute(key)!=null){
  32. // System.out.println("request");
  33. AuthenticationException exception = (AuthenticationException) session.getAttribute(key);
  34. modelMap.addAttribute(ERROR,exception.getMessage());
  35. }
  36. if (request.getAttribute(key)!=null){
  37. // System.out.println("session");
  38. AuthenticationException exception = (AuthenticationException) request.getAttribute(key);
  39. modelMap.addAttribute(ERROR,exception.getMessage());
  40. }
  41. return "login";
  42. }
  43. }

自定义login登录页面

  • Security规定若是GET访问则是请求页面,POST访问则为提交登录
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>登录页面</title>
  6. </head>
  7. <body>
  8. <form th:action="@{/login}" method="post">
  9. 用户名:<input type="text" placeholder="username" name="username" required=""/><br/>
  10. 密码:<input type="password" placeholder="password" name="password" required=""/><br/>
  11. 记住我:<input type="checkbox" name="remember"/>
  12. <input type="submit" value="提交"/>
  13. <span th:if="${param.error!=null}" th:text="${error}"/>
  14. </form>
  15. </body>
  16. </html>

讲一下:

  • 如果你debug追踪一下,你就可以了解Security的运行原理
  • Security的SimpleUrlAuthenticationFailureHandler(简单认证故障处理)会把异常保存到requestsession中,forwardToDestination默认为false,也就是保存在session,实际我们测试是保存在request

6.在view层使用Security

6.1 使用HTML sec标签 (推荐)

  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>首页</title>
  6. </head>
  7. <body>
  8. <div sec:authorize="isAuthenticated()">
  9. <form th:action="@{/logout}" method="POST">
  10. <input type="submit" value="注销" />
  11. </form>
  12. user:<b sec:authentication="name"></b><br/>
  13. <!-- principal对应org.springframework.security.core.userdetails.User类 -->
  14. Role:<b sec:authentication="principal.authorities"></b>
  15. </div>
  16. <div sec:authorize="!isAuthenticated()">
  17. <h2>游客你好!</h2><a th:href="@{/login}">登录</a>
  18. </div>
  19. <div sec:authorize="hasRole('VIP1')">
  20. <h2>ROLE_VIP1_可见</h2>
  21. </div>
  22. <div sec:authorize="hasRole('VIP2')">
  23. <h2>ROLE_VIP2_可见</h2>
  24. </div>
  25. <div sec:authorize="hasAuthority('VIP1')">
  26. <h2>Authority:VIP1_可见</h2>
  27. </div>
  28. </body>
  29. </html>

6.2 编码获取用户登录信息

  • 下面为我自己写的方法,看看就好!
  1. /**
  2. * 不使用sec标签(不推荐)
  3. * 在Controller获取用户信息
  4. */
  5. @RequestMapping("/index")
  6. public String index1(ModelMap model){
  7. userAndRoles(model);
  8. return "index";
  9. }
  10. /**
  11. * Security辅助方法:获取用户信息
  12. */
  13. private void userAndRoles(ModelMap model) {
  14. //从Security获取当前用户会话
  15. Object principal = SecurityContextHolder.getContext()
  16. .getAuthentication()
  17. .getPrincipal();
  18. User user = null;
  19. //判断用户已经登录
  20. if (principal instanceof User){
  21. user = (User) principal;
  22. //遍历迭代器获取用户权限
  23. Iterator<GrantedAuthority> iterator = user.getAuthorities().iterator();
  24. List<String> roles = new ArrayList<>();
  25. while (iterator.hasNext()){
  26. roles.add(iterator.next().getAuthority());
  27. }
  28. //保存角色信息
  29. model.addAttribute("roles",roles.toString());
  30. }
  31. //保存用户信息,未登录为空
  32. model.addAttribute("user",user);
  33. }

6.权限及用户的Entity类

  • 权限表
  1. /**
  2. * 权限表
  3. * Create by yster@foxmail.com 2018/6/21/021 18:00
  4. */
  5. @Entity
  6. @Table(name = "role")
  7. public class Role {
  8. @Id
  9. @GeneratedValue
  10. private int id;
  11. private String name;
  12. ...
  13. }
  • 用户表
  1. /**
  2. * Create by yster@foxmail.com 2018/6/21/021 17:59
  3. */
  4. @Entity
  5. @Table(name = "user",uniqueConstraints = {@UniqueConstraint(columnNames="username")})
  6. public class User {
  7. @Id
  8. @GeneratedValue
  9. private int id;
  10. private String username;
  11. private String password;
  12. @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
  13. @JoinColumn(name = "r_id")
  14. private List<Role> roleList;
  15. ....
  16. }

2019/1/9补充

Spring Security在方法级别上的保护

Spring Security从2.0版本开始,提供了方法级别的安全支持,并提供了 JSR-250 的支持。写一个配置类 SecurityConfig 继承 WebSecurityConfigurationAdapter,并加上相关注解,就可以开启方法级别的保护。

  1. @EnableWebSecurity
  2. @Configuration
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class SerurityConfig extends WebSecurityConfigurerAdapter {
  5. }

在上面的配置代码中,@EnableGlobalMethodSecurity(prePostEnabled = true) 注解开启了方法级别的保护,括号后面的参数可选,可选的参数如下。

  • prePostEnabled:Spring Security 的 Pre 和 Post 注解是否可用,即 @PreAuthorize 和 @PostAuthorize 是否可用。
  • secureEnabled:Spring Security 的 @Service 注解是否可用。
  • jsr250Enabled:Spring Security 对 JSR-250 的注解是否可用。

一般来说,只会用到 prePostEnabled。因为 即 @PreAuthorize 注解比 @PostAuthorize 注解更适合方法级别的安全控制,并且支持 Spring EL 表达式,适合 Spring 开发者。其中,@PreAuthorize 注解会在进入方法钱进行权限验证,@PostAuthorize 注解在方法执行后再进行权限验证。

如何在方法上写权限注解呢?
例如有权限点字符串“ROLE_ADMIN”,在方法上可以写为 @PreAuthorize(“hasRole(‘ADMIN’)”),也可以写为 @PreAuthorize(“hasAuthority(‘ROLE_ADMIN’)”),这二者是等价的。加多个权限点,可以写为 @PreAuthorize(“hasRole(‘ADMIN’,‘USER’)”)、@PreAuthorize(“hasAuthority(‘ROLE_ADMIN’,‘ROLE_USER’)”)。

从源码看Spring Security之采坑笔记(Spring Boot篇)的更多相关文章

  1. clickhouse源码Redhat系列机单机版安装踩坑笔记

    前情概要 由于工作需要用到clickhouse, 这里暂不介绍概念,应用场景,谷歌,百度一大把. 将安装过程踩下的坑记录下来备用 ClickHouse源码 git clone安装(直接下载源码包安装失 ...

  2. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  3. spring源码解析(一) 环境搭建(各种坑的解决办法)

    上次搭建spring源码的环境还是两年前,依稀记得那时候也是一顿折腾,奈何当时没有记录,导致两年后的今天把坑重踩了一遍,还遇到了新的坑,真是欲哭无泪;为了以后类似的事情不再发生,这次写下这篇博文来必坑 ...

  4. 从源码看Azkaban作业流下发过程

    上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...

  5. 从源码看Android中sqlite是怎么通过cursorwindow读DB的

    更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ 执行query 执行SQLiteDatabase类中query系列函数 ...

  6. 从源码看Android中sqlite是怎么读DB的(转)

    执行query 执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询. (query的源码追踪路径) 执行move(里面的fillwindow是真正打开文件句柄并分 ...

  7. 从Chrome源码看浏览器的事件机制

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  8. 从Chrome源码看JS Array的实现

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  9. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

随机推荐

  1. 官网下载的Struts 2解压后缺少xwork-core.jar文件

    为Eclipse配置Struts-2.5.10所需最少jar文件: 缺少的文件已被合并在struts2-core-2.5.10.jar文件中.我下的是最新版的,如果你下的找不到就是这个原因啦.

  2. 关于bug的一些思考

    上午看了两道算法,自己编译器上面敲了一遍,然后又去网站上敲了一遍: 编译器上面无论哦如何都调不出来,网站上面也是: 吃个午饭,睡个觉,醒来重新手撸了一遍,然后就过了 : 面对这种事情,真的是自己应该多 ...

  3. Spark SQL整体架构

    0.整体架构 注意:Spark SQL是Spark Core之上的一个模块,所有SQL操作最终都通过Catalyst翻译成类似的Spark程序代码被Spark Core调度执行,其过程也有Job.St ...

  4. jar、war、ear

    附加jboss里面的application.xml <?xml version=”1.0″ encoding=”UTF-8″?> <application xmlns="h ...

  5. C#核心基础--类的声明

    C#核心基础--类的声明 类是使用关键字 class 声明的,如下面的示例所示: 访问修饰符 class 类名 { //类成员: // Methods, properties, fields, eve ...

  6. postgresql自定义类型并返回数组

    转自 https://blog.csdn.net/victor_ww/article/details/44415895 create type custom_data_type as ( id int ...

  7. 在泛微系统中修改AD密码的配置

    参照文档: Windows server 2008 R2 安装AD域证书:https://blog.csdn.net/zhuyongru/article/details/81107839 配置泛微OA ...

  8. c/c++ 重载运算符的思考

    c/c++ 重载运算符的思考 #include <iostream> using namespace std; class Imaginary{ public: Imaginary():r ...

  9. 02.Python网络爬虫第二弹《http和https协议》

    一.HTTP协议 1.官方概念: HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文 ...

  10. 关于this的理解

    var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //windo ...