From ec40473835f3c67c859b756ae8c47dd6b1075c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=9D=E5=85=88=E7=91=9E?= <1490493387@qq.com> Date: Thu, 29 Jun 2023 20:04:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9ESAS=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E3=80=81=E9=AA=8C=E8=AF=81=E7=A0=81=E3=80=81=E7=9F=AD=E4=BF=A1?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=E5=92=8C=E5=BE=AE=E4=BF=A1=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=8E=88=E6=9D=83=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/nacos/nacos_config.zip | Bin 10368 -> 9770 bytes .../src/main/resources/bootstrap-prod.yml | 4 +- .../src/main/resources/bootstrap-prod.yml | 4 +- .../src/main/resources/bootstrap-prod.yml | 4 +- .../src/main/resources/bootstrap-prod.yml | 4 +- pom.xml | 20 +- youlai-auth/pom.xml | 10 +- .../CaptchaAuthenticationConverter.java | 115 ++++++++++ .../CaptchaAuthenticationProvider.java | 216 ++++++++++++++++++ .../captcha/CaptchaAuthenticationToken.java | 62 +++++ .../captcha/CaptchaParameterNames.java | 26 +++ ...a => PasswordAuthenticationConverter.java} | 8 +- ...va => PasswordAuthenticationProvider.java} | 16 +- ....java => PasswordAuthenticationToken.java} | 10 +- .../SmsCodeAuthenticationConverter.java | 8 +- .../SmsCodeAuthenticationProvider.java | 18 +- .../smscode/SmsCodeAuthenticationToken.java | 2 +- .../smscode/SmsCodeParameterNames.java | 41 ++++ .../WxMiniAppAuthenticationProvider.java | 12 +- .../config/AuthorizationServerConfig.java | 151 +++++++++--- .../youlai/auth/config/SecurityConfig.java | 2 + ...berUserDetails.java => MemberDetails.java} | 6 +- ...Service.java => MemberDetailsService.java} | 37 ++- .../member/MobileUserDetailsService.java | 61 ----- .../auth/userdetails/user/SysUserDetails.java | 4 +- .../user/SysUserDetailsService.java | 1 - .../src/main/resources/bootstrap-dev.yml | 6 +- .../src/main/resources/bootstrap-prod.yml | 5 +- .../CaptchaAuthenticationTests.java | 48 ++++ .../PasswordAuthenticationTests.java | 45 ++++ .../SmsCodeAuthenticationTests.java | 43 ++++ .../WechatMiniAppAuthenticationTests.java | 43 ++++ ...ourceOwnerPasswordAuthenticationTests.java | 99 -------- .../password/SmsCodeAuthenticationTests.java | 100 -------- .../WechatMiniAppAuthenticationTests.java | 97 -------- .../captcha/handler/CaptchaHandler.java | 2 +- .../src/main/resources/bootstrap-prod.yml | 6 +- .../system/controller/SysUserController.java | 9 +- .../service/impl/SysUserServiceImpl.java | 3 - .../src/main/resources/bootstrap-prod.yml | 6 +- 40 files changed, 870 insertions(+), 484 deletions(-) create mode 100644 youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java create mode 100644 youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java create mode 100644 youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java create mode 100644 youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java rename youlai-auth/src/main/java/com/youlai/auth/authentication/password/{ResourceOwnerPasswordAuthenticationConverter.java => PasswordAuthenticationConverter.java} (92%) rename youlai-auth/src/main/java/com/youlai/auth/authentication/password/{ResourceOwnerPasswordAuthenticationProvider.java => PasswordAuthenticationProvider.java} (92%) rename youlai-auth/src/main/java/com/youlai/auth/authentication/password/{ResourceOwnerPasswordAuthenticationToken.java => PasswordAuthenticationToken.java} (79%) create mode 100644 youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java rename youlai-auth/src/main/java/com/youlai/auth/userdetails/member/{MemberUserDetails.java => MemberDetails.java} (89%) rename youlai-auth/src/main/java/com/youlai/auth/userdetails/member/{OpenidUserDetailsService.java => MemberDetailsService.java} (69%) delete mode 100644 youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java create mode 100644 youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java create mode 100644 youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java create mode 100644 youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java create mode 100644 youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java delete mode 100644 youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java delete mode 100644 youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java delete mode 100644 youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java diff --git a/docs/nacos/nacos_config.zip b/docs/nacos/nacos_config.zip index 304e82a22e484097caf974f1d97f26db199235e4..7b18968636026d16d6a2612bfce539ce449a756b 100644 GIT binary patch literal 9770 zcmbW7RaD&R((Zxa?he7d3GPmC4ekz&ySrPEAc4jb+-Y2zpn(K;cL)&N-A`tJ-(EX2 zXV0v&PW8>dZ+=(xRMk`Usw%vK#f3sdM1-QwUD1O2Ey#a;zm<{}*HqLnmQhpDRDI*) z>Qm7SyZ;5riVr z?_j%eM&)T=vDi8SEGM2WI*U32Zb-6j#L5bh+_psi8fv!bUN=d}XjjWn7wt|;9)duk zmW0z5?e1E^nSw{F2pnQr7F&!FpkScN$uM;4*jXs_3i6-!<6K6&g1qQktf%=?2cWBK+%Ua&yB?~QknP&0 zU)jjoxW3#t*}I_k2^o`~auh9Th4)vuoWn{i1N$w^q-CZyh6`6YD2DSFe6;|ohY)8c zhwcu%0c?_0#lQ!6jOFFW}$-NRa8bC5`28`^gFbaC?uUHjA4QqN^y$vW`7=aWP6 zOMvfz`2F1M*?Py3*^cF8Kk%u58`zdCeE*5fD7A?7!5R35!OIoE}W$;HgoQaXz=_f4Xh$kRXy9UhWiw`@w zu=4doN_*8-&1Y`i#TMJ6up}#n!4nL8g6@a-ibi6lH>pU^H4;Awm=)q-4D)o}c^g}U zoA{c8Gd17-v?Bz3aBWL9Kd$x8yax^maU`(}NcQ=j;NH_JWu7**F6}VKspDp~?PkOeJw_6eA=%#z zqs>lF$JA3k+QRm(V4}Z|#g%;o#cU25LkID8`t6=#Ygaa}&MwHhjnBo`6*)JX-~w;% zu{|2dCNG4Ho-1j{{;)iF`+V>G1JIIvOP6|`|IVfA;Vfr)CDki%j2&$2 zhpfqha`E6DHT;0ue*3& zKWg&KYREj_;IU^a;1V00+VxpOBD{)#k!xamXBgc@N+PJdr+&MZQ0EQLR<&2MHUOY=)AK~#c4N=%b6R-w0}OuH zj##j$FYrn+zTR3jU#F=eb6|M=2n%})KRVpDDnngZe9)68Mlrv>Laez{N6iWX@(uS) z&-q^S-aOcmtYD~!&*KzLz&yWkpeQzdkS2R*JUL()v{TR8arhZvNEIef?zcXRZf2}z z7hD+eu&lGW#$@7q1NecmkHgznPav%0-9G5NNE1fJt*Z=3*q>ioJUVs)qWKsI9lN-# zFB8ZKy$ZAYa@>W^9T4%uG$FC#})CnfNF>LagbCuEIB}UWSkEUM_kU zZ^{H{tFE@T54JXJC-+N-y?A`T1P%HH1#{G0Np~2GoKX}8DitXmG89tHLO4ldeBX7J z05>QIY09i-C}YRtBl(O9hb&dq2-kN?pZeYx`9q6rmOu41tg)N8R*^r#SsXWMJFEAkegzBGlr0U(PBgUG*gz2LHLflNNvmZ$ z$>2k4SQ~4h7!?bZt#_#`9$tjo8wC?jp<`%b+`z%%#B z3-lj)ANmsLm;X!e!U+GQcgnxhJJ8hK(#zE6UxF`EA5mTs!|Zrq)XJwXoX%ZkaVgjM zNP>I`&2tT^ocEqu;pUQ%m4imo{Gf^yE7lMZ`&>p9*`&58k(J)<>A%nH&g&UU(xM@} za4|B>yY^UF>iT0uA6^1IO(zKJ$2w-xGZl>-9Ow;zi_^==jOUeyJrYXI_|{waj~S}s zUTF^lUX6{(KIL&cLW{({HJA!@;Twu?qKjdo1sxR&-0Aw74CVuqXxG zRCfY(XnC0yvR=X0`8`#q3oi4%ekIZ4~8m~ z!m@(x@Wx5?p{fya=OWh$W7mvn9_Vj~^eM(s1SamLPPYzDhDHh}(B_&CMLtdIYp2U> zeAjJRi4%n@Yaw;PVk)1a61o=ej|wJRS7$WG@15D-cmvS$tPYq&A=X|~?m4yv%YDW{ znMk(GCyh&-RZo!wdPRSBO#uP6tsVNuzxA3i%X=#Nh-)U8iW*+)f47s|*aO#)40 zO!Hhy@CZx{%~%8pKD%NWw~-Td1M;PmWJ@2v9CDqAhOaF*vPY6bW!$LNb>O|&lYn`+ zbae;Im5{tSipa^$Uve?cPz20+=R`H&kUmNa<)(GypVooVn^j2R*AT$BCV)kx{E_bf z)PA;9@U0=WK){rpS4z8QHBNZvEGl#paq*!jN%|SK3+X92UP;tZMf2iecoo5(Rvq;| zxlDW)p}Q!Dq5T8aTwQ=f*Oe>Ers#Mc-8wyrQxP#v)Ul)!)6 zu-tsy+$|mcmA@le`gSBTxPf!kI?n6^Jz#8PS{BeGb~AT_=fRL0;ZZ_iEfC1VS zH7PlV6O{n5%chI8v;$RA!{3?&{fZ^{9W-w6b{!NXO@*-uTs~JZX|C*NjgX#a*{_K3 zYE%iPt#_&7^kCiU7KJ)I{fDuRc*a$a)CiNN&hL#cIkt=+M zFI>y^65(T+ElJm|kxP==6*I;Ma|hv|DB=*?q`-8?AMAn^km!ryOjmY^ut_K@Dt9rK zuaU?Q>{3_YekU*s5+^vj<>n!PniJhFqV2Do8}FJRGePIT%X6RP z_4l4Zjh|FKpo^(8hoBHXEJ;uH-?MzHaFKR2Ue!;`wr4RhA$YEh-&i=W#7vW*KWCU}W*2?2q6 zfIZ2H1S9}eAv`QbaOxTY(|AW@Z47az#oKzkjg{Izv6rcy^L=RkdPSC@gEjSWbYKCy zvXsT^Ag6O_W5aRBo!aeP+{P+>SDycTZlT(%9$4^@2DPiOIH0PiXfJJ9GD=?g8}Sb< znb1=Al4Z)E^#?+JF$#H>m9t{`Zx+yz{=N0R*6Yl3kb%9svb}-o>HvPh`qLx7iIJ8Y ze12MLP%OyAHUg{GcBTGFr!wTYrZ*Vng+7yJ;jy`n=6zh({(#TNP{qhB5p?&bb0da) zwJ!I_z3AOe>&WT^0s5?s`}SkqFLU?8<-&6y1bCQ(o z9e0yd3l_x8MxrIpqWS14iGWU0!dS@VJs#aOD}veGzs=_k6ZuvOKtZ)nKy~ zl6XFpezE_&ahJ8gothqmIH+DIyY{b+Z0ou}8TPA~@gemll~p?j3fb+%bav(FR_DOqn;m7lja>=fND{=xu-H(fnuRqs#E!#u<51RX zbJk&Eo!`d3w811tP#}eB)M}PfeXX5M>biTIQ@yt&M8~_CWs?;= zju7Ep;Tz1&%WFwYIcMBI?&w-WmK>}^9tm?+)=1g| z;Xuf;fr3cBCo6V#4YtxBiHXnP@dPeDmlt0yp=4R|X_H7)C=$K*(U=)WCIl|3ifd|; zNn~!rm0Y1vDmo(hL``uOyhA5bQB8oOfwryF)U3>BoN65Iaj2|t6~>zDzo#)$rk_JBRBDB=~W#6uMuxF89wZ&a;u>Ek}Me#naNU!V6Z@ z$fpx{*zQHU@98mYzWBDg>;8D(?0OQ_wcQe3DO)!AmWh~%2nz%kWqjh-s-nat5+ES- z*r*q^G0<_~^bsSFymL3$y<2v4fPT|2PvX&IIo81f7orCkl6_*oc zMCQ)BDF-2sMBkpx^a6T>nPm(7yLYs*6(i`Lue)yYkd+?TqTlJTAPNwZ-q(#QwZDwXxBo zN>q;CLQ2|F1zy@S?t5#yix3ji-H4&`Io%L(mC)&B6@*HJYE;C6b9GmB8|5|6UQChw z&a`~O$eqRME*fPS`R8H^v4x_lHBZaMh0iidjNwUj=DE4=WSH;sn^#9k30!Q}zN=?57r&)seB_)RCc!<$^uCoNRd~OVw?5Y#LCIO1sUvC@wBxIR^ z3)<{O>a|^NlxLvC657PGRBahjAMA@U2{wuLDYyF1ORtrXhAHLgTSp{>o?<9UIn-rL zs}lNgV`;x6P`X45+ng6xQZfKochdFKP|3xw);Nw6&z9>_W3mT_YE|Ce^Kxq^vB{yW zZ>1FtODb?k$BPl=Zxc~VSCVGrCg_N}B)WSATmWEnc<(TN54(GAY$SI-RzF3k0y(_ZeH#nrHmH9Q!v7s(L zXphCR==VHJUi}z2y}`M1vojly7juHpB)$j*J>Dnp+oLNBI^FAsv^=hj$`O+5b7Xzh z`7(>}x}&-ibqFVdEyWh{QoJS-bk5;(#$tZ@c<2wwf)GbGc{Y$lf7fZzVs{ZpYV`uT zJ9F^~lZY&;^vvi1=Zzz?Gcfus*Lsx%e|U$m7$J6ICbuoiHO)yQyojMpWe-$5O{~7@ zsrr(!;|px@@*Yry2=T3uladj1+ZGsl_e5Y;wmLM{<+TPu94GhbF+8K_y;5d8JjRuI zj7yNZYbiLp>okxvHECjMsEJ9GOxN&b0Y6PFOjqkJb^@;Uot>xEDoA=Z>nHY{&At5=I9!t1yN`~>*!xyhEqgA~ZXG2jMJC6NphX*e6VxW!P7Q z=J!ium2rW!guJ)x;&512k=`?a4It-l{wN9OH%AzQze++E{QpP0cKhFw;GnK#hbxZR zeo{>seMreByhVrdo`HQ`s+$J5ceFR`IXNYKEn`8Ag7qjXEAbdDA4-lJ`mPr(8hbPH zF(YSTWT4Z;V@w&lmmgLAYQG)qJ5#{?D(fAueoj%7nC-MJyW-u>6%l zEA1OYN^*hC4L@3(?$~L#POW9R#IH`B*R==A@w^O3=%><)(}#@)_s{8nBMDL9@t32H z1q-pfUsB6*EQsYOQ{RIU z@vdjuQe!s1RAWBgm?qQ*wBe>aQXz+?#(eljN}N?*l!MQs7)8|F;o>DO!DNrjsF>!= zVdO2Nmq6r-B3Z)*3(-K{Vh({%V6Ew88c)s_vZLaASRU(qjh?`B-k9FAIW;jbXLeA~ zr?gx_S}1ZsZBGAq%x7r9TAP_3|$uQV&( zM2Dgx6sW$v1E7W>0(GM~@c}AQe4at633T-Tvkt~x%-sC=%U#p_qr3i_hUf9W*&U#+ zq=F+(*m0w6uQs)G9GPV^Nr;y0J<F#kf}wnLUexjEga7b@2q!akf{jKspE4p1rlL#q@SuwYy|@QL-TW(9 z1PUmlemwN{aP}WcnR9Yg)_M_m;iO~K#}Y8%&WzD09?MvXF$gl`wRgW4UV5~zC8Po8Q~rSANH7>MC{?GvUqcPVa-Jc| zIYLe7$Pry4{q#(D={40ebNDTNV)eVTm()&1yLXl&dGT0cy~;z-1Ib=rSVL#FWBcgr zDzI}_rrj$uE#NkX^`ub0y?fx<`Jw9`8y~`_e3@e|bS?&CLgF*8yk)~OIrkNZ!$RIy ziAY5C{1crvlV(ZGrS8^>1}qag<~vMGT%$`v5BR==y`gq@2M_N$wVErLhV@K^NJ>}g zQ4KXOiD{VFeq9Kg9kW#wV>tCbZho3B2(YL|_rc_?<)@<=zyftoOK2k$7Cf$AB;(}N z{;Z-{weKw7(Gw$O%9Q7uCGUGt1Yt%E(xHU2Ie`E(&TIn%ow+mdiqz`R?u|)#)F^dT>ocBicV~<-1bWZh5sl5s$Y&wT}oU-{C_W%ZkDF* zrs|fio|dkx&aO_*magtLmTpx#x^7E6n7@`x>v6#QE!AXW_DsZE79&+|y1h_JIPKI>;tv(O)7&$rZ1e^*>v6}Yy3Y`UT0wUTY?laxIkFUI zeiDrzNJO#aM*!Q>Uqh?H#&WNIBA59kR)+h-LIU~?@mGR?(0*fI_}cYf0Zt!8__Yu3 zFye$>KIMkXvK0$N=o=m78}f_vlJI4{e}%j`LVuY6_5gu+iL!2- z5WjG})es<;XI?w_h-V6-SrIS`uLtVKgjDU|r^j-o=caxTr{f?@<&KM0lKcR2VbS-k zThG1c25@xLMpWT2y!SIn4taO_InA5vU#_91JNbAE`dYQ^JT|u6#dU18^H<&G%$k$=DRmN&v+?Mt9+_ zs1$=mI_k*}QFhJ5W}8%e=hWA3(UNx0A$v!)EIMtLeU-VmDb>?kZEMM5X@za+BdCWQ z44*E)a`XtJBtP!LK`|snh9)DW}xB@xx6ELTvYzDwM)+|bql6q*?5G=R$n{D*XlRbAk zVtsL(NNC@7tIhVfAJD#(9GbzmD5vf<3d|}XM1*@YsmQPEsKZs)k7JM$k34#xZFksD zJh+stqAcxDqL~XBfxkE+@APQVG-iJ2x>m9cHWdD(+Y$9%Nt-sbuX}6D#o2_X)Q8Zw zb%sI>YZH_|Z7dc!QdD=qDfbRgbdzR}W-pfdYG80F9_Ntn^*2{w$wE6{9^30#x?IG} z{1hd(y(>UOH{H-lGk9`ul~nW%VU*;X1oSE@q@14=D^M=EmZSsX3SjpwdX=(Sr4 zHtfNZCgH0XWU|;2y~{9txY|ZKdif2|dtvv)Wu3k4vbN}&q$jv66O)~9k_BfqC5O)X zV`1F9IEy{pvCb+eC*7ri){$}H8bTp<+c^)?TFy3+{NZ7edOarq2kY{p5#@o!tBQ>J zuK25gvpYGAZC^b;3d%gB!?dRL2zhXZ@1sSVt&B>R;k_BM0j7aY zg=UYX0!+CVkiame!wdvXuCLR~m+m+E8HA7Fa=Gp&N{i31*O(Mdy;PjaP*e0VtZ3t{ z=W-zlsVJb_0<=TfM^kSZvwfr={lcA48sxBm*7iY&El%NmxAwSg zAjXe4o_^EV#Sbz<$o&4-Q5(7W=mYdl* zcXyd{oCza6FUnynT*ZVj)>cJD$~0)1#0<^;pJ$F@R~s19g{>ea&F=-Q5XEwr*Fpq~ z;3C5o#?I*Vo@NDbeY50I(KqJw1K0JBVe&BF*g(9Xm9gQaURKi?8gBmSsLV9N4pJk4 z*PCwJ=82;oUErLbMdNcjQSfSNOyc?vxJyj51g=B^C zy|KV;Qd(nXWAUp#r~0qA3eGsY?g--mU2Qaf+k+yzPp>u-l=%>ho^^Yk-sJ{*Z}doT z+&`6AWnh=JMx19)I|s zzW&3XjPlQGJN)t{QaFG36KEJ*sQ)~*`Tf}c$5Wd>$3H*3`77)9Ln6hWK%igl=)bal zdo1u*+V5ALKaP+8iRfR4%Ku9H{S^6E-0wS8ivO^FeaRvJ4eqzY&|hi4Z-M?8eg6~0 z7=NYxX)F6z(C^;y&!8sk{|fr|3hA$)-^cVng9h;bE9l?G^RJ-aN1Z=|N{Id|=-)H` nUqQd8-5-mOsvn96cs;OHRG(9j^wInx>-e+t51*WwbABI@#LhEgg@>dH(2M|V48YX)O? zH!DVfvAvzW+PVW8AG+U+W-NHswsDz)m>g=QDE^0FtVAn-Wc)cfWP-qIUb>4Kjr0+y zO|c7;S|B*;E-CjhkDeN_x={FJ31Am%^=>jJeS%;7&cK$*r@otjfQC}%Hu(s}EHS-t zVDW-~hF-Gy!WaiK5(sHi5Ll6!M4X%qHG1|F+ibD}K0_EiyZp;2zV$gA`r+D6QO^*V zFG-s)E;cH|xGPNKuKSGSX}l^C(YJS>5eGT@^pj!6`=OC-Wk7mxL%}L{?%7FVYMPc_QzShPKPVDr32CPniuRYWTk(eZ!X$l0WauZ|2yp*Z>a$m_To~ZVx8E-mZmC+iI=n zL^ne6>hAs{%URp<3G)d{WAIzKC0J#%bHvPQYLFU;dWPBXu~l$jcuv$54YB1rZgZ50 zgD>swY-{oE;6lipYHxCrmlJ-bB|5*+(2-F9YjK7wTt=+|a|gTxAm6CJZ!c`31(#MS z4MtFnBxNCO5Fx@Q8Y4_9l+tDaO|?Q>!mKHL4xL6bdrB53!**%h2I$gugP<%21r5xe zb!P(u0il2Z0a5<1^dtUn^fPs|w|8{-gMevjx=Lsw=>FdfSzws1ly!6q+8k|sw<*JK zlOIIOI>q?hJj>4}qiw#Zpd)# zxz1%f;sX*kF)wj|xP-D2OwP>QV?{ZNZOkNoLh}mp0wt0yorxeyoCoY`^Kxg%BfulV zNV`V6O$+Lhq{)W5<&(~YA2p9u%#W*PX%l_)kX0ekL1IQW zYj}L}zcSBe%6I^(Jd3nHc7NqwJ-GG3MbI2{V`PU}3i1LI9@~;MfY(Vc2g}Z!O&^9w zLV}NoHf78c0RxHx)n8|;7(VwbKO{Ws81#Ef;Vi`NC-I&8d`5JM#klc$5x?ka+qLmz z`3}^}8S8}q7}g2jIfltmm7%;A%$`BQykTr<9MaTq=iS|{VuLSW?{LO>l5JCr+w45& zIcRhuJ^CTAEQ*>yY#61G&HsF0jF^qGiOqB0d?%)OZ@6+n9PASzYA)B<{#0fA06p7$ zv>zSd7BjDnlNYd;9NQ2H)>sWzP2JQFdZBr&@YNAjs%1q?TYiJN<#@yjv12VK2MPq+ z1(wt7J8ENSZp#4z2`gBZVS~p|{}Af_;fJXKpWV6r z&rSE9=2{fm`*ko%1Tgmdo*$kG!j9JpY4vF#M=b@-6*kZ94d0EhQUv?Whl3L{@>yL# zJIPWCR5>bA9|sj0sb#A21%wMT^s9v%Cdea;5*(JPP@||D)N5M}H6Fh>DA##3^QTJ< zS0GwNNeyZ68|XR+tw&38+#DF%sb3um2B{@^WHk1@%mkdNT#+*)rAK zwrF^G-Ma5N#W~tVjv0X{r)ZW0!td0&F~+>6aY@*(zZ6F0OHkc0wem`{^C|~G(Y*v9Ec%1}W*XD7dolhm zrp^2#(@6ds(=3hM%sq_(e^9MdbyM*d)q1{_XM)#DpNMv?hti=HdzNg2$$QSs!z|?T zvgz8z%FrbugE3H22NeYO_hNTQC{zXCnXhhMq}6ZBd+II^#Qm_j_dUwF^gXf^PeXGY zXGL~t7qzggTtvXmD36gv#?~Xm<)yO_+gdltE!v8rj+(0 zM@rWe3DYFZnd)dN@gq0{jUS$*2|G@42BxU0)ocJp%a&w$#GDA(+2_3kq2$dOoQJM# zxLV|TVf-oyy2Y|dPn9wXdSi$csb<-TDVx6240mc^qZz*;Hosm`K}lyOtTOMwIq}vb zGJ#!~Qo_I;uq~}#VGZ^wxK>gFxzBb8l44ji`PnG;OK^-IEgWMZYFk^4x^RpgP8X@N z9#M!YwSPeR8NoLTyH9<@-zCbG2KMTEXSh-h1 zs;QMrcx{IydVb*RJ5m{;kE~uAG}LK66FKriW@qZj4SikGOA(~PETWiTKbe!S^^+40=T*ONLZ}Ts_nmr+1(UnRF7|Y zyd2OA`UtQD+lt3@Bq~38u32vdK=*don!;{=Xxc9{^P_uPW6`s>YHCVV&qlp6lNAsg z*%2WbN_QU$b@Zx$MWv1N5@zs|*a3(UTqJVC_ax~3c++V{6VINq#ZD|V8@|k%8_yOg z&p-2k>?VF`v4Cz^qCdD=YKzT?+za&V^>Wx8jtTYDVJxby%p$)qBVW1b?mwH zQbMH%g}?#R{^Xer1;w#x;j>Zm(ew0qHqesZSFy{4(G(joS-K%`U8@@>yyk)1a&@u= znF5e1l|Bfzt0ic<*iqtd0Ga?eFC@TM{F0f?DIuCk=z`|hN?P}E!k{TVrwbuI+RQ2< za3QC#hG%Nr97yDg1(cH?OVfLwvec_HqoXf~)N7=H%TcN`ohBAAAZC1n&rtrvENIS# zWHJHa!BgPIkL)!^#_4*My8nT&RW6cD*F^9<2qh~|NAF=H2OldlBm>qh7vOcGDx&Ps z6WV10ljMvp%^5+`5Mf`rH|a z4BF#utK{XgO*sIXvyS1SWtEPBtr1Z@Yk4gKvJ^2UbKt#hk9a_u&wRiof6}54s{J1lb5If%req`zg>8B+)7;SR_;zXyJEzKM55}#J$_Je~3AcEmtC>1#N9WGFAIZgfdE)sC6WDUmvr)G*hh}h6 z*CMOfq(z{jRPspNy=XGCb24+VaqoBh&|((X*WwN8-0vMjPx+L!f|?HQ#@E#KKB`|V ze+_6I7~H%&mOLrK9t56}WJei?B+L5kP`cpPLCSnjv$SSlP-xwTw};G$<`~k)fhqcu z>m2@)bg61WCX;D-9L1qdLy1IAB+0OsEl*V*ufz=D3>cE-VN;cYp zE+eJP{K8DC(c=1RmjgBZD>D>D+gp>pRrG*iOX&iV{p9!09$%?? zhu0f7R}m8YBRTInl|J)!av42J;du_8eDu1ee=J6>tGp^n8Z-L^I87-}`LS2cFOLi`;)q%sDBhU`SNZS?VrB7V$aOiuNG=^pLq?si;NANGQy zBc?^qWTc}=xAE_+1BogMy zV-pr5yd5I2HregCk2L)wibv77bCw?pwlg+ybTM{wboqa2I8H-X>9?BQ*JIOQ*bsThJS9%Z zRusX?B-gz`zbtpjF&my5F6gCh2`6z&hyg5$#4)5eO6Mg?7&HUU59KVf-W zSa^0T%A*q|S{~*Azwun>UdF06xU*H4AkR0%5KI8oi-@&I_c7tFR9k+sa^ApC8)*Xt z^i$x{&Bw6TW@5IJ4;eQ^fVX@@>1tqn@n*Ze-*cIGTM@LoX}&G8(sHwsIb5!V2d|b5 zD5P#})%#s*m-twB_QmDr+M~tO4-5n$V=YvKj!UU5o|U+t4)M9Ttss0 z<47hPC7zbm>EIr_+9%vp@YtCyQQQT>WG6a(yG@dte-G z-e7s1`(hvV4BYtS*i08qA}U708}DT+aiF}N zb_)!$2%?f1H%u)`$2m#@^$%2%j|4mAC}3FT4#^F((97f@ZGC7_v6Znd+e}|^s(D7N(`dS9$*cPlk<4g!ZB6KVn z{4NS(Z^v8os90FQd)jMQw0O;d>u+5B_C5a2rLqqf1Ovwy^l&=F!!XF%afi@fK;$Zw{jRkEYj7TlZ{lVA-sq zP)=*X&VWoX2iuxo;c%p>jIRoUQ-a5sFYinigr|a{%em0#JFSQNL~1*>EPSXL+pz3| zdC&yJVJ0CqE)G8K!_LTo?chp1C4FWDc{phdvOU`eDiFCS+thx<)tn18SBrUtFOUAZ zqkQ5zIquZ#3wZT@AM7w&;WAegWLDrG9zLn{WCrCBR2q5V<4!hy6gXY$LYA$#tL;TR zzKHLAk4!(py){)fPV^)>ZX*UzHWQ_;hc`3Ggnh-i%0Iy5!WkR+0Xkc)Fq%8Qy<_2O z4;cv!Wb%5zt38{o^loVCAA%qA)&ea>`Z5zb6vZPf2iL4~jl>x^N~%Cdcl;RK&mO2x zqTwpO43K2aF<%?zt%LkYR<3Wq>I&7br3ZpDoVO!8bo2A&GdVI+Mbst(I$s7^kajMd1l=QKofIwUMpb zSz9_j@~qh(li_wG=-05mDqy72&vk4^QKN3#zWCw_>J~K!>rS$|S(I{y!7@|=l35Je zYW(yxO4kpMosp&Ls3IzUQz3ET#Ev@Kj@;u4(TS$K2ZvglvqZ4MNE=1PteLk+6`Y2Q z3E`YWBB8Q59IG8lyDZU(J&aFmx$%|c^NsMkbU*}V|FAx(_i`*@VzFI+7dDH!7!=8+ zAX=?%PDid~@v4(FEsNqK9def2=Mk_$t*Cgd`stN|MSaqO_dl{s^M%4EJV+1_8Tfz7 zGI;+h%h(&+*)cfUyZ%uz`>E?X;&Y(;&(t@dr$)%K$vuZkMk_1p4uLW3|HKCfOxLK+ zc-i=HDts(aE0K{!RUy{H)rlg7{ZQyo0s=dp{_^Se>;f$y$keC4@Tv(n;Ar62B(4aMcg~V(+tSwNhi8t-T zj#ee{pfwY(zPcwsI(_hW<&aRr;pf@=9+AbOzli_t{Z!N|vs7HToab3crhvL`EJrr$gkDa?rEPscnscga(&m z9XdifD$vl9e4*@MV&xSm#;%)Oa!Oxh&K?NE1fru_rdnl;0>g<7_3{mgv|aD;s|-8>(#QpzC~0#JJr9QP#DdV9B+;duYa|-Bf6cssw_k?rCj+G(1@G)p1sOE z2+b9GB7XvU-RD}t9m`f~Em>+Ak!WH)ZT%jKOzFwmIAEPKOB@z2MLmo|{vo9{ZbTkn zN!LKDv3+&uRk(cMuFmVLwTm6#h(|V4xeS5B3}hu}lGny;!1gMxMmx5_uq_MD!HUO? z^@^eh16MW^G(&X{aL7v8TRx6od+YM^m;>C}w%>OHOSzGv>C045NURGagbPOT{q60+ za^;Jrhf|i1nfxbf4-0v==!sqz0X^%28z9=L2|dg{qZX$%zIVHqhjjpNv#e8#cc9C2 z_0RiU#XUJEy56_J8X=bFxaG%~yZ$65Rq`MLkVhL75pu+9fdtp%u11@J->}c3d$3kZ%|juh&?Ov}ATNV^TEE9L zHf(ING9gI~^664-I&!TCyej)&=kjbZBPo5QI+}Gu${O`y(I0?V*(^WJB_e886moA) z3?TfHLI!NN{hZ)vOo}ca7U;0eV6D$j=h+YF2HxUrtQQ~Ls(RXiCdzL47<9WxR!CNO zRvM;&FU!|Sk})}2)Vo^VFDZf2@Q@i)kNtq>pLz4|5HlLJw%p?v%^<;NslDwwzb+6K z3u!ra&hBJk69PV5oSlauc|&4vD11q-d3Rt~3yG_;&PAKHD)WnI0krTc<4_i9KP_C)ey>3X%)NL@75O z#}OVIM1{k2&G?Qd>cUHk=%Q_8nvdy>vQC&#X(<)^c&F{h4@X!$GRW*V(mQKx+!-@QU5)-n3MI6<({AXSB42P;$B6i}FXrNyJDeO?h z3bf@FD^Br;$H-LYFjm3cCgg!HEFOY)uZ3EPKN348$;gsN&Bho^C;#U1{%$(}@wFkMH-(upI?bkDmxd$EZ|B0Br8qhewv7 zptw4V*E>H{iSfj-oExPNEDZ~$^}1VH>ymI~oQ8Rz8B++um^5#R?^dd+-!5HlN%5si zMB8H<9ZRuc0k8#R!W=UCBFvr>G|Gy7)N*Wx%CEBRQK~%!`p-#pa7Y=t}2czPUq-tVt;rKzW@4F z(Vb#OimbW0P`%$Do~Mv8$eozOv%9-zb7S3c6Oi6fUFu!^F)R~s4&KO^VokSemaItN zBD9~}<(pc=(6V9(<~pA_z6`uu$k(j~ema=)K0Q8k20IXUb*ydgUU1iFwb8frLe5!| zRhaswLKncN*t8+-z;As%O&P=uFvd84Fwr|Oq#Byg~G13f$Zb&83i) z!+W}-9I&y9^C+dX{9KA-4AeqUyQB8kklmOH@F0HFb|=qKWpmJJ_Tzbf=HPBbpBYTB z;smUvX>P+0RJ)vQLKYK&bTuY6!w&zL^Xp=XSx6mt^#ta;X=AfgsV7N#e(;Yz5Oz0b z1N(qtT)JGo6(9;{7pnF0>a`KGxTGA=`gu9l)vNvG-G-;F%{zO)8Io02IGY6dsV1L_ zgz;(U6w?Wr!!v!UH35X<=5wdZerhQft9z9CE`2<{S$a=S7PIDF<|5|elTELWk8$<& zpzrA|)A(!t;$cnJx{dw$$qutG_FqtywpDEpK=Y0M1LN?&Dd1rvw6CH z2@#gg@1W66>bEZCX7lrIbmMb(j199VUG=N_G^yjL{Q>{PAB@$m_-Hyd_tV4vZrG0* z_X$cEhOoz7E-d@{jIvWzREyIaC!{rVpa#+jWg}B$r6#qtbl|A>8py4qLuQ}NUcO!_ z)%G+b`eP)t6-e9Oz*Bme)i(4X_w=hnF1=ctY;*=QR#*y^4{JiZhc=|Y5^3G83d|SK zGcF-D#IKr)SWe;$PG|xoH+`Vig)UVfxx^GBG(0*CMc4zcaKHO}?o=_wr(b=(=s))P z|D^(R{g;HQ>Dn#||Ms;|DsDuii~N$qNNq5Dl?&GeC(I7n`OeKKr{#4tX{c>#S5=tQ z8^4DHdx>lWQ>$yfuuc88wY)5vj&(4uky*d-d~5fkGIg0U|7r*Rfh-+c-3s;s9sF~O z7+V2tOyo9Sr5EUeFQJ<>1Pf&$Gn@Pmctz3dN!QZR%4F+wnxW(W5V#mx%rg_t+iQN(wAf&%W8{G2wzWD^h_MdY#h%sE4~oJrQ% zS07chIAE}~KwAVA)j5)@4o{wJ%;wfz&-_KKIh$7^fwo2)YS-@9ijn=oQZH*9=h9oH zxFQ(H>qNW$*Z~x{tNwE6;Gi`OHZ1zs6epL&V{Py}XqhioWT)gqE@+2ESEW2VVu=#r z$c3)t1kh_A!-)A^M8&DaL$%7x_63r{Q?hof3t7^W)ytBMF74}I+S?`Jxbud(N3j12|#^2hcf;WkjT3ffyH+422-;6t3KRPhpUUqHy zH8ugA(aP6*uN(9>=^X>kg#vDYttx^(s&{p1w)f8y3D5Z#7al}~y6@vhKff-x)ONm} zFHJvX+wjsN0Mhp$X>%pP@$AKhUWrMYOWVkk8s#xKZL~cK8!YGA=dfYE9F)48O0May z>-HL8EL|;dsV{`GAdIj>$#&+xDa;<`v%VP)h0w7s+D&4xj;Dx^7u5CV>p`RlXX;wh z6D$$%1tS%eR|yc}LVC%~@jT!ON-^eNJv(whsU8SL=0;3>K6oc;71Nr***^B?VY}F_ zvewO3?HBoKLm`|j4zjMPHN}%4WXC3sZOJaZhpJ4z0akw6U5BLS(Yr`Vb?M#O>+(v3 z5-d3g!oc22kO30aX1$=3`6L7#;`Jd}yGX(J;R@L_TGBo{io^s{I%{_`eE#YAqTu~S zOWuW$bP8T1`G}`4lxmS%v zPmPi7l=BIrm!Pg-*e#QAcOi)Lq&kHG@=Z+I&9gV9MzhOAiNS{Ehieo?iV9bJHWMas z9-r`9zQd^R`}E2E3^Fx)^OI}O^?k0Zq(yL1wXc*jfT<4$xh>XH~UJ8{emL^UZ{ zjgB-2^ww9WZODu&ZGvyCjB0Gs%%*Yx(re#s5Y9DAsbfhi~bm3O}aM=15A21oZfi<4+Ds^&7E;>X9tlSxFK|6=e zKOt~0HT27qvvx=lokPwyt@-9B_$tFa(yZ^JsKr?1Me{#wAL>kD;>y4O_Pu%@qX6dao49)X7P zyK}Nx-N-3<^1Y@AbUzPo-}pW}wVJ#`Rm(XY9$1Tq!nYIW?HWMaF+XJOGDc>L3|^PN zrWi^k+(FeF-ug&&0QLFb01rhR++RWSqDKu+UgNiT81i~w%2(!R;@urpiF^&t{PkZN zx$FWU8g{dVZVy%cW-s!)@$m^ec>!m0@~6r`AHLXzJKd^^v(lp2~fm7O)a;x9zIM2h+FiC(#!Yy&AUx8WJ8K7hfrSTEZo4 zrFwa@IEgOYq`uiBy}dO)-1%&-%_nq(T;L>Lgg%Y?i>EtJFMeM;yef})O_{ON89SGo z6Ifnn=w5FR5o0W*Bbh{?*L;5g%z<7VIvsgN>XscGS}s}a^;ei3xb9I}(k{``hLCnr zv82h?C<#ea!dPD3v3r0r;vj6+kY<-3{iMIW*!|>@wWSs6DDa?KWf8?0LvI|;cvm)A z>tX9}?0@{~17 17 - 3.1.0 + 3.1.1 - 2022.0.3 + 2022.0.2 2022.0.0.0-RC2 - 1.1.0 - 9.16.1 + 1.1.0 + 9.31 8.0.28 @@ -60,7 +60,7 @@ 3.0.5 1.6.2 9.16.1 - 0.4.17 + 0.4.19 4.5.25 @@ -69,7 +69,7 @@ 8.5.3 4.8.1 - + 3.16.3 @@ -302,16 +302,10 @@ ${nimbus-jose-jwt.version} - - net.coobird - thumbnailator - ${thumbnailator.version} - - org.springframework.security spring-security-oauth2-authorization-server - ${authorization-server.version} + ${spring-authorization-server.version} diff --git a/youlai-auth/pom.xml b/youlai-auth/pom.xml index 09c31fe8e..3a682aabb 100644 --- a/youlai-auth/pom.xml +++ b/youlai-auth/pom.xml @@ -12,6 +12,11 @@ youlai-auth + + org.springframework.boot + spring-boot-starter-test + test + @@ -88,11 +93,6 @@ common-mybatis - - org.springframework.boot - spring-boot-starter-test - test - diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java new file mode 100644 index 000000000..bb50fac88 --- /dev/null +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java @@ -0,0 +1,115 @@ +package com.youlai.auth.authentication.captcha; + +import cn.hutool.core.util.StrUtil; +import com.youlai.auth.util.OAuth2EndpointUtils; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 密码认证参数解析器 + *

+ * 解析请求参数中的用户名和密码,并构建相应的身份验证(Authentication)对象 + * + * @author haoxr + * @since 3.0.0 + */ +public class CaptchaAuthenticationConverter implements AuthenticationConverter { + + @Override + public Authentication convert(HttpServletRequest request) { + // 授权类型 (必需) + String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); + if (!CaptchaAuthenticationToken.CAPTCHA.getValue().equals(grantType)) { + return null; + } + + // 客户端信息 + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + + // 参数提取验证 + MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); + + // 令牌申请访问范围验证 (可选) + String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && + parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + OAuth2EndpointUtils.throwError( + OAuth2ErrorCodes.INVALID_REQUEST, + OAuth2ParameterNames.SCOPE, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + Set requestedScopes = null; + if (StringUtils.hasText(scope)) { + requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); + } + + // 用户名验证(必需) + String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); + if (StrUtil.isBlank(username)) { + OAuth2EndpointUtils.throwError( + OAuth2ErrorCodes.INVALID_REQUEST, + OAuth2ParameterNames.USERNAME, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI + ); + } + + // 密码验证(必需) + String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); + if (StrUtil.isBlank(password)) { + OAuth2EndpointUtils.throwError( + OAuth2ErrorCodes.INVALID_REQUEST, + OAuth2ParameterNames.PASSWORD, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI + ); + } + + // 验证码(必需) + String verifyCode = parameters.getFirst(CaptchaParameterNames.VERIFY_CODE); + if (StrUtil.isBlank(verifyCode)) { + OAuth2EndpointUtils.throwError( + OAuth2ErrorCodes.INVALID_REQUEST, + CaptchaParameterNames.VERIFY_CODE, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI + ); + } + + // 验证码缓存Key(必需) + String verifyCodeKey = parameters.getFirst(CaptchaParameterNames.VERIFY_CODE_KEY); + if (StrUtil.isBlank(verifyCodeKey)) { + OAuth2EndpointUtils.throwError( + OAuth2ErrorCodes.INVALID_REQUEST, + CaptchaParameterNames.VERIFY_CODE_KEY, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI + ); + } + + + // 附加参数(保存用户名/密码传递给 CaptchaAuthenticationProvider 用于身份认证) + Map additionalParameters = parameters + .entrySet() + .stream() + .filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) && + !e.getKey().equals(OAuth2ParameterNames.SCOPE) + ).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))); + + return new CaptchaAuthenticationToken( + clientPrincipal, + requestedScopes, + additionalParameters + ); + } + + +} diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java new file mode 100644 index 000000000..8e8d13c29 --- /dev/null +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java @@ -0,0 +1,216 @@ +package com.youlai.auth.authentication.captcha; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.youlai.auth.authentication.smscode.SmsCodeParameterNames; +import com.youlai.auth.util.OAuth2AuthenticationProviderUtils; +import com.youlai.common.constant.SecurityConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.*; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; +import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; +import org.springframework.util.CollectionUtils; + +import java.security.Principal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 验证码模式身份验证提供者 + *

+ * 处理基于用户名和密码的身份验证 + * + * @author haoxr + * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider + * @since 3.0.0 + */ +@Slf4j +public class CaptchaAuthenticationProvider implements AuthenticationProvider { + + + private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; + + private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); + private final AuthenticationManager authenticationManager; + private final OAuth2AuthorizationService authorizationService; + private final OAuth2TokenGenerator tokenGenerator; + private final RedisTemplate redisTemplate; + + /** + * Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters. + * + * @param authenticationManager the authentication manager + * @param authorizationService the authorization service + * @param tokenGenerator the token generator + * @since 0.2.3 + */ + public CaptchaAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator, + RedisTemplate redisTemplate + ) { + Assert.notNull(authorizationService, "authorizationService cannot be null"); + Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); + this.authenticationManager = authenticationManager; + this.authorizationService = authorizationService; + this.tokenGenerator = tokenGenerator; + this.redisTemplate = redisTemplate; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + CaptchaAuthenticationToken captchaAuthenticationToken = (CaptchaAuthenticationToken) authentication; + OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils + .getAuthenticatedClientElseThrowInvalidClient(captchaAuthenticationToken); + RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + + // 验证客户端是否支持授权类型(grant_type=password) + if (!registeredClient.getAuthorizationGrantTypes().contains(CaptchaAuthenticationToken.CAPTCHA)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); + } + + // 证码校验 + Map additionalParameters = captchaAuthenticationToken.getAdditionalParameters(); + String verifyCode = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE); + String verifyCodeKey = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE_KEY); + + String cacheCode = (String) redisTemplate.opsForValue().get(verifyCodeKey); + if (!StrUtil.equals(verifyCode, cacheCode)) { + throw new OAuth2AuthenticationException("验证码错误"); + } + + // 生成用户名密码身份验证令牌 + String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME); + String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password); + // 用户名密码身份验证,成功后返回带有权限的认证信息 + Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken); + + // 验证申请访问范围(Scope) + Set authorizedScopes = registeredClient.getScopes(); + Set requestedScopes = captchaAuthenticationToken.getScopes(); + if (!CollectionUtils.isEmpty(requestedScopes)) { + Set unauthorizedScopes = requestedScopes.stream() + .filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope)) + .collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(unauthorizedScopes)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); + } + authorizedScopes = new LinkedHashSet<>(requestedScopes); + } + + // 访问令牌(Access Token) 构造器 + DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() + .registeredClient(registeredClient) + .principal(usernamePasswordAuthentication) // 身份验证成功的认证信息(用户名、权限等信息) + .authorizationServerContext(AuthorizationServerContextHolder.getContext()) + .authorizedScopes(authorizedScopes) + .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 授权方式 + .authorizationGrant(captchaAuthenticationToken) // 授权具体对象 + ; + + // 生成访问令牌(Access Token) + OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType((OAuth2TokenType.ACCESS_TOKEN)).build(); + OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); + if (generatedAccessToken == null) { + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, + "The token generator failed to generate the access token.", ERROR_URI); + throw new OAuth2AuthenticationException(error); + } + + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), + generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); + + OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) + .principalName(usernamePasswordAuthentication.getName()) + .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) + .authorizedScopes(authorizedScopes) + .attribute(Principal.class.getName(), usernamePasswordAuthentication); + if (generatedAccessToken instanceof ClaimAccessor) { + authorizationBuilder.token(accessToken, (metadata) -> + metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims())); + } else { + authorizationBuilder.accessToken(accessToken); + } + + // 生成刷新令牌(Refresh Token) + OAuth2RefreshToken refreshToken = null; + if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && + // Do not issue refresh token to public client + !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { + + tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); + OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); + if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, + "The token generator failed to generate the refresh token.", ERROR_URI); + throw new OAuth2AuthenticationException(error); + } + + refreshToken = (OAuth2RefreshToken) generatedRefreshToken; + authorizationBuilder.refreshToken(refreshToken); + } + + // 生成 ID token + OidcIdToken idToken; + if (requestedScopes.contains(OidcScopes.OPENID)) { + // @formatter:off + tokenContext = tokenContextBuilder + .tokenType(ID_TOKEN_TOKEN_TYPE) + .authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token + .build(); + // @formatter:on + OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext); + if (!(generatedIdToken instanceof Jwt)) { + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, + "The token generator failed to generate the ID token.", ERROR_URI); + throw new OAuth2AuthenticationException(error); + } + + idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(), + generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims()); + authorizationBuilder.token(idToken, (metadata) -> + metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())); + } else { + idToken = null; + } + + OAuth2Authorization authorization = authorizationBuilder.build(); + + this.authorizationService.save(authorization); + + additionalParameters = Collections.emptyMap(); + if (idToken != null) { + additionalParameters = new HashMap<>(); + additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue()); + } + return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters); + } + + @Override + public boolean supports(Class authentication) { + return CaptchaAuthenticationToken.class.isAssignableFrom(authentication); + } + +} diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java new file mode 100644 index 000000000..914e8a147 --- /dev/null +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java @@ -0,0 +1,62 @@ +package com.youlai.auth.authentication.captcha; + +import jakarta.annotation.Nullable; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 验证码模式身份验证令牌(包含用户名、密码、验证码) + * + * @author haoxr + * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken + * @since 3.0.0 + */ +public class CaptchaAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { + + /** + * 令牌申请访问范围 + */ + private final Set scopes; + + /** + * 授权类型(验证码: captcha) + */ + public static final AuthorizationGrantType CAPTCHA = new AuthorizationGrantType("captcha"); + + + /** + * 验证码模式身份验证令牌 + * + * @param clientPrincipal 客户端信息 + * @param scopes 令牌申请访问范围 + * @param additionalParameters 自定义额外参数(用户名、密码、验证码) + */ + public CaptchaAuthenticationToken( + Authentication clientPrincipal, + Set scopes, + @Nullable Map additionalParameters + ) { + super(CAPTCHA, clientPrincipal, additionalParameters); + this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet()); + + } + + /** + * 用户凭证(密码) + */ + @Override + public Object getCredentials() { + return this.getAdditionalParameters().get(OAuth2ParameterNames.PASSWORD); + } + + public Set getScopes() { + return scopes; + } +} diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java new file mode 100644 index 000000000..de76d729d --- /dev/null +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java @@ -0,0 +1,26 @@ + + +package com.youlai.auth.authentication.captcha; + +/** + * 验证码模式请求参数名称常量 + * + * @author haoxr + * @since 3.0.0 + */ +public final class CaptchaParameterNames { + /** + * 验证码 + */ + public static final String VERIFY_CODE = "verifyCode"; + + /** + * 验证码缓存Key + */ + public static final String VERIFY_CODE_KEY = "verifyCodeKey"; + + + private CaptchaParameterNames() { + } + +} diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java similarity index 92% rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java index 3cf6cb392..7acf99857 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java @@ -20,7 +20,7 @@ import java.util.Set; import java.util.stream.Collectors; /** - * 密码认证参数解析器 + * 密码模式参数解析器 *

* 解析请求参数中的用户名和密码,并构建相应的身份验证(Authentication)对象 * @@ -28,7 +28,7 @@ import java.util.stream.Collectors; * @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter * @since 3.0.0 */ -public class ResourceOwnerPasswordAuthenticationConverter implements AuthenticationConverter { +public class PasswordAuthenticationConverter implements AuthenticationConverter { @Override public Authentication convert(HttpServletRequest request) { @@ -78,7 +78,7 @@ public class ResourceOwnerPasswordAuthenticationConverter implements Authenticat ); } - // 附加参数(保存用户名/密码传递给 ResourceOwnerPasswordAuthenticationProvider 用于身份认证) + // 附加参数(保存用户名/密码传递给 PasswordAuthenticationProvider 用于身份认证) Map additionalParameters = parameters .entrySet() .stream() @@ -86,7 +86,7 @@ public class ResourceOwnerPasswordAuthenticationConverter implements Authenticat !e.getKey().equals(OAuth2ParameterNames.SCOPE) ).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))); - return new ResourceOwnerPasswordAuthenticationToken( + return new PasswordAuthenticationToken( clientPrincipal, requestedScopes, additionalParameters diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java similarity index 92% rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java index f0c660ad3..f1c7f57e7 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java @@ -1,5 +1,6 @@ package com.youlai.auth.authentication.password; + import cn.hutool.core.lang.Assert; import com.youlai.auth.util.OAuth2AuthenticationProviderUtils; import lombok.extern.slf4j.Slf4j; @@ -36,11 +37,10 @@ import java.util.stream.Collectors; * 处理基于用户名和密码的身份验证 * * @author haoxr - * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider * @since 3.0.0 */ @Slf4j -public class ResourceOwnerPasswordAuthenticationProvider implements AuthenticationProvider { +public class PasswordAuthenticationProvider implements AuthenticationProvider { private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; @@ -58,9 +58,9 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati * @param tokenGenerator the token generator * @since 0.2.3 */ - public ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager, - OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator + public PasswordAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator ) { Assert.notNull(authorizationService, "authorizationService cannot be null"); Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); @@ -72,7 +72,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - ResourceOwnerPasswordAuthenticationToken resourceOwnerPasswordAuthentication = (ResourceOwnerPasswordAuthenticationToken) authentication; + PasswordAuthenticationToken resourceOwnerPasswordAuthentication = (PasswordAuthenticationToken) authentication; OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(resourceOwnerPasswordAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); @@ -138,7 +138,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati authorizationBuilder.accessToken(accessToken); } - // 生成刷新令牌(Refresh token) + // 生成刷新令牌(Refresh Token) OAuth2RefreshToken refreshToken = null; if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && // Do not issue refresh token to public client @@ -194,7 +194,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati @Override public boolean supports(Class authentication) { - return ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication); + return PasswordAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java similarity index 79% rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java index d4b3da645..c9ad27d74 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java @@ -12,10 +12,12 @@ import java.util.*; * 密码授权模式身份验证令牌(包含用户名和密码等) * * @author haoxr - * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken * @since 3.0.0 */ -public class ResourceOwnerPasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { +public class PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { + + public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password"); + /** * 令牌申请访问范围 @@ -29,12 +31,12 @@ public class ResourceOwnerPasswordAuthenticationToken extends OAuth2Authorizatio * @param scopes 令牌申请访问范围 * @param additionalParameters 自定义额外参数(用户名和密码) */ - public ResourceOwnerPasswordAuthenticationToken( + public PasswordAuthenticationToken( Authentication clientPrincipal, Set scopes, @Nullable Map additionalParameters ) { - super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters); + super(PASSWORD, clientPrincipal, additionalParameters); this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet()); } diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java index fffc1a1d4..6dcc4f4e1 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java @@ -57,20 +57,20 @@ public class SmsCodeAuthenticationConverter implements AuthenticationConverter { } // 手机号(必需) - String mobile = parameters.getFirst("mobile"); + String mobile = parameters.getFirst(SmsCodeParameterNames.MOBILE); if (StrUtil.isBlank(mobile)) { OAuth2EndpointUtils.throwError( OAuth2ErrorCodes.INVALID_REQUEST, - "mobile", + SmsCodeParameterNames.MOBILE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } // 验证码(必需) - String verifyCode = parameters.getFirst("verifyCode"); + String verifyCode = parameters.getFirst(SmsCodeParameterNames.VERIFY_CODE); if (StrUtil.isBlank(verifyCode)) { OAuth2EndpointUtils.throwError( OAuth2ErrorCodes.INVALID_REQUEST, - "verifyCode", + SmsCodeParameterNames.VERIFY_CODE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java index 833f28b3c..d4c62ef24 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java @@ -2,7 +2,7 @@ package com.youlai.auth.authentication.smscode; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; -import com.youlai.auth.userdetails.member.MobileUserDetailsService; +import com.youlai.auth.userdetails.member.MemberDetailsService; import com.youlai.auth.util.OAuth2AuthenticationProviderUtils; import com.youlai.common.constant.SecurityConstants; import lombok.extern.slf4j.Slf4j; @@ -41,7 +41,7 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { private final OAuth2AuthorizationService authorizationService; private final OAuth2TokenGenerator tokenGenerator; - private final MobileUserDetailsService mobileUserDetailsService; + private final MemberDetailsService memberDetailsService; private final RedisTemplate redisTemplate; @@ -55,17 +55,17 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { public SmsCodeAuthenticationProvider( OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator tokenGenerator, - MobileUserDetailsService mobileUserDetailsService, + MemberDetailsService memberDetailsService, RedisTemplate redisTemplate ) { Assert.notNull(authorizationService, "authorizationService cannot be null"); Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - Assert.notNull(mobileUserDetailsService, "userDetailsService cannot be null"); + Assert.notNull(memberDetailsService, "userDetailsService cannot be null"); Assert.notNull(redisTemplate, "redisTemplate cannot be null"); this.authorizationService = authorizationService; this.tokenGenerator = tokenGenerator; - this.mobileUserDetailsService = mobileUserDetailsService; + this.memberDetailsService = memberDetailsService; this.redisTemplate = redisTemplate; } @@ -83,10 +83,10 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); } - // 微信 code 获取 openid + // 短信验证码校验 Map additionalParameters = smsCodeAuthenticationToken.getAdditionalParameters(); - String mobile = (String) additionalParameters.get("mobile"); - String verifyCode = (String) additionalParameters.get("verifyCode"); + String mobile = (String) additionalParameters.get(SmsCodeParameterNames.MOBILE); + String verifyCode = (String) additionalParameters.get(SmsCodeParameterNames.VERIFY_CODE); if (!verifyCode.equals("666666")) { // 666666 是后门,因为短信收费,正式环境删除这个if String codeKey = SecurityConstants.SMS_CODE_PREFIX + mobile; @@ -98,7 +98,7 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { } // 根据手机号获取会员信息 - UserDetails userDetails = mobileUserDetailsService.loadUserByUsername(mobile); + UserDetails userDetails = memberDetailsService.loadUserByMobile(mobile); Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(userDetails, null); diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java index 9b271ed12..6c276af6c 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java @@ -25,7 +25,7 @@ public class SmsCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenti private final Set scopes; /** - * 授权类型(短信验证码:sms_code) + * 授权类型(短信验证码: sms_code) */ public static final AuthorizationGrantType SMS_CODE = new AuthorizationGrantType("sms_code"); diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java new file mode 100644 index 000000000..ee406e1ec --- /dev/null +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.youlai.auth.authentication.smscode; + +/** + * 短信验证码模式参数名称常量 + * + * @author haoxr + * @since 3.0.0 + */ +public final class SmsCodeParameterNames { + + /** + * 手机号 + */ + public static final String MOBILE = "mobile"; + + /** + * 验证码 + */ + public static final String VERIFY_CODE = "verifyCode"; + + + private SmsCodeParameterNames() { + } + +} diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java index 51c21ef31..9869ef3a9 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java +++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java @@ -3,7 +3,7 @@ package com.youlai.auth.authentication.wxminiapp; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.hutool.core.lang.Assert; -import com.youlai.auth.userdetails.member.OpenidUserDetailsService; +import com.youlai.auth.userdetails.member.MemberDetailsService; import com.youlai.auth.util.OAuth2AuthenticationProviderUtils; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; @@ -42,7 +42,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider { private final OAuth2AuthorizationService authorizationService; private final OAuth2TokenGenerator tokenGenerator; - private final OpenidUserDetailsService openidUserDetailsService; + private final MemberDetailsService memberDetailsService; private final WxMaService wxMaService; @@ -57,17 +57,17 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider { public WxMiniAppAuthenticationProvider( OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator tokenGenerator, - OpenidUserDetailsService openidUserDetailsService, + MemberDetailsService memberDetailsService, WxMaService wxMaService ) { Assert.notNull(authorizationService, "authorizationService cannot be null"); Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - Assert.notNull(openidUserDetailsService, "userDetailsService cannot be null"); + Assert.notNull(memberDetailsService, "userDetailsService cannot be null"); Assert.notNull(wxMaService, "wxMaService cannot be null"); this.authorizationService = authorizationService; this.tokenGenerator = tokenGenerator; - this.openidUserDetailsService = openidUserDetailsService; + this.memberDetailsService = memberDetailsService; this.wxMaService = wxMaService; } @@ -97,7 +97,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider { } String openid = sessionInfo.getOpenid(); // 根据 openid 获取会员信息 - UserDetails userDetails = openidUserDetailsService.loadUserByUsername(openid); + UserDetails userDetails = memberDetailsService.loadUserByOpenid(openid); Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword()); diff --git a/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java b/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java index f7b91fd74..4f99d7f69 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java +++ b/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java @@ -7,15 +7,19 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; -import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationConverter; -import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationProvider; +import com.youlai.auth.authentication.captcha.CaptchaAuthenticationConverter; +import com.youlai.auth.authentication.captcha.CaptchaAuthenticationProvider; +import com.youlai.auth.authentication.captcha.CaptchaAuthenticationToken; +import com.youlai.auth.authentication.password.PasswordAuthenticationConverter; +import com.youlai.auth.authentication.password.PasswordAuthenticationProvider; import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationConverter; import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationProvider; +import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken; import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationConverter; import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationProvider; -import com.youlai.auth.userdetails.member.MemberUserDetails; -import com.youlai.auth.userdetails.member.MobileUserDetailsService; -import com.youlai.auth.userdetails.member.OpenidUserDetailsService; +import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken; +import com.youlai.auth.userdetails.member.MemberDetails; +import com.youlai.auth.userdetails.member.MemberDetailsService; import com.youlai.auth.userdetails.user.SysUserDetails; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -30,16 +34,21 @@ import org.springframework.security.config.annotation.authentication.configurati import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.*; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.token.*; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -52,14 +61,19 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +/** + * 授权服务器配置 + * + * @author haoxr + * @since 3.0.0 + */ @Configuration @RequiredArgsConstructor public class AuthorizationServerConfig { private final WxMaService wxMaService; - private final MobileUserDetailsService mobileUserDetailsService; - private final OpenidUserDetailsService openidUserDetailsService; private final RedisTemplate redisTemplate; + private final MemberDetailsService memberDetailsService; /** @@ -80,25 +94,25 @@ public class AuthorizationServerConfig { authorizationServerConfigurer .tokenEndpoint(tokenEndpoint -> tokenEndpoint - .accessTokenRequestConverters( // <1> - authenticationConverters -> - authenticationConverters.addAll( - List.of( - new ResourceOwnerPasswordAuthenticationConverter(), - new WxMiniAppAuthenticationConverter(), - new SmsCodeAuthenticationConverter() - ) - ) - ) - .authenticationProviders( // <2> - authenticationProviders -> - authenticationProviders.addAll( + .accessTokenRequestConverters(authenticationConverters ->// <1> + authenticationConverters.addAll( List.of( - new ResourceOwnerPasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator), - new WxMiniAppAuthenticationProvider(authorizationService, tokenGenerator, openidUserDetailsService,wxMaService), - new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, mobileUserDetailsService,redisTemplate) + new PasswordAuthenticationConverter(), + new CaptchaAuthenticationConverter(), + new WxMiniAppAuthenticationConverter(), + new SmsCodeAuthenticationConverter() ) - ) + ) + ) + .authenticationProviders(authenticationProviders ->// <2> + authenticationProviders.addAll( + List.of( + new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator), + new CaptchaAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator, redisTemplate), + new WxMiniAppAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, wxMaService), + new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate) + ) + ) ) ); @@ -161,9 +175,16 @@ public class AuthorizationServerConfig { @Bean public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { - return new JdbcRegisteredClientRepository(jdbcTemplate); + JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); + + // 初始化 OAuth2 客户端 + initMallAppClient(registeredClientRepository); + initMallAdminClient(registeredClientRepository); + + return registeredClientRepository; } + @Bean public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { @@ -178,7 +199,6 @@ public class AuthorizationServerConfig { } - @Bean OAuth2TokenGenerator tokenGenerator(JWKSource jwkSource) { JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource)); @@ -194,13 +214,13 @@ public class AuthorizationServerConfig { @Bean public OAuth2TokenCustomizer jwtCustomizer() { return context -> { - if (OAuth2TokenType.ACCESS_TOKEN .equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) { + if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) { // Customize headers/claims for access_token Optional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal -> { JwtClaimsSet.Builder claims = context.getClaims(); if (principal instanceof SysUserDetails userDetails) { claims.claim("user_id", String.valueOf(userDetails.getUserId())); - } else if (principal instanceof MemberUserDetails userDetails) { + } else if (principal instanceof MemberDetails userDetails) { claims.claim("member_id", String.valueOf(userDetails.getId())); } }); @@ -211,9 +231,84 @@ public class AuthorizationServerConfig { }; } + @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } + /** + * 初始化创建商城管理客户端 + * + * @param registeredClientRepository + */ + private void initMallAdminClient(JdbcRegisteredClientRepository registeredClientRepository) { + + String clientId = "mall-admin"; + String clientSecret = "123456"; + String clientName = "商城管理客户端"; + + // 如果使用明文,在客户端认证的时候会自动升级加密方式(修改密码), 直接使用 bcrypt 加密避免不必要的麻烦 + // 不开玩笑,官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099 + String encodeSecret = passwordEncoder().encode(clientSecret); + + RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId); + String id = registeredMallAdminClient != null ? registeredMallAdminClient.getId() : UUID.randomUUID().toString(); + + RegisteredClient mallAppClient = RegisteredClient.withId(id) + .clientId(clientId) + .clientSecret(encodeSecret) + .clientName(clientName) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式 + .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 验证码模式 + .redirectUri("http://127.0.0.1:8080/authorized") + .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out") + .scope(OidcScopes.OPENID) + .scope(OidcScopes.PROFILE) + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + registeredClientRepository.save(mallAppClient); + } + + /** + * 初始化创建商城APP客户端 + * + * @param registeredClientRepository + */ + private void initMallAppClient(JdbcRegisteredClientRepository registeredClientRepository) { + + String clientId = "mall-app"; + String clientSecret = "123456"; + String clientName = "商城APP客户端"; + + // 如果使用明文,在客户端认证的时候会自动升级加密方式,直接使用 bcrypt 加密避免不必要的麻烦 + String encodeSecret = passwordEncoder().encode(clientSecret); + + RegisteredClient registeredMallAppClient = registeredClientRepository.findByClientId(clientId); + String id = registeredMallAppClient != null ? registeredMallAppClient.getId() : UUID.randomUUID().toString(); + + RegisteredClient mallAppClient = RegisteredClient.withId(id) + .clientId(clientId) + .clientSecret(encodeSecret) + .clientName(clientName) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) // 微信小程序模式 + .authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE) // 短信验证码模式 + .redirectUri("http://127.0.0.1:8080/authorized") + .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out") + .scope(OidcScopes.OPENID) + .scope(OidcScopes.PROFILE) + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + registeredClientRepository.save(mallAppClient); + } + + } diff --git a/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java b/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java index 1dc7c85de..7800bbdf9 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java +++ b/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java @@ -10,6 +10,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import java.util.List; diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java similarity index 89% rename from youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java rename to youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java index a042ea16f..d4e579bed 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java +++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java @@ -1,6 +1,5 @@ package com.youlai.auth.userdetails.member; -import cn.hutool.core.collection.CollectionUtil; import com.youlai.common.constant.GlobalConstants; import com.youlai.mall.ums.dto.MemberAuthDTO; import lombok.Data; @@ -9,7 +8,6 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; /** @@ -19,7 +17,7 @@ import java.util.HashSet; * @since 3.0.0 */ @Data -public class MemberUserDetails implements UserDetails { +public class MemberDetails implements UserDetails { /** * 会员ID @@ -47,7 +45,7 @@ public class MemberUserDetails implements UserDetails { * * @param memAuthInfo 会员认证信息 */ - public MemberUserDetails(MemberAuthDTO memAuthInfo) { + public MemberDetails(MemberAuthDTO memAuthInfo) { this.setId(memAuthInfo.getId()); this.setUsername(memAuthInfo.getUsername()); this.setEnabled(GlobalConstants.STATUS_YES.equals(memAuthInfo.getStatus())); diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java similarity index 69% rename from youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java rename to youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java index 8d8334c3a..fab02441b 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java +++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java @@ -12,29 +12,53 @@ import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** - * 会员信息(openid为主体)加载实现类 + * 商城会员用户认证服务 * * @author haoxr * @since 3.0.0 */ @Service @RequiredArgsConstructor -public class OpenidUserDetailsService implements UserDetailsService { +public class MemberDetailsService { private final MemberFeignClient memberFeignClient; + + /** + * 手机号码认证方式 + * + * @param mobile 手机号 + * @return 用户信息 + */ + public UserDetails loadUserByMobile(String mobile) { + Result result = memberFeignClient.loadUserByMobile(mobile); + + MemberAuthDTO memberAuthInfo; + if (!(Result.isSuccess(result) && (memberAuthInfo = result.getData()) != null)) { + throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg()); + } + MemberDetails userDetails = new MemberDetails(memberAuthInfo); + if (!userDetails.isEnabled()) { + throw new DisabledException("该账户已被禁用!"); + } else if (!userDetails.isAccountNonLocked()) { + throw new LockedException("该账号已被锁定!"); + } else if (!userDetails.isAccountNonExpired()) { + throw new AccountExpiredException("该账号已过期!"); + } + return userDetails; + } + /** * 根据用户名获取用户信息 * * @param openid 微信公众平台唯一身份标识 - * @return {@link MemberUserDetails} + * @return {@link MemberDetails} */ - public UserDetails loadUserByUsername(String openid) { + public UserDetails loadUserByOpenid(String openid) { // 根据 openid 获取微信用户认证信息 Result getMemberAuthInfoResult = memberFeignClient.loadUserByOpenId(openid); @@ -61,7 +85,7 @@ public class OpenidUserDetailsService implements UserDetailsService { throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg()); } - UserDetails userDetails = new MemberUserDetails(memberAuthInfo); + UserDetails userDetails = new MemberDetails(memberAuthInfo); if (!userDetails.isEnabled()) { throw new DisabledException("该账户已被禁用!"); } else if (!userDetails.isAccountNonLocked()) { @@ -72,5 +96,4 @@ public class OpenidUserDetailsService implements UserDetailsService { return userDetails; } - } diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java deleted file mode 100644 index 7a6e6498e..000000000 --- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.youlai.auth.userdetails.member; - -import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; -import cn.hutool.core.lang.Assert; -import com.youlai.common.enums.StatusEnum; -import com.youlai.common.result.Result; -import com.youlai.common.result.ResultCode; -import com.youlai.mall.ums.api.MemberFeignClient; -import com.youlai.mall.ums.dto.MemberAuthDTO; -import com.youlai.mall.ums.dto.MemberRegisterDto; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.springframework.security.authentication.AccountExpiredException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -/** - * 商城会员用户认证服务 - * - * @author haoxr - * @since 3.0.0 - */ -@Service -@RequiredArgsConstructor -public class MobileUserDetailsService implements UserDetailsService { - - private final MemberFeignClient memberFeignClient; - - - /** - * 手机号码认证方式 - * - * @param mobile 手机号 - * @return 用户信息 - */ - @Override - public UserDetails loadUserByUsername(String mobile) { - Result result = memberFeignClient.loadUserByMobile(mobile); - - MemberAuthDTO memberAuthInfo; - if (!(Result.isSuccess(result) && (memberAuthInfo = result.getData()) != null)) { - throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg()); - } - MemberUserDetails userDetails = new MemberUserDetails(memberAuthInfo); - if (!userDetails.isEnabled()) { - throw new DisabledException("该账户已被禁用!"); - } else if (!userDetails.isAccountNonLocked()) { - throw new LockedException("该账号已被锁定!"); - } else if (!userDetails.isAccountNonExpired()) { - throw new AccountExpiredException("该账号已过期!"); - } - return userDetails; - } - - -} diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java index b9b113002..e0319f6e2 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java +++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java @@ -14,9 +14,7 @@ import java.util.stream.Collectors; /** - * 系统用户信息 - *

- * 包含用户名、密码和权限 + * 系统用户信息(包含用户名、密码和权限) *

* 用户名和密码用于认证,认证成功之后授予权限 * diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java index bf0a24a85..f7b056a65 100644 --- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java +++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java @@ -19,7 +19,6 @@ import org.springframework.stereotype.Service; * @author haoxr * @since 3.0.0 */ -@Primary // UserDetailsService 默认的实现,其他需要显式声明 @Service @RequiredArgsConstructor public class SysUserDetailsService implements UserDetailsService { diff --git a/youlai-auth/src/main/resources/bootstrap-dev.yml b/youlai-auth/src/main/resources/bootstrap-dev.yml index 119a4fc90..c6fce4870 100644 --- a/youlai-auth/src/main/resources/bootstrap-dev.yml +++ b/youlai-auth/src/main/resources/bootstrap-dev.yml @@ -2,6 +2,9 @@ server: port: 9000 spring: + mvc: + path-match: + matching-strategy: ant_path_matcher cloud: nacos: # 注册中心 @@ -17,5 +20,4 @@ spring: data-id: youlai-common.yaml refresh: true username: nacos - password: nacos - + password: nacos \ No newline at end of file diff --git a/youlai-auth/src/main/resources/bootstrap-prod.yml b/youlai-auth/src/main/resources/bootstrap-prod.yml index e6e30c513..d5ad70eec 100644 --- a/youlai-auth/src/main/resources/bootstrap-prod.yml +++ b/youlai-auth/src/main/resources/bootstrap-prod.yml @@ -10,13 +10,12 @@ spring: # 注册中心 discovery: server-addr: http://f.youlai.tech:8848 - namespace: prod-namespace-id + namespace: prod # 配置中心 config: server-addr: ${spring.cloud.nacos.discovery.server-addr} file-extension: yaml - namespace: prod-namespace-id + namespace: prod shared-configs[0]: data-id: youlai-common.yaml - namespace: prod-namespace-id refresh: true \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java new file mode 100644 index 000000000..d2edc4ae4 --- /dev/null +++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java @@ -0,0 +1,48 @@ +package com.youlai.auth.authentication; + + +import com.youlai.auth.authentication.captcha.CaptchaParameterNames; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +public class CaptchaAuthenticationTests { + + @Autowired + private MockMvc mvc; + + + @Test + void testPasswordAuthentication() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth("mall-admin", "123456"); + + // @formatter:off + this.mvc.perform(post("/oauth2/token") + .param(OAuth2ParameterNames.GRANT_TYPE, "captcha") + .param(OAuth2ParameterNames.USERNAME, "admin") + .param(OAuth2ParameterNames.PASSWORD, "123456") + .param(CaptchaParameterNames.VERIFY_CODE, "123456") + .param(CaptchaParameterNames.VERIFY_CODE_KEY, "123456") + .headers(headers)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").isNotEmpty()); + // @formatter:on + } + + +} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java new file mode 100644 index 000000000..8aac9bb9d --- /dev/null +++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java @@ -0,0 +1,45 @@ +package com.youlai.auth.authentication; + + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +public class PasswordAuthenticationTests { + + @Autowired + private MockMvc mvc; + + + @Test + void testPasswordAuthentication() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth("mall-admin", "123456"); + + // @formatter:off + this.mvc.perform(post("/oauth2/token") + .param(OAuth2ParameterNames.GRANT_TYPE, "password") + .param(OAuth2ParameterNames.USERNAME, "admin") + .param(OAuth2ParameterNames.PASSWORD, "123456") + .headers(headers)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").isNotEmpty()); + // @formatter:on + } + + +} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java new file mode 100644 index 000000000..731b9624c --- /dev/null +++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java @@ -0,0 +1,43 @@ +package com.youlai.auth.authentication; + + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +public class SmsCodeAuthenticationTests { + + + @Autowired + private MockMvc mvc; + + @Test + void testSmsCodeAuthentication() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth("mall-app", "123456"); + + this.mvc.perform(post("/oauth2/token") + .param(OAuth2ParameterNames.GRANT_TYPE, "sms_code") + .param("mobile", "18866668888") + .param("verifyCode", "666666") + .headers(headers)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").isNotEmpty()); + } + + +} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java new file mode 100644 index 000000000..daf994408 --- /dev/null +++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java @@ -0,0 +1,43 @@ +package com.youlai.auth.authentication; + + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +public class WechatMiniAppAuthenticationTests { + + @Autowired + private MockMvc mvc; + + @Test + void testWechatMiniAppPasswordAuthentication() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setBasicAuth("mall-app", "123456"); + + // @formatter:off + this.mvc.perform(post("/oauth2/token") + .param(OAuth2ParameterNames.GRANT_TYPE, "wechat_mini_app") + .param(OAuth2ParameterNames.CODE, "codeVal") + .headers(headers)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").isNotEmpty()); + // @formatter:on + } + + +} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java deleted file mode 100644 index c156bbf4d..000000000 --- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.youlai.auth.security.authentication.password; - - -import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.UUID; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -@Slf4j -public class ResourceOwnerPasswordAuthenticationTests { - - - private final String clientId = "mall-app"; - private final String clientSecret = "secret"; - - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private MockMvc mvc; - - @Autowired - private PasswordEncoder passwordEncoder; - - @BeforeEach - public void setUp() { - // 注册 mall-app 客户端 - - // - String encodeSecret = passwordEncoder.encode(clientSecret); - RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId(clientId) - .clientSecret(encodeSecret) - .clientName(clientId) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrantType(AuthorizationGrantType.PASSWORD) - .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) - .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") - .redirectUri("http://127.0.0.1:8080/authorized") - .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out") - .scope(OidcScopes.OPENID) - .scope(OidcScopes.PROFILE) - .scope("message.read") - .scope("message.write") - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - - RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId); - if (registeredMessagingClient == null) { - registeredClientRepository.save(messagingClient); - } - } - - - @Test - void testLoginApiForOAuth2PasswordMode() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth("mall-app", "secret"); - - // @formatter:off - this.mvc.perform(post("/oauth2/token") - .param(OAuth2ParameterNames.GRANT_TYPE, "password") - .param(OAuth2ParameterNames.USERNAME, "admin") - .param(OAuth2ParameterNames.PASSWORD, "123456") - .headers(headers)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()); - // @formatter:on - } - - -} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java deleted file mode 100644 index 901c0c880..000000000 --- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.youlai.auth.security.authentication.password; - - -import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken; -import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.UUID; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -@Slf4j -public class SmsCodeAuthenticationTests { - - - private final String clientId = "mall-app"; - private final String clientSecret = "secret"; - - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private MockMvc mvc; - - @Autowired - private PasswordEncoder passwordEncoder; - - @BeforeEach - public void setUp() { - // 注册 mall-app 客户端 - - String encodeSecret = passwordEncoder.encode(clientSecret); - RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId(clientId) - .clientSecret(encodeSecret) - .clientName(clientId) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrantType(AuthorizationGrantType.PASSWORD) - .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) - .authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE) - .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") - .redirectUri("http://127.0.0.1:8080/authorized") - .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out") - .scope(OidcScopes.OPENID) - .scope(OidcScopes.PROFILE) - .scope("message.read") - .scope("message.write") - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - - RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId); - if (registeredMessagingClient == null) { - registeredClientRepository.save(messagingClient); - } - } - - - @Test - void testSmsCodeMode() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth("mall-app", "secret"); - - // @formatter:off - this.mvc.perform(post("/oauth2/token") - .param(OAuth2ParameterNames.GRANT_TYPE, "sms_code") - .param("mobile", "18866668888") - .param("verifyCode", "666666") - .headers(headers)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()); - // @formatter:on - } - - -} \ No newline at end of file diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java deleted file mode 100644 index bc9c112ae..000000000 --- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.youlai.auth.security.authentication.password; - - -import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.UUID; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -@Slf4j -public class WechatMiniAppAuthenticationTests { - - - private final String clientId = "mall-app"; - private final String clientSecret = "secret"; - - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private MockMvc mvc; - - @Autowired - private PasswordEncoder passwordEncoder; - - @BeforeEach - public void setUp() { - // 注册 mall-app 客户端 - - String encodeSecret = passwordEncoder.encode(clientSecret); - RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId(clientId) - .clientSecret(encodeSecret) - .clientName(clientId) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrantType(AuthorizationGrantType.PASSWORD) - .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) - .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") - .redirectUri("http://127.0.0.1:8080/authorized") - .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out") - .scope(OidcScopes.OPENID) - .scope(OidcScopes.PROFILE) - .scope("message.read") - .scope("message.write") - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - - RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId); - if (registeredMessagingClient == null) { - registeredClientRepository.save(messagingClient); - } - } - - - @Test - void testWechatMiniAppPasswordMode() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth("mall-app", "secret"); - - // @formatter:off - this.mvc.perform(post("/oauth2/token") - .param(OAuth2ParameterNames.GRANT_TYPE, "wechat_mini_app") - .param(OAuth2ParameterNames.CODE, "codeVal") - .headers(headers)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()); - // @formatter:on - } - - -} \ No newline at end of file diff --git a/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java b/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java index 8b3f71005..02b1b4e35 100644 --- a/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java +++ b/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java @@ -65,7 +65,7 @@ public class CaptchaHandler implements HandlerFunction { String captchaBase64 = captcha.toBase64(); Map result = new HashMap<>(2); result.put("verifyCodeKey", uuid); - result.put("verifyCodeImg", captchaBase64); + result.put("verifyCodeBase64", captchaBase64); return ServerResponse.ok().body(BodyInserters.fromValue(Result.success(result))); } diff --git a/youlai-gateway/src/main/resources/bootstrap-prod.yml b/youlai-gateway/src/main/resources/bootstrap-prod.yml index 875ee156a..781214814 100644 --- a/youlai-gateway/src/main/resources/bootstrap-prod.yml +++ b/youlai-gateway/src/main/resources/bootstrap-prod.yml @@ -10,12 +10,12 @@ spring: nacos: discovery: server-addr: http://f.youlai.tech:8848 - namespace: prod-namespace-id + namespace: prod config: server-addr: ${spring.cloud.nacos.discovery.server-addr} file-extension: yaml - namespace: prod-namespace-id + namespace: prod shared-configs[0]: data-id: youlai-common.yaml - namespace: prod-namespace-id + namespace: prod refresh: true diff --git a/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java b/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java index 31b42f995..d3b9794cf 100644 --- a/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java +++ b/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java @@ -162,7 +162,8 @@ public class SysUserController { @PostMapping("/_import") public Result importUsers(@Parameter(description = "部门ID") Long deptId, MultipartFile file) throws IOException { UserImportListener listener = new UserImportListener(deptId); - String msg = importExcel(file.getInputStream(), UserImportVO.class, listener); + EasyExcel.read(file.getInputStream(), UserImportVO.class, listener).sheet().doRead(); + String msg = listener.getMsg(); return Result.success(msg); } @@ -177,10 +178,4 @@ public class SysUserController { EasyExcel.write(response.getOutputStream(), UserExportVO.class).sheet("用户列表") .doWrite(exportUserList); } - - public static String importExcel(InputStream is, Class clazz, MyAnalysisEventListener listener) { - EasyExcel.read(is, clazz, listener).sheet().doRead(); - return listener.getMsg(); - } - } diff --git a/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java b/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java index 4a62b8deb..828531faf 100644 --- a/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java +++ b/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java @@ -21,7 +21,6 @@ import com.youlai.system.model.query.UserPageQuery; import com.youlai.system.model.vo.UserExportVO; import com.youlai.system.model.vo.UserInfoVO; import com.youlai.system.model.vo.UserPageVO; -import com.youlai.system.service.SysMenuService; import com.youlai.system.service.SysRoleService; import com.youlai.system.service.SysUserRoleService; import com.youlai.system.service.SysUserService; @@ -50,8 +49,6 @@ public class SysUserServiceImpl extends ServiceImpl impl private final SysUserRoleService userRoleService; - private final SysMenuService menuService; - private final SysRoleService roleService; private final UserConverter userConverter; diff --git a/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml b/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml index 3e877af08..6b75c44b4 100644 --- a/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml +++ b/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml @@ -11,13 +11,13 @@ spring: nacos: discovery: server-addr: http://f.youlai.tech:8848 - namespace: prod-namespace-id + namespace: prod config: server-addr: ${spring.cloud.nacos.discovery.server-addr} file-extension: yaml - namespace: prod-namespace-id + namespace: prod shared-configs[0]: data-id: youlai-common.yaml - namespace: prod-namespace-id + namespace: prod refresh: true