一丶SpringSecurity+oauth2(密码模式)方式进行认证授权

1.新增自定义配置类实现WebSecurityConfigurerAdapter,重写其中的configure(HttpSecurity http)方法,配置登陆页面以及登陆请求url等参数。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationSuccessHandler authenticationSuccessHandler;@Autowired(required = false)private AuthenticationEntryPoint authenticationEntryPoint;@Resourceprivate UserDetailsService userDetailsService;@Autowiredprivate PasswordEncoder passwordEncoder;@Resourceprivate LogoutHandler oauthLogoutHandler;@Autowiredprivate OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;@Autowiredprivate MobileAuthenticationSecurityConfig mobileAuthenticationSecurityConfig;@Autowiredprivate MobileAuthrnticationCodeSecurityConfig mobileAuthrnticationCodeSecurityConfig;@Autowiredprivate PasswordAuthrnticationCodeSecurityConfig passwordAuthrnticationCodeSecurityConfig;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate TenantAuthenticationSecurityConfig tenantAuthenticationSecurityConfig;@Autowiredprivate TenantProperties tenantProperties;/*** 这一步的配置是必不可少的,否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户* @return 认证管理对象*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest()//授权服务器关闭basic认证.permitAll().and().logout().logoutUrl(SecurityConstants.LOGOUT_URL).logoutSuccessHandler(new OauthLogoutSuccessHandler()).addLogoutHandler(oauthLogoutHandler).clearAuthentication(true).and().apply(openIdAuthenticationSecurityConfig).and().apply(mobileAuthenticationSecurityConfig).and().apply(passwordAuthrnticationCodeSecurityConfig).and().apply(mobileAuthrnticationCodeSecurityConfig).and().addFilterBefore(new LoginProcessSetTenantFilter(), UsernamePasswordAuthenticationFilter.class).csrf().disable()// 解决不允许显示在iframe的问题.headers().frameOptions().disable().cacheControl();http.formLogin().loginPage(SecurityConstants.LOGIN_PAGE).loginProcessingUrl(SecurityConstants.OAUTH_LOGIN_PRO_URL).successHandler(authenticationSuccessHandler);// 基于密码 等模式可以无session,不支持授权码模式if (authenticationEntryPoint != null) {http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);} else {// 授权码模式单独处理,需要session的支持,此模式可以支持所有oauth2的认证http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);}}}

2.OAuth2 授权服务器配置

@Configuration
@EnableAuthorizationServer
@AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 注入authenticationManager 来支持 password grant type*/@Autowiredprivate AuthenticationManager authenticationManager;@Resourceprivate UserDetailsService userDetailsService;@Autowiredprivate TokenStore tokenStore;@Autowiredprivate WebResponseExceptionTranslator webResponseExceptionTranslator;@Autowiredprivate RedisClientDetailsService clientDetailsService;@Autowiredprivate RandomValueAuthorizationCodeServices authorizationCodeServices;@Autowiredprivate TokenGranter tokenGranter;/*** 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory* @param endpoints*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.tokenStore(tokenStore).authenticationManager(authenticationManager).userDetailsService(userDetailsService).authorizationCodeServices(authorizationCodeServices).exceptionTranslator(webResponseExceptionTranslator).tokenGranter(tokenGranter);}/*** 配置应用名称 应用id* 配置OAuth2的客户端相关信息* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetailsService);clientDetailsService.loadAllClientToCache();}/*** 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器* @param security*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.tokenKeyAccess("isAuthenticated()").checkTokenAccess("permitAll()")//让/oauth/token支持client_id以及client_secret作登录认证.allowFormAuthenticationForClients();}
}

3.前端部分代码,需要传入grant_type,deviceId,username,password,例如请求是这样的:

https://oauth.b.com/oauth/token?grant_type=password&       # 授权方式是"密码式"username=USERNAME&password=PASSWORD&client_id=CLIENT_ID&client_secret=123123&scope=all
                    //账号密码登陆var grant_type = 'password_code';var deviceId =  $("input[name=deviceId]").val();var validCode = $("input[name=validCode]").val();layer.load(2);var clients = $("#clients").attr("value");var loginData ={"clients":clients,"grant_type":grant_type,"username":username,"password":hex_md5(password) ,"deviceId":deviceId,"validCode":validCode}config.putApp(clients);$.ajax({url: config.base_server + '/oauth/token',xhrFields: {withCredentials: true},data: loginData,type: 'POST',beforeSend: function (xhr) {xhr.setRequestHeader('Authorization', 'Basic ' + window.btoa(config.clientId + ":" + config.clientSecret));},success: function (data) {if (data.resp_code === 0) {config.putToken(data.datas);layer.msg('登录成功', {icon: 1, time: 500}, function () {location.replace('./');});} else {layer.closeAll('loading');layer.msg(data.resp_msg, {icon: 5, time: 500});}},error: function (xhr) {layer.closeAll('loading');//区分错误信息//验证码错误if(xhr.responseJSON.error === 'invalid_grant'){layer.msg(xhr.responseJSON.resp_msg, {icon: 5, time: 500});}else if(xhr.responseJSON.error === 'unsupported_response_type'){//账号错误var win = layer.open({content:'<div>该账号已经被系统锁定或者禁用,<br>如需帮助请及时联系系统管理员进行处理!</div>',btn: ['确定'],btnAlign: 'c',closeBtn: 0,yes: function(index, layero){layer.close(win);}});}else {layer.msg(xhr.responseJSON.resp_msg, {icon: 5, time: 500});}var src = $(".login-code").attr("src");$(".login-code").attr("src", src + '?t=' + (new Date).getTime());}});//阻止表单跳转return false;

4.如果需要其他自定义的授权模式,可以新增一个配置类。

@Configuration
public class TokenGranterConfig {@Autowiredprivate ClientDetailsService clientDetailsService;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate TokenStore tokenStore;@Autowired(required = false)private List<TokenEnhancer> tokenEnhancer;@Autowiredprivate IValidateCodeService validateCodeService;@Autowiredprivate RandomValueAuthorizationCodeServices authorizationCodeServices;private boolean reuseRefreshToken = true;private AuthorizationServerTokenServices tokenServices;private TokenGranter tokenGranter;@Autowiredprivate UserService userService;/*** 是否登录同应用同账号互踢*/@Value("${zlt.uaa.isSingleLogin:false}")private boolean isSingleLogin;/*** 授权模式*/@Beanpublic TokenGranter tokenGranter() {if (tokenGranter == null) {tokenGranter = new TokenGranter() {private CompositeTokenGranter delegate;@Overridepublic OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {if (delegate == null) {delegate = new CompositeTokenGranter(getAllTokenGranters());}return delegate.grant(grantType, tokenRequest);}};}return tokenGranter;}/*** 所有授权模式:默认的5种模式 + 自定义的模式*/private List<TokenGranter> getAllTokenGranters() {AuthorizationServerTokenServices tokenServices = tokenServices();AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();OAuth2RequestFactory requestFactory = requestFactory();//获取默认的授权模式List<TokenGranter> tokenGranters = getDefaultTokenGranters(tokenServices, authorizationCodeServices, requestFactory);if (authenticationManager != null) {//添加手机号加验证码tokenGranters.add(new MobileCodeGranter(authenticationManager,tokenServices,clientDetailsService,requestFactory, validateCodeService));// 添加密码加图形验证码模式tokenGranters.add(new PwdImgCodeGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory, validateCodeService,userService));// 添加openId模式tokenGranters.add(new OpenIdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));// 添加手机号加密码授权模式tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));tokenGranters.add(new PwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory,userService));}return tokenGranters;}/*** 默认的授权模式*/private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerTokenServices tokenServices, AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) {List<TokenGranter> tokenGranters = new ArrayList<>();// 添加授权码模式tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));// 添加刷新令牌的模式tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));// 添加隐士授权模式tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));// 添加客户端模式tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));if (authenticationManager != null) {// 添加密码模式tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));}return tokenGranters;}private AuthorizationServerTokenServices tokenServices() {if (tokenServices != null) {return tokenServices;}this.tokenServices = createDefaultTokenServices();return tokenServices;}private AuthorizationCodeServices authorizationCodeServices() {if (authorizationCodeServices == null) {authorizationCodeServices = new InMemoryAuthorizationCodeServices();}return authorizationCodeServices;}private OAuth2RequestFactory requestFactory() {return new DefaultOAuth2RequestFactory(clientDetailsService);}private DefaultTokenServices createDefaultTokenServices() {DefaultTokenServices tokenServices = new CustomTokenServices(isSingleLogin);tokenServices.setTokenStore(tokenStore);tokenServices.setSupportRefreshToken(true);tokenServices.setReuseRefreshToken(reuseRefreshToken);tokenServices.setClientDetailsService(clientDetailsService);tokenServices.setTokenEnhancer(tokenEnhancer());addUserDetailsService(tokenServices, this.userDetailsService);return tokenServices;}private TokenEnhancer tokenEnhancer() {if (tokenEnhancer != null) {TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(tokenEnhancer);return tokenEnhancerChain;}return null;}private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {if (userDetailsService != null) {PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService));tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));}}
}

二丶SpringSecurity+自定义登陆控制器(使用最多)

1.自定义类继承CustomWebSecurityConfigurerAdapter,配置请求过滤路径等

@Configuration
//@EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {@Autowiredprivate MemoryCacheSecurityContextRepository memoryCacheSecurityContextRepository;@Autowiredprivate CustomAuthenticatedSessionStrategy customAuthenticatedSessionStrategy;@Autowiredprivate CustomLogoutHandler customLogoutHandler;@Autowiredprivate CustomAuthenticationFailureHandler customAuthenticationFailureHandler;@Autowiredprivate CustomAuthenticationEntryPoint customAuthenticationEntryPoint;@Overrideprotected void configure(HttpSecurity http) throws Exception {http
//      .requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests()
//      .anyRequest().hasRole("ENDPOINT_ADMIN")
//      .and().authorizeRequests().antMatchers("/api/public/**").permitAll().and().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").and().authorizeRequests().antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')").and().authorizeRequests().anyRequest().authenticated().and().formLogin().defaultSuccessUrl("/newweb/templates/admin_grid.html", true)
//          .loginPage("/login").failureHandler(customAuthenticationFailureHandler).and().logout()
//            .logoutUrl("/logout")
//                .logoutSuccessUrl("/newweb/templates/admin_login.html")
//            .logoutSuccessHandler(new CustomLogoutSuccessHandler()).invalidateHttpSession(true).addLogoutHandler(customLogoutHandler).deleteCookies(SecurityConstants.SECURITY_TOKEN_KEY).and();// 不创建sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 自定义安全上下文仓库,覆盖默认的httpsession实现http.securityContext().securityContextRepository(memoryCacheSecurityContextRepository);// 认证成功之后,不进行http session相关处理http.sessionManagement().sessionAuthenticationStrategy(customAuthenticatedSessionStrategy);http.csrf().disable();//http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);}protected void configure(AuthenticationManagerBuilder auth) throws Exception {super.configure(auth);}
}

2.登陆控制器,做一些验证码处理和账号密码验证等操作

@RestController
@RequestMapping("/api/oauth")
public class LoginController {@Autowiredprivate UserService userService;@Autowiredprivate TokenEndpoint tokenEndpoint;@Autowiredprivate LoginService loginService;@Autowiredprivate UserProvider userProvider;@Autowiredprivate ConfigValueUtil configValueUtil;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate ExpertInfoService expertInfoService;@Autowiredprivate RoleService roleService;@ApiOperation("登陆(切换登录模式需请清空loginForm中的值)")@PostMapping("/Login")public ActionResult<LoginVO> login(Principal principal, @RequestParam Map<String, String> parameters, @RequestBody LoginForm loginForm) throws LoginException {TenantContextHolder.clear();UserInfo userInfo = new UserInfo();String phone = loginForm.getPhone();String phoneCode = loginForm.getPhoneCode();String timestampkey = loginForm.getTimestamp();if(StringUtil.isNotEmpty(phone)){List<UserEntity> userEntityList = userService.list(new QueryWrapper<UserEntity>().lambda().eq(UserEntity::getMobilePhone,phone));if(CollectionUtils.isNotEmpty(userEntityList)){String phoneCode1 = String.valueOf(redisUtil.getString(phone));if("null".equals(phoneCode1)){throw new LoginException("验证码已过期!");}if(!(phoneCode1.equals(phoneCode))){throw new LoginException("验证码输入错误!");}if(StringUtil.isNotEmpty(loginForm.getAccount())){userEntityList = userEntityList.stream().filter(t->loginForm.getAccount().equals(t.getAccount())).collect(Collectors.toList());}if(userEntityList.size() > 1){List<UserLoginForm> userLoginFormList = JsonUtil.getJsonToList(userEntityList,UserLoginForm.class);LoginVO loginVO = new LoginVO();loginVO.setUserLogFormList(userLoginFormList);return ActionResult.success(loginVO);}UserEntity userEntity = userEntityList.get(0);loginForm.setAccount(userEntity.getAccount());loginForm.setPassword(userEntity.getPassword());redisUtil.remove(phone);}}else{String code = loginForm.getCode();String timestamp = String.valueOf(redisUtil.getString(timestampkey));if("null".equals(timestamp)){throw new LoginException("验证码已过期!");}if(!(code).equalsIgnoreCase(timestamp)){throw new LoginException("验证码错误!");}}loginService.isExistUser(loginForm.getAccount().trim(), loginForm.getPassword().trim());List<UserEntity> userEntityList = userService.getUserEntitys(StringUtil.isNotEmpty(loginForm.getPhonePassword())?loginForm.getPhonePassword():loginForm.getAccount());UserEntity entity = new UserEntity();if(userEntityList.size() > 1){for (UserEntity item : userEntityList) {if(item.getPassword().equals(Md5Util.getStringMd5(loginForm.getPassword() + item.getSecretkey().toLowerCase()))){if(StringUtil.isNotEmpty(loginForm.getPhonePassword())){entity = userEntityList.stream().filter(t->loginForm.getAccount().equals(t.getAccount())).collect(Collectors.toList()).get(0);loginForm.setAccount(entity.getAccount());loginForm.setPassword(entity.getPassword());}else{List<UserLoginForm> userLoginFormList = JsonUtil.getJsonToList(userEntityList,UserLoginForm.class);LoginVO loginVO = new LoginVO();loginVO.setUserLogFormList(userLoginFormList);return ActionResult.success(loginVO);}}}if(StringUtil.isEmpty(loginForm.getPhonePassword())){throw new LoginException("账号密码错误");}}if(StringUtil.isEmpty(loginForm.getPhonePassword())){entity = userEntityList.get(0);}userInfo = loginService.userInfo(userInfo, entity);//        if(StringUtil.isNotEmpty(loginForm.getRoleId())){
//            String[] roles = new String[1];
//            roles[0] = loginForm.getRoleId();
//            userInfo.setRoleIds(roles);
//        }
//
//        List<RoleLoginVo> roleLoginVoList = new ArrayList<>();
//
//        if(ArrayUtils.isNotEmpty(userInfo.getRoleIds())){
//            if(userInfo.getRoleIds().length > 1){
//                for (String roleId : userInfo.getRoleIds()) {
//                    RoleLoginVo roleLoginVo = JsonUtil.getJsonToBean(roleService.getById(roleId),RoleLoginVo.class);
//                    roleLoginVoList.add(roleLoginVo);
//                }
//            }
//        }
//        if(CollectionUtil.isNotEmpty(roleLoginVoList)){
//            LoginVO loginVO = new LoginVO();
//            loginVO.setRoleList(roleLoginVoList);
//            return ActionResult.success(loginVO);
//        }userInfo.setMybatisTenantId(entity.getTenantId());ExpertInfoEntity expertInfoEntity = expertInfoService.getOne(new QueryWrapper<ExpertInfoEntity>().lambda().eq(ExpertInfoEntity::getUserId,entity.getId()));if(null != expertInfoEntity){userInfo.setExpertId(expertInfoEntity.getId());}//写入会话userProvider.add(userInfo);//验证账号密码Map<String, String> map = new HashMap<>(16);map.put("account",loginForm.getAccount());map.put("password",loginForm.getPassword());map.putAll(parameters);map.put("username", loginForm.getAccount());OAuth2AccessToken oAuth2AccessToken;try {oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, map).getBody();} catch (HttpRequestMethodNotSupportedException e) {throw new LoginException("账号密码错误");}TenantContextHolder.setTenant(entity.getTenantId());//登陆日志记录在JwtTokenEnhancer类中//获取主题LoginVO loginVO = new LoginVO();loginVO.setToken(oAuth2AccessToken.getTokenType() + " " + oAuth2AccessToken.getValue());loginVO.setTheme(entity.getTheme() == null ? "classic" : entity.getTheme());return ActionResult.success(loginVO);}}

3.前端调用登陆接口,返回token

SpringSecurity实现登陆认证并返回token相关推荐

  1. 若依管理系统——前后端分离版(二)登陆接口分析及SpringSecurity的登陆认证流程

    一.登陆流程分析 0. 流程整理 1. 图片验证码接口/captchaImage 2.登陆验证接口/login 2.1 校验图片验证码 2.1 查询用户信息 2.3查询用户的权限信息 2.4 生成令牌 ...

  2. .anonymous springsecurity需要登陆嘛_springSecurity之java配置篇

    一 前言 本篇是springSecurity知识的入门第二篇,主要内容是如何使用java配置的方式进行配置springSeciruty,然后通过一个简单的示例自定义登陆页面,覆盖原有springSec ...

  3. SSM整合Shiro进行登陆认证和授权详细配置

    本篇博客将进行详细介绍Shiro+Spring+SpringMVC+Mybatis+数据库整合并进行登陆认证和授权详细配置. SSM的整合可以参考:https://blog.csdn.net/a745 ...

  4. 开始使用Lumen吧,3分钟搞定登陆认证

    用户注册 我们在 Controller/Controller.php 添加 succeed 和 faied 公用接口数据返回方法 通过 status_code 来区分失败和成功 namespace A ...

  5. 【SpringSecurity系列02】SpringSecurity 表单认证逻辑源码解读

    概要 前面一节,通过简单配置即可实现SpringSecurity表单认证功能,而今天这一节将通过阅读源码的形式来学习SpringSecurity是如何实现这些功能, 前方高能预警,本篇分析源码篇幅较长 ...

  6. 用户的登陆认证、DjangoRestFramework JWT多条件登录,导航栏实现

    用户的登陆认证.DjangoRestFramework JWT&多条件登录 Django REST framework JWT JWT介绍 JWT的构成 生成规则: header payloa ...

  7. SpringSecurity OAuth2.0认证授权-part1

    在学习此之前回顾一下SpringSecurity认证授权原理. 此篇文章包含springsecurity认证授权回顾及分布式系统认证方案: 篇幅过长,拆分为: part1: 认证授权原理回顾及分布式系 ...

  8. 魔方APP项目-06-用户注册,完成短信验证码的校验、基于Celery实现短信异步发送、用户登录,jwt登陆认证、服务端提供用户登录的API接口

    一.用户注册- 1.完成短信验证码的校验 application.utils.language.message,代码: class ErrorMessage():ok = "ok" ...

  9. Springsecurity使用浏览器登录失败返回302(跨域问题),前后端分离

    问题: 在使用Springsecurity时使用postman访问登录失败能得到自定义错误信息401,但是使用浏览器访问的时候,当登录成功时能正常进入自定义配置类,但是失败是直接给浏览器返回一个状态码 ...

  10. SpringSecurity的安全认证的详解说明(附完整代码)

    SpringSecurity登录认证和请求过滤器以及安全配置详解说明 环境 系统环境:win10 Maven环境:apache-maven-3.8.6 JDK版本:1.8 SpringBoot版本:2 ...

最新文章

  1. 破解Win2008口令-ERD6.0
  2. 一次搞定OpenCV源码及扩展模块的编译与环境配置
  3. dbcontext mysql_.Net Core 2.0数据库第一种方法Mysql DB的Scaffold-DbContext
  4. 你们计算机专业的学生应该看看这篇文章
  5. python括号的区别_Python中类-带括号与不带括号的区别
  6. 【渝粤教育】国家开放大学2018年春季 0014-22T秘书学(一) 参考试题
  7. 地质灾害防治条例释义的摘要
  8. eclipse启动出现“An Error has Occurred. See the log file”解决方法
  9. 学python最重要的是_Python学习,要选哪个版本?
  10. 【2018百度之星资格赛】1002 子串查询
  11. CentOS7配置ssh证书登录无效
  12. Win10系统更新后共享打印机无法打印
  13. UE 编辑器无法保存文件(图表被连接到外部包中的私有对象)
  14. Redis与数据库的数据一致性
  15. git官网下载比较慢的解决方法
  16. C++-求积分(面积)
  17. 超图软件裁剪倾斜数据
  18. 19-Python基础知识学习-----迭代器与生成器
  19. office2016安装失败,或者需要卸载,如何彻底卸载
  20. 1分钟学会PS背景虚化

热门文章

  1. python登录系统账号检测_使用Python脚本检测邮件账户密码是否被泄漏,提高你的账户安全性...
  2. 证书错误 SSLCertVerificationError
  3. C# Word 文档保护
  4. Windows常用注册表文件-修改右键菜单
  5. 简单处理点击EditText外部区域关闭软键盘
  6. 【JS ES6】use strict 严格模式
  7. 使用豆瓣源下载python包
  8. 苹果账号申请流程——99刀(个人版或公司版 ),299刀(企业版)
  9. AtCoder Beginner Contest 156 D Bouquet 失之交臂 容斥原理+二项式定理+乘法逆元+快速幂+模后负数 多余担心
  10. 解决SecureCRT连接网络设备console口失败的问题