</span><el-inputv-model="loginForm.validateCode"auto-complete="off"placeholder="请输入验证码"@keyup.enter.native="handleLogin"/><div><img:src="captchaUrl"@click="getValidateCode"height="38px"/></div></el-form-item>
返回的图片是base64加密后的字符串,所以添加前缀data:image/gif;base64,
复制代码12345678JAVASCRIPT//获取验证码getValidateCode(){getCaptcha().then(response=>{const{img,uuid}=response.datathis.captchaUrl="data:image/gif;base64,"+imgthis.loginForm.uuid=uuid;})}
src/store/modules/user.js设置请求参数
复制代码1234567891011121314151617181920JAVASCRIPTlogin({commit},userInfo){const{username,password,validateCode,uuid}=userInforeturnnewPromise((resolve,reject)=>{login({username:username,password:password,grant_type:'captcha',//授权模式指定为captcha验证码模式,原先为password密码模式uuid:uuid,//从Redis获取正确验证码的标识validateCode:validateCode//验证码}).then(response=>{const{access_token,refresh_token,token_type}=response.dataconsttoken=token_type+""+access_tokencommit('SET_TOKEN',token)setToken(token)setRefreshToken(refresh_token)resolve()}).catch(error=>{reject(error)})})
src/api/user.js请求API设置请求头部
复制代码12345678910JAVASCRIPTexportfunctionlogin(params){returnrequest({url:'/youlai-auth/oauth/token',method:'post',params:params,headers:{'Authorization':'BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2'//OAuth2客户端信息Base64加密,明文:mall-admin-web:123456}})}
手机短信验证码模式时序图如下,变动的角色还是用绿色背景标识。可以看到扩展是对授权者Granter和认证提供者Provider做切入口。
手机短信验证码授权流程:流程基本上和密码模式一致,根据grant_type匹配授权者SmsCodeTokenGranter,委托给ProviderManager进行认证,根据SmsCodeAuthenticationToken的匹配认证提供者SmsCodeAuthenticationProvider进行短信验证码校验。
复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556JAVA/***手机验证码授权者**@author<ahref="mailto:TokenRequest)*@seeorg.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String,TokenRequest)*/privatestaticfinalStringGRANT_TYPE="sms_code";privatefinalAuthenticationManagerauthenticationManager;publicSmsCodeTokenGranter(AuthorizationServerTokenServicestokenServices,ClientDetailsServiceclientDetailsService,OAuth2RequestFactoryrequestFactory,AuthenticationManagerauthenticationManager){super(tokenServices,clientDetailsService,requestFactory,GRANT_TYPE);this.authenticationManager=authenticationManager;}@OverrideprotectedOAuth2AuthenticationgetOAuth2Authentication(ClientDetailsclient,TokenRequesttokenRequest){Map<String,String>parameters=newLinkedHashMap(tokenRequest.getRequestParameters());Stringmobile=parameters.get("mobile");//手机号Stringcode=parameters.get("code");//短信验证码parameters.remove("code");AuthenticationuserAuth=newSmsCodeAuthenticationToken(mobile,code);((AbstractAuthenticationToken)userAuth).setDetails(parameters);try{userAuth=this.authenticationManager.authenticate(userAuth);}catch(AccountStatusExceptionvar8){thrownewInvalidGrantException(var8.getMessage());}catch(BadCredentialsExceptionvar9){thrownewInvalidGrantException(var9.getMessage());}if(userAuth!=null&&userAuth.isAuthenticated()){OAuth2RequeststoredOAuth2Request=this.getRequestFactory().createOAuth2Request(client,tokenRequest);returnnewOAuth2Authentication(storedOAuth2Request,userAuth);}else{thrownewInvalidGrantException("Couldnotauthenticateuser:"+mobile);}}}
复制代码123456789101112131415161718192021222324252627282930313233343536373839JAVA/***短信验证码认证授权提供者**@author<ahref="mailto:xianrui0365@163.com">xianrui</a>*@date2021/9/25*/@DatapublicclassSmsCodeAuthenticationProviderimplementsAuthenticationProvider{privateUserDetailsServiceuserDetailsService;privateMemberFeignClientmemberFeignClient;privateStringRedisTemplateredisTemplate;@OverridepublicAuthenticationauthenticate(Authenticationauthentication)throwsAuthenticationException{SmsCodeAuthenticationTokenauthenticationToken=(SmsCodeAuthenticationToken)authentication;Stringmobile=(String)authenticationToken.getPrincipal();Stringcode=(String)authenticationToken.getCredentials();StringcodeKey=AuthConstants.SMS_CODE_PREFIX+mobile;StringcorrectCode=redisTemplate.opsForValue().get(codeKey);//验证码比对if(StrUtil.isBlank(correctCode)||!code.equals(correctCode)){thrownewBizException("验证码不正确");}else{redisTemplate.delete(codeKey);}UserDetailsuserDetails=((MemberUserDetailsServiceImpl)userDetailsService).loadUserByMobile(mobile);WechatAuthenticationTokenresult=newWechatAuthenticationToken(userDetails,newHashSet<>());result.setDetails(authentication.getDetails());returnresult;}@Overridepublicbooleansupports(Class<?>authentication){returnSmsCodeAuthenticationToken.class.isAssignableFrom(authentication);}}
在认证中心配置把SmsCodeTokenGranter添加到认证器的授权类型的集合中去。
访问?spm=5176.10695662.1128094.7.2a6b4bee30xtJx申请阿里云免费短信试用
添加签名,等待审核通过
签名审核通过之后就可以创建AccessKey访问密钥
添加模板,国内消息→模板管理→添加模板
签名审核通过后得到AccessKey和模板审核通过得到模板CODE,
SpringBoot整合SMS网上教程很多,这里不画蛇添足,接下来简单说下youlai-mall整合阿里云SMS短信。完整源码
按惯例把短信封装成一个公共模块以便给其他需要短信的应用模块引用。
youlai-auth引入common-sms依赖
复制代码123456XML<dependencies><dependency><groupId>com.youlai</groupId><artifactId>common-sms</artifactId></dependency></dependencies>
其中AliyunSmsProperties需要的属性需要配置在Nacos的配置中心文件youlai-auth.yaml
复制代码123456789YAML#阿里云短信配置aliyun:sms:accessKeyId:LTAI5tSxxxxxxNcD6diBJLyRaccessKeySecret:SoOWRqpjtSxxxxxxM8QZ2PZiMTJOVCdomain:dysmsapi.aliyuncs.comregionId:cn-shanghaitemplateCode:SMS_225xxx770signName:有来技术
发送短信验证码接口
复制代码12345678910111213141516JAVA@Api(tags="短信验证码")@RestController@RequestMapping("/sms-code")@RequiredArgsConstructorpublicclassSmsCodeController{privatefinalAliyunSmsServicealiyunSmsService;@ApiOperation(value="发送短信验证码")@ApiImplicitParam(name="phoneNumber",example="17621590365",value="手机号",required=true)@PostMappingpublicResultsendSmsCode(StringphoneNumber){booleanresult=aliyunSmsService.sendSmsCode(phoneNumber);returnResult.judge(result);}}
有来移动端mall-app使用uni-app跨平台应用的前端框架。所以uni-app的强大之处没法体现。借着这次给mall-app扩展手机短信验证码的授权模式的机会,为H5、Android和IOS添加手机短信验证码的登录界面。
H5/Android/IOS登录界面 | |
登录页面/pages/login/login.vue在不同的平台有不同的呈现实现原理是通过#ifdefMP和#ifndefMP条件编译指令实现的,其中#ifdefMP是在小程序平台编译生效,而#ifdefMP是非小程序平台编译生效。
在开发编译时,当在HBuilderX工具栏点击运行选择不同的平台会有不同的页面呈现。
说到接入SpringSecurityOAuth2扩展的手机短信验证码,重要的还是看如何传参。在mall-app的/api/user.js代码:
复制代码1234567891011121314151617JAVASCRIPT//H5/Android/IOS手机短信验证码登录//#ifndefMPexportfunctionlogin(mobile,code){returnrequest({url:'/youlai-auth/oauth/token',method:'post',params:{mobile:mobile,code:code,grant_type:'sms_code'},headers:{'Authorization':'BasicbWFsbC1hcHA6MTIzNDU2'//客户端信息Base64加密,明文:mall-app:123456}})}//#endif
赋予mall-app客户端支持sms_code模式
到此H5/Android/IOS移动端接入SpringSecurityOAuth2扩展的手机短信验证码授权模式已经完成。
我们所扮演的角色是开发者服务器,后续小程序携带token和开发者服务器进行交互,
添加授权者WechatTokenGranter构建WechatAuthenticationToken,匹配到认证提供者WechatAuthenticationProvider,在其authenticate方法完成认证授权逻辑。
encryptedData、iv构建WechatAuthenticationToken
TokenRequest)*@seeorg.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String,TokenRequest)*/privatestaticfinalStringGRANT_TYPE="wechat";privatefinalAuthenticationManagerauthenticationManager;publicWechatTokenGranter(AuthorizationServerTokenServicestokenServices,ClientDetailsServiceclientDetailsService,OAuth2RequestFactoryrequestFactory,AuthenticationManagerauthenticationManager){super(tokenServices,clientDetailsService,requestFactory,GRANT_TYPE);this.authenticationManager=authenticationManager;}@OverrideprotectedOAuth2AuthenticationgetOAuth2Authentication(ClientDetailsclient,TokenRequesttokenRequest){Map<String,String>parameters=newLinkedHashMap(tokenRequest.getRequestParameters());Stringcode=parameters.get("code");StringencryptedData=parameters.get("encryptedData");Stringiv=parameters.get("iv");parameters.remove("code");parameters.remove("encryptedData");parameters.remove("iv");AuthenticationuserAuth=newWechatAuthenticationToken(code,encryptedData,iv);//未认证状态((AbstractAuthenticationToken)userAuth).setDetails(parameters);try{userAuth=this.authenticationManager.authenticate(userAuth);//认证中}catch(Exceptione){thrownewInvalidGrantException(e.getMessage());}if(userAuth!=null&&userAuth.isAuthenticated()){//认证成功OAuth2RequeststoredOAuth2Request=this.getRequestFactory().createOAuth2Request(client,tokenRequest);returnnewOAuth2Authentication(storedOAuth2Request,userAuth);}else{//认证失败thrownewInvalidGrantException("Couldnotauthenticatecode:"+code);}}}
成功返回token。
=null&&ResultCode.USER_NOT_EXIST.getCode().equals(memberAuthResult.getCode())){StringsessionKey=sessionInfo.getSessionKey();StringencryptedData=authenticationToken.getEncryptedData();Stringiv=authenticationToken.getIv();//解密encryptedData获取用户信息WxMaUserInfouserInfo=wxMaService.getUserService().getUserInfo(sessionKey,encryptedData,iv);UmsMembermember=newUmsMember();BeanUtil.copyProperties(userInfo,member);member.setOpenid(openid);member.setStatus(GlobalConstants.STATUS_YES);memberFeignClient.add(member);}UserDetailsuserDetails=((MemberUserDetailsServiceImpl)userDetailsService).loadUserByOpenId(openid);WechatAuthenticationTokenresult=newWechatAuthenticationToken(userDetails,newHashSet<>());result.setDetails(authentication.getDetails());returnresult;}@Overridepublicbooleansupports(Class<?>authentication){returnWechatAuthenticationToken.class.isAssignableFrom(authentication);}}
同样是在mall-app的接口文件中/api/user.js,先让我们看下小程序端如何传值?
复制代码123456789101112131415161718JAVASCRIPT//小程序授权登录//#ifdefMPexportfunctionlogin(code,encryptedData,iv){returnrequest({url:'/youlai-auth/oauth/token',method:'post',params:{code:code,encryptedData:encryptedData,iv:iv,grant_type:'wechat'},headers:{'Authorization':'BasicbWFsbC13ZWFwcDoxMjM0NTY='//客户端信息Base64加密,明文:mall-weapp:123456}})}//#endif
设置OAuth2客户端支持wechat授权模式
实际业务场景常用的3种授权模式也就告一段落。
但是如果你对SpringSecurityOAuth2有些了解的话,你会有疑问这些扩展的模式对应的刷新模式需不需要做什么调整呢?
如果扩展只是针对一种用户体系以及一种认证方式(用户名/手机号/openid)的话,比如验证码模式的扩展,就不需要对刷新模式做调整。
但是如果是多用户体系或者多种认证方式,youlai-mall就是多用户体系以及多种认证方式,这时你必须做些调整来适配,不过改动不大,具体为什么调整和如何调整下文细说。
刷新模式时序图如下,相较于密码模式还只是Granter和Provider的变动。
着重说一下刷新模式的认证提供者PreAuthenticatedAuthenticationProvider,其authenticate()认证方法只做用户状态校验,check()方法调用AccountStatusUserDetailsChecker#check(UserDetails)。
注意下this.preAuthenticatedUserDetailsService.loadUserDetails((PreAuthenticatedAuthenticationToken)authentication);的preAuthenticatedUserDetailsService用户服务。
在没有进行授权模式扩展的时候,是下面这样设置的
然后在AuthorizationServerEndpointsConfigurer#addUserDetailsService(DefaultTokenServices,UserDetailsService)构造PreAuthenticatedAuthenticationProvider里设置了UserDetailService用户服务。
这样在多用户体系认证下问题可想而知,用户分别有系统用户和会员用户,这里固定成一个用户服务肯定是行不通的,扩展授权模式创建Provider时可以指定具体的用户服务UserDetailService,就如下面这样:
你可以为每个授权模式扩展新增对应的刷新模式,但是这样的话比较麻烦,所以这里使用的另一种方案,重新设置PreAuthenticatedAuthenticationProvider的preAuthenticatedUserDetailsService属性,让其有判断选择用户体系和认证方式的能力。
首先我们清楚一个OAuth2客户端基本对应的是一个用户体系,比如youlai-mall项目的客户端和用户体系对应关系如下表:
OAuth2客户端名称 | OAuth2客户端ID | 用户体系 |
管理系统 | mall-admin-web | 系统用户 |
H5/Android/IOS移动端 | mall-app | 商城会员 |
小程序端 | mall-weapp | 商城会员 |
那就有一个很简单有效的思路,可以在系统内部维护一个如上表的映射关系Map,然后根据传递的客户端ID去选择用户体系。
就这?当然不是,还有个点你必须要考虑到,举个例子虽然移动端的用户体系是会员用户,但是它可能有多种认证方式呀,比如可以同时支持手机短信验证码和用户名密码甚至更多的认证方式。
而SpringSecurityOAuth2默认的UserDetailsService接口只有一个loadUserByUsername()方法,很显然是做不到会员体系支持多种认证方式的。
复制代码123JAVApublicinterfaceUserDetailsService{UserDetailsloadUserByUsername(Stringvar1)throwsUsernameNotFoundException;}
所以需要在UserDetailsService的实现类新增认证方式,然后在运行时将UserDetailsService转为具体的实现类,具体可看下有来项目的MemberUserDetailsServiceImpl的实现,同时支持手机号和三方标识openid获取用户认证信息,即两种不同的认证方式。
复制代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273JAVA/***商城会员用户认证服务**@author<ahref="mailto:xianrui0365@163.com">xianrui</a>*/@Service("memberUserDetailsService")@RequiredArgsConstructorpublicclassMemberUserDetailsServiceImplimplementsUserDetailsService{privatefinalMemberFeignClientmemberFeignClient;@OverridepublicUserDetailsloadUserByUsername(Stringusername){returnnull;}/***手机号码认证方式**@parammobile*@return*/publicUserDetailsloadUserByMobile(Stringmobile){MemberUserDetailsuserDetails=null;Result<MemberAuthDTO>result=memberFeignClient.loadUserByMobile(mobile);if(Result.isSuccess(result)){MemberAuthDTOmember=result.getData();if(null!=member){userDetails=newMemberUserDetails(member);userDetails.setAuthenticationMethod(AuthenticationMethodEnum.MOBILE.getValue());//认证方式:OpenId}}if(userDetails==null){thrownewUsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());}elseif(!userDetails.isEnabled()){thrownewDisabledException("该账户已被禁用!");}elseif(!userDetails.isAccountNonLocked()){thrownewLockedException("该账号已被锁定!");}elseif(!userDetails.isAccountNonExpired()){thrownewAccountExpiredException("该账号已过期!");}returnuserDetails;}/***openid认证方式**@paramopenId*@return*/publicUserDetailsloadUserByOpenId(StringopenId){MemberUserDetailsuserDetails=null;Result<MemberAuthDTO>result=memberFeignClient.loadUserByOpenId(openId);if(Result.isSuccess(result)){MemberAuthDTOmember=result.getData();if(null!=member){userDetails=newMemberUserDetails(member);userDetails.setAuthenticationMethod(AuthenticationMethodEnum.OPENID.getValue());//认证方式:OpenId}}if(userDetails==null){thrownewUsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());}elseif(!userDetails.isEnabled()){thrownewDisabledException("该账户已被禁用!");}elseif(!userDetails.isAccountNonLocked()){thrownewLockedException("该账号已被锁定!");}elseif(!userDetails.isAccountNonExpired()){thrownewAccountExpiredException("该账号已过期!");}returnuserDetails;}}
新增的PreAuthenticatedUserDetailsService可根据客户端和认证方式选择UserDetailService和方法获取用户信息UserDetail
复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768JAVA/***刷新token再次认证UserDetailsService**@author<ahref="mailto:xianrui0365@163.com">xianrui</a>*@date2021/10/2*/@NoArgsConstructorpublicclassPreAuthenticatedUserDetailsService<TextendsAuthentication>implementsAuthenticationUserDetailsService<T>,InitializingBean{/***客户端ID和用户服务UserDetailService的映射**@seecom.youlai.auth.security.config.AuthorizationServerConfig#tokenServices(AuthorizationServerEndpointsConfigurer)*/privateMap<String,UserDetailsService>userDetailsServiceMap;publicPreAuthenticatedUserDetailsService(Map<String,UserDetailsService>userDetailsServiceMap){Assert.notNull(userDetailsServiceMap,"userDetailsServicecannotbenull.");this.userDetailsServiceMap=userDetailsServiceMap;}@OverridepublicvoidafterPropertiesSet()throwsException{Assert.notNull(this.userDetailsServiceMap,"UserDetailsServicemustbeset");}/***重写PreAuthenticatedAuthenticationProvider的preAuthenticatedUserDetailsService属性,可根据客户端和认证方式选择用户服务UserDetailService获取用户信息UserDetail**@paramauthentication*@return*@throwsUsernameNotFoundException*/@OverridepublicUserDetailsloadUserDetails(Tauthentication)throwsUsernameNotFoundException{StringclientId=RequestUtils.getOAuth2ClientId();//获取认证方式,默认是用户名usernameAuthenticationMethodEnumauthenticationMethodEnum=AuthenticationMethodEnum.getByValue(RequestUtils.getAuthenticationMethod());UserDetailsServiceuserDetailsService=userDetailsServiceMap.get(clientId);if(clientId.equals(SecurityConstants.APP_CLIENT_ID)){//移动端的用户体系是会员,认证方式是通过手机号mobile认证MemberUserDetailsServiceImplmemberUserDetailsService=(MemberUserDetailsServiceImpl)userDetailsService;switch(authenticationMethodEnum){caseMOBILE:returnmemberUserDetailsService.loadUserByMobile(authentication.getName());default:returnmemberUserDetailsService.loadUserByUsername(authentication.getName());}}elseif(clientId.equals(SecurityConstants.WEAPP_CLIENT_ID)){//小程序的用户体系是会员,returnmemberUserDetailsService.loadUserByOpenId(authentication.getName());default:returnmemberUserDetailsService.loadUserByUsername(authentication.getName());}}elseif(clientId.equals(SecurityConstants.ADMIN_CLIENT_ID)){//管理系统的用户体系是系统用户,认证方式通过用户名username认证switch(authenticationMethodEnum){default:returnuserDetailsService.loadUserByUsername(authentication.getName());}}else{returnuserDetailsService.loadUserByUsername(authentication.getName());}}}
AuthorizationServerConfig配置重新设置PreAuthenticatedAuthenticationProvider的preAuthenticatedUserDetailsService属性值
核心代码基本都在上面,在完成以上的调整之后刷新模式就可以了,接下来对新扩展的授权模式对应的刷新模式进行逐一测试。 下面所有的测试都会把cURL贴出来,至于为什么强调这个?原来以为我把用Postman测试SpringSecurityOAuth2获取token的完整请求截图放入项目说明文档README.md这样就不会再有人问登录接口403报错,但事实反馈确实自己挺失望,以致于后来再有这样的问题基本上选择沉默了,希望大家换位思考理解下。所以这次想到的方案是把接口信息以cURL的形式贴出来,然后直接导入Postman测试。 下面是有来项目获取token的cURL 进入Postman选择File→Import→Rawtext把上面的cURL导入 密码模式的测试使用的客户端信息,客户端ID:客户端密钥:mall-admin-web:123456-----Base64在线编码→bWFsbC1hZG1pbi13ZWI6MTIzNDU2 如果你要更改客户端,请在下方接口的请求头Authorization更换客户端信息即可,不然会报403提示,因为你的客户端信息不正确认证不成功禁止访问。 有些人会问现在有来项目没有自定义客户端认证异常的处理,其实在我之前的文章有提供解决方案客户端认证异常,有需要的可以根据文章调整。至于为什么项目中没有使用方案,首先觉得实现比较复杂,如果你有好的解决方案欢迎提出,另外这种客户端信息错误作为一个开发人员来说你是完全可以规避的。 refresh_token需要替换,在第一步获取token返回的refresh_token 验证码模式的测试使用客户端的信息,客户端ID:客户端密钥:mall-admin-web:123456-----Base64在线编码→bWFsbC1hZG1pbi13ZWI6MTIzNDU2 手机短信验证码模式测试使用的客户端的信息,客户端ID:客户端密钥:mall-app:123456-----Base64在线编码→bWFsbC1hZG1pbi13ZWI6MTIzNDU2 客户端ID:客户端密钥:mall-weapp:123456-----Base64在线编码→bWFsbC13ZWFwcDoxMjM0NTY= 本篇基于SpringSecurityOAuth2扩展了实际开发常用的验证码模式、同时稍调整刷新模式使其能够适配扩展的几种模式以及多用户体系。通过授权模式的扩展揭露SpringSecurityOAuth2的认证流程和底层原理,相信对流程和原理有个清晰的思路之后,不同的认证需求都可以做到得心应手。最后还是感叹下Spring框架的魅力,就是你能感受到它在功能的实现的基础上会给你留个扩展的入口,而不是让你想着去改它的源码去实现。最后希望大家都能收获些东西吧,虽然咱这也不图啥,写这些说实话对自己提升也不大,算是自己的一份心血,也不希望白费了。 -进入鱼眼拍摄模式:在菜单设置中选择鱼眼拍摄模式,开启鱼眼模式。 佳能EF8-15mmf/4LUSM鱼眼镜头在不同焦距时的圆形鱼眼效果 佳能EF8-15mmf/4LUSM鱼眼镜头在不同焦距时的对角线鱼眼效果 该镜头虽然已停产,但在各类电商平台上仍有全新镜头在售,售价约为3700元,性价比略优。 佳能800D价格大概在4500块左右;77D价格大概在8000左右,80D10000左右,5D等或者更高级都可以。为什么说佳能,因为我用的是佳能,用的还顺手,其他的品牌没用过,不好评价。 这款16mm手动对焦鱼眼镜头的体积比上述的适马镜头更加小巧,有尼康、佳能与M42mm螺口卡口。如果购买时再多加150元,还能增添带对焦提示音电路的款式。该鱼眼镜头在成像素质方面具有色彩浓厚、立体感强的特点,在锐度、解像力、反差、色彩还原方面不输佳能或尼康原厂镜头。全套购置的花费仅为1600元,性价比极高。 1.镜头的安装 在安装鱼眼镜头时,一定要注意镜头的定位。鱼眼镜头是一个弯曲的圆柱形,因此必须正确地安装在相机上,以确保图像呈现出正确的曲线。 2.镜头的曲率 鱼眼镜头有着非常大的曲率,这意味着镜头的曲率半径越小,所捕捉到的场景视角越大。佳能相机鱼眼镜头通常拥有曲率半径为10mm到20mm的范围。 3.镜头的对焦 由于鱼眼镜头具有非常大的视角,图像通常都是非常宽广的,因此焦距也非常重要。在使用佳能相机鱼眼镜头时,焦距非常短,所以您需要非常小心地对焦,确保您的主题位于光圈的中心位置。 4.镜头的光圈 镜头的光圈大小对图像的亮度和景深都有很大的影响。在使用佳能相机鱼眼镜头时,鱼眼镜头的光圈通常很小,为f/4到f/5.6之间。这意味着您可能需要使用较长的曝光时间来获取正确的曝光,或者开启相机的高ISO设置。 相机和镜头都是有唯一编号的。相机出厂时是不交关税的。只有进口到大陆才交 水货有序列号只是三码不能合一。香港是是有免税的商场,但是大陆没有,你带回来也不会给你质保的,除非你买的是行货! 你说的这个绝对是山寨我朋友被骗过,客服说的和你这里说的一样,典型的山寨机就是一个佳能的智能小单反1100 相机和镜头都是有唯一编号的。相机出厂时是不交关税的。只有进口到大陆才交。我买的行货机上面直接有查询真伪的查询码,水货感觉序列号应该也可以吧,怎么说也是香港出售的 如果你是在淘宝买的相机的话,很有可能买到翻新的相机! 行货水货都是正品。查不着就是水货。水货不是假的,是不保的。 佳能镜头三码是指佳能公司制造的每个镜头都有三个辨识度高且唯一的编码: 1.序列号:唯一且不会重复的数字编码,每个镜头都有自己独特的序列号。 2.生产日期:年月日的编码,记录了该镜头的生产日期。 3.镜头型号:也就是镜头的名称和型号,比如EF100mmf/2.8LMacroISUSM。 伪造佳能镜头需要同时伪造三个编码,因此我们需要确认这三个编码是否合一: 1.序列号:每款佳能镜头都带有一个唯一的序列号,用户可通过佳能官网或其他认证渠道查询序列号是否真实,来确保镜头的真伪。 2.生产日期:佳能镜头的生产日期在序列号中留下痕迹,我们可以查询佳能官网中该型号镜头的生产版本以及生产批次,来确定生产日期的真实性。 3.镜头型号:每款佳能镜头都有独特的型号,我们可以通过佳能官网或其他认证渠道查询该型号的详细参数和特点,来核实镜头型号是否匹配。 了解80D的镜头锁位置是非常重要的,它能够确保你的镜头牢固地与相机连接在一起,避免在拍摄过程中产生意外的摇晃或者意外脱落。80D的镜头锁在哪儿呢? 我们需要先寻找到80D镜头锁的位置。一般而言,80D的镜头锁位于镜头底部。这个锁是一个小的开关,可以在镜头上轻松找到。当你找到它之后,你需要将它向上或向下移动,来开启或关闭镜头锁。一旦锁定,镜头就会安全地固定在相机上,确保稳定和安全的拍摄。 镜头锁的目的主要是为了确保镜头在拍摄过程中保持稳定,防止意外移动或脱落。这对于拍摄高质量的照片和视频非常重要,特别是在使用较长焦距或重型镜头时。 镜头为什么转不动啊?确实是推拉的,但推不动也拉不动,纹丝不动。锁在哪里啊? 1.无光学低通滤镜2416万有效像素DX格式CMOS传感器 D5300搭载2416万有效像素DX格式CMOS传感器。提供从ISO100到ISO12800的标准感光度范围,上限比D5200更高。此外该传感器采用无低通滤镜设计可充分发挥相机高像素的优势。 那么这样看来D5300就是采用D7100的传感器?先不谈新闻稿中“尼康独自开发”是否可靠,就传感器的外貌,D5300与D7100、D5200、D3200均不相同。我们可在官方资料中找到传感器的图片,将几种传感器放在一起就会发现他们封装风格并不相同。 四块CMOS对比 chipworks拆解过D3200、D5200,前者为尼康设计瑞萨制造,后者则是东芝生产的。D7100的传感器可以视为D5200的无低通版。那么问题来了,D5300的传感器出自哪里?目前还没有拆解证实这一点,对于普通消费者来说不必过分纠结于此。 D5300支持14bit/12bit。D5200仅支持14bit 2.EXPEED4图像处理器 尼康D5300是首款搭载EXPEED4图像处理器的尼康数码相机,具备更快速的处理能力,优化数码单反相机性能,提供更优异的降噪效果、自动白平衡、色彩还原以及色调处理。复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071JAVA/***配置授权以及令牌的访问端点和令牌服务(tokenservices)*/@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints){//Token增强TokenEnhancerChaintokenEnhancerChain=newTokenEnhancerChain();List<TokenEnhancer>tokenEnhancers=newArrayList<>();tokenEnhancers.add(tokenEnhancer());tokenEnhancers.add(jwtAccessTokenConverter());tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);//获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者List<TokenGranter>granterList=newArrayList<>(Arrays.asList(endpoints.getTokenGranter()));//添加验证码授权模式授权者granterList.add(newCaptchaTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory(),authenticationManager,stringRedisTemplate));//添加手机短信验证码授权模式的授权者granterList.add(newSmsCodeTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory(),authenticationManager));CompositeTokenGrantercompositeTokenGranter=newCompositeTokenGranter(granterList);endpoints.authenticationManager(authenticationManager).accessTokenConverter(jwtAccessTokenConverter()).tokenEnhancer(tokenEnhancerChain).tokenGranter(compositeTokenGranter)/**refreshtoken有两种使用方式:重复使用(true)、非重复使用(false),默认为true*1重复使用:accesstoken过期刷新时,refreshtoken过期时间未改变,仍以初次生成的时间为准*2非重复使用:accesstoken过期刷新时,refreshtoken过期时间延续,在refreshtoken有效期内刷新便永不失效达到无需再次登录的目的*/.reuseRefreshTokens(true).tokenServices(tokenServices(endpoints));}publicDefaultTokenServicestokenServices(AuthorizationServerEndpointsConfigurerendpoints){TokenEnhancerChaintokenEnhancerChain=newTokenEnhancerChain();List<TokenEnhancer>tokenEnhancers=newArrayList<>();tokenEnhancers.add(tokenEnhancer());tokenEnhancers.add(jwtAccessTokenConverter());tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);DefaultTokenServicestokenServices=newDefaultTokenServices();tokenServices.setTokenStore(endpoints.getTokenStore());tokenServices.setSupportRefreshToken(true);tokenServices.setClientDetailsService(clientDetailsService);tokenServices.setTokenEnhancer(tokenEnhancerChain);//多用户体系下,刷新token再次认证客户端ID和UserDetailService的映射MapMap<String,UserDetailsService>clientUserDetailsServiceMap=newHashMap<>();clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID,sysUserDetailsService);//管理系统客户端clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID,memberUserDetailsService);//Android/IOS/H5移动客户端clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID,
3.测试
3.1Postman导入cURL操作说明
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?username=admin&password=123456&grant_type=password'\--header'Authorization:BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2'
3.2密码模式测试
获取token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?username=admin&password=123456&grant_type=password'\--header'Authorization:BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2'
刷新token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2'
3.3验证码模式测试
获取token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?username=admin&password=123456&grant_type=captcha&uuid=11add22b38e74a57bade0bf628a70645&validateCode=1'\--header'Authorization:BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2
刷新token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?BasicbWFsbC1hZG1pbi13ZWI6MTIzNDU2'
3.4手机短信验证码测试
获取token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?mobile=17621590365&code=666666&grant_type=sms_code'\--header'Authorization:BasicbWFsbC1hcHA6MTIzNDU2'
刷新token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?BasicbWFsbC1hcHA6MTIzNDU2'
获取token
复制代码12SHELLcurl--location--requestPOST':9999/youlai-auth/oauth/token?code=063hEOFa1N1dWB0XpRIa1WvNw74hEOF-&encryptedData=1qmFeCKbTxZyCdzctu37sX+jOnM9dZG9lKyD3v6FhA5sCEtDwaF/wqyVR70QVrqt7bGVH+Kb+PBsFJlBXUdjnFGlrwmPqgNusI4f5eA8SvZgopvmlzJhXwe+OjLCQooeGnSkcnUrUuMA/G4ZYWFeljaHhxJq/75APWs4HyLANfbeLp50qI9xrRJVUXlTqdqJ0ub38ZxWVvWZMqY8FaskAiZpxzrF30eXu93BCpDavRCVzlSfv6LFJmmvEGVOKr4Wap9ND82N3sDMyArRsdhdhmoWIYBbRs+iLbKcS4WyOhpmaQr4fhhOuxO+zSAa7W+eNmCH2Id6Pgpvhl6ureNNzEb0cQLoksP6oakPmv/yEiw5fnW6Oi9jJbxzlMyORN3/atHgBl6zLIgS9UMhFE+42Vp5B3L8jLly4+B4NpNgol+khXoh+ycUXSRPV4bUuriv&iv=j+brWSrqRW+d4lAjRWW4RA==&grant_type=wechat'\--header'Authorization:BasicbWFsbC13ZWFwcDoxMjM0NTY='
刷新token
六.总结
佳能镜头鱼眼设置在哪里
佳能镜头三码不合
什么是佳能镜头三码?
那如何验证佳能镜头的三码合一呢?
尼康d5300镜头锁在哪里
技术亮点