From 3329910bdd5cf2d085752ee2f155735454f7d6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=94?= <2393584716@qq.com> Date: Wed, 5 Feb 2020 00:31:51 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + sa-token-demo-springboot/.classpath | 32 ++ sa-token-demo-springboot/.gitignore | 6 + sa-token-demo-springboot/.project | 23 ++ .../lib/sa-token-spring-1.0.0.jar | Bin 0 -> 37151 bytes sa-token-demo-springboot/pom.xml | 90 ++++++ .../java/com/pj/SaTokenDemoApplication.java | 18 ++ .../java/com/pj/satoken/SaTokenDaoRedis.java | 91 ++++++ .../main/java/com/pj/satoken/StpCustom.java | 28 ++ .../src/main/java/com/pj/test/AjaxJson.java | 163 ++++++++++ .../main/java/com/pj/test/TestController.java | 83 +++++ .../main/java/com/pj/test/TopController.java | 57 ++++ .../src/main/resources/application.yml | 44 +++ sa-token-dev/.classpath | 32 ++ sa-token-dev/.gitignore | 6 + sa-token-dev/.project | 23 ++ sa-token-dev/pom.xml | 31 ++ .../java/cn/dev33/satoken/SaTokenManager.java | 78 +++++ .../java/cn/dev33/satoken/SaTokenUtil.java | 30 ++ .../dev33/satoken/config/SaTokenConfig.java | 65 ++++ .../satoken/config/SaTokenConfigFactory.java | 128 ++++++++ .../java/cn/dev33/satoken/dao/SaTokenDao.java | 61 ++++ .../dev33/satoken/dao/SaTokenDaoDefault.java | 62 ++++ .../satoken/exception/NotLoginException.java | 21 ++ .../exception/NotPermissionException.java | 33 ++ .../cn/dev33/satoken/session/SaSession.java | 129 ++++++++ .../dev33/satoken/session/SaSessionUtil.java | 44 +++ .../cn/dev33/satoken/spring/SaTokenSetup.java | 21 ++ .../dev33/satoken/spring/SpringSaToken.java | 48 +++ .../cn/dev33/satoken/stp/StpInterface.java | 14 + .../satoken/stp/StpInterfaceDefaultImpl.java | 16 + .../java/cn/dev33/satoken/stp/StpLogic.java | 306 ++++++++++++++++++ .../java/cn/dev33/satoken/stp/StpUtil.java | 131 ++++++++ .../cn/dev33/satoken/util/SaCookieUtil.java | 80 +++++ .../cn/dev33/satoken/util/SpringMVCUtil.java | 34 ++ .../main/java/com/pj/SaTokenApplication.java | 18 ++ .../src/main/resources/application.yml | 20 ++ sa-token-doc/index.html | 11 + 38 files changed, 2079 insertions(+) create mode 100644 sa-token-demo-springboot/.classpath create mode 100644 sa-token-demo-springboot/.gitignore create mode 100644 sa-token-demo-springboot/.project create mode 100644 sa-token-demo-springboot/lib/sa-token-spring-1.0.0.jar create mode 100644 sa-token-demo-springboot/pom.xml create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/satoken/StpCustom.java create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/test/AjaxJson.java create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java create mode 100644 sa-token-demo-springboot/src/main/java/com/pj/test/TopController.java create mode 100644 sa-token-demo-springboot/src/main/resources/application.yml create mode 100644 sa-token-dev/.classpath create mode 100644 sa-token-dev/.gitignore create mode 100644 sa-token-dev/.project create mode 100644 sa-token-dev/pom.xml create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenManager.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenUtil.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfigFactory.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefault.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotLoginException.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotPermissionException.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSession.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSessionUtil.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/spring/SaTokenSetup.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/spring/SpringSaToken.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterface.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterfaceDefaultImpl.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpLogic.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpUtil.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/util/SaCookieUtil.java create mode 100644 sa-token-dev/src/main/java/cn/dev33/satoken/util/SpringMVCUtil.java create mode 100644 sa-token-dev/src/main/java/com/pj/SaTokenApplication.java create mode 100644 sa-token-dev/src/main/resources/application.yml create mode 100644 sa-token-doc/index.html diff --git a/README.md b/README.md index 68a4c33..63f289e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # sa-token 一个好用的JavaWeb权限认证框架 + +在线文档:[http://sa-token.dev33.cn/](http://sa-token.dev33.cn/) \ No newline at end of file diff --git a/sa-token-demo-springboot/.classpath b/sa-token-demo-springboot/.classpath new file mode 100644 index 0000000..cd04a79 --- /dev/null +++ b/sa-token-demo-springboot/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sa-token-demo-springboot/.gitignore b/sa-token-demo-springboot/.gitignore new file mode 100644 index 0000000..ae57b0f --- /dev/null +++ b/sa-token-demo-springboot/.gitignore @@ -0,0 +1,6 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ \ No newline at end of file diff --git a/sa-token-demo-springboot/.project b/sa-token-demo-springboot/.project new file mode 100644 index 0000000..4772c71 --- /dev/null +++ b/sa-token-demo-springboot/.project @@ -0,0 +1,23 @@ + + + sa-token-demo-springboot + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/sa-token-demo-springboot/lib/sa-token-spring-1.0.0.jar b/sa-token-demo-springboot/lib/sa-token-spring-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..cce96b09433139309af27f3136f3462bb2e030dd GIT binary patch literal 37151 zcmb5VW0Yo1(k@)Ktu9w}*|u%lwr#V^wr$(CZJS-j>1SrX@661aGv__`y7#XgnYmYF z#1&U$M9N75gP;IFKtKRY0Sn6k{L>Ew00clgfo_p;_h|nJ2tZB}9HQPA=orwog+|COgkFC+L2XnQCMZnQ*`xMXft6O-vR7TH?l> z_sr}&V#fNCxy;FRjoz41mnSXsu+e76h0<;*~&ng7|`$0Tvm*JBg z4!TBUB}Qb6RK+OlH)o`HV(0{&$@4=6vo`2Ya`N`U@uJb&L3V%UE*9^(JO zmRRV!=r<`y%VP0k@HC8lg+7M~?e0p`a=X#v(s#vNim~)wo46(iF-ez}=|Yy8Q-KjL z4{I30C$fO#<^7(a1J3+_p1uT!>fm1(G=hJ)=6vw#oUzPxLNlf-Yzhj1mXw5qM&yok z4}L^dk0)WaJtc*FqP9{muO?9-JIc=77*mr<#s@=-;9OCA9ms7F^tFb+z+2lx@5DD#hwMMw2iU%Y(f$Ks~#a;S+v2=@n+f?KrKIV2q3`7|`@kqR!2X-s{#a z-f3BL5B&TZ#{$patckvsIbD0bJ%(?k-N6_Ya^IEJ_nI#pN!oLsuuV%{Ej~ukk{?V+ zBrGmB#t$6RwJ99;XGW=eAUxYn55`J2Y7sPTg$IV#)N1#UAo_6UhKgw&NWG$ zJrjf<;(HU1tt*LZ`T*8f?Sx&F+si2xv~u^~do+QlxYQK8KlguTbJ;%xg1Ri5EG;Ky z-NIN4@*CmC<(v_@ysS&rO{OW2B2CSVYL`COKmHAR9I;}6FaKE25y(GV&%Zk~)&Jdk zgp5t}ovoby?$=3*ma@q52;42QWSV5Ms7ko^W)p_;xcT^{`0`X1RAqYy!8-bRPU5aD z;olNSNx9*74ZP!Q&o1Oe9b(cAT@4M4rqh{i5AQS2IRGYgxWaz=PA#P;!tN#_Ev?qL zP#y=x*+-i5ItRz;FQZujKZL^w?0YA)Xm9tkzg$lEF5_AIXMJIT1es>6yT@WM*BPMt z>!Jf>ehG&hSJtT>HY*~*IHvT_7}J~^U&YIBYECXPs%eo%jmFC}T^fhzF(es6RHeHk z3L;ozY2*seP0#xHf5dCP2jL0=Sx@Pm&LapX9GajEVV{_ZCN{sw_`f(AO=|0m-o+E_ zXq%31vr>f^71Qs9=`3W|I(F{L-J8vCD2`uP^jU94@52dx#1mq@(eqg&>HSEq5E;LS zFgTq*q#%Jfl=F(G_J~k?EUtZ?`ry>r7wtzQv1l19e~NuP>$_MJJ2FBihc%B5d@MX4caSxKPbu)&1IFKs|&$x)#;z= z-s+#>#IPwqE3A48^jJ{&i0}S*8fdF?@YuOF66z6QQODwlI8brDpl!W5uG&cvgXGycRBx!Qf!Vq8u& z!A$w;bA(F}DKK*DR#2gD>m6|t%t&q=}*U9bu%00D`B&*!(A%kP(} zK54+BR13mnyHI$G##W}CdsGr%wAwau(!t~6=K_2I(rn9)67x#~2h=iFivXJvEdsO! zvsSJbYS>J;|92(k;e;bbl(*dZ*EK2_7rXs6!%n$=73aj#l0=c3ACw)=nDN=1%DWG1 z+lG%1m#@X=mDVM3)y4Ff^~p7&l$Z~1Bc4or*Vf^&);<1)1~poM*_fksE%zagMQbdk zG*L!z4u(6Bkti03>dL;~q=6uz)`E&oGByYe{ha zj-&7VmVqa;Aq33oQr=y@@zFjc>ResH%__F$%V+Qs&^yMz%W(RhEbtF#{?DxNKhiTtC%gYb&j|mMS^YIc(aBEP$=vE6+)UY862Mr4HjxaHK z)grIiLcF&gYE>myNyADSmX^i*G7>4}5DBf{X4C367w@s+dno&0g8dPPdjxMc=AO;b z72Aas`&O_%Bh5YA-Q;?6jQ9KNmDP`(4f4^fRjKZ=oGUeJDI>)4MqJXi9ky$i1-HY5 z+n*6D#5EdNum)R2T6bqX2~|}v-r(M4s?w}A^F|~W2#w6GPJxwrL3@5SV=S2|v&~f1 z7^ub^gA*}hFgK)|qf3LlkdBxxssZZ_-Me{C>PtbY~I451>u%L!c@x<$)$WL>*uBb~|lEj1gmtQVa35N(c5IdK|UNXV8kP7=dgr#Oc{%~Xy>)^O{a~L^wjQ$|E$J8eCu$Dgoz4P$fj27~mK>JOW<-Tp*>k2~LTn0?>UVnfQx_Fjq$58nlU;gGDaiW@_HhEjmf+*nR>znHihi;+Vs9zaUla1H{DtXa%XKC_logMY@!}l3^$wX&L;}k zdO^W>KhuD9sf_H?lXSNb2Nb3f!XD@@5Y4=!-*Yj1wj3`09jC}IyR#LD4*|{t09r8* z!&w;awG86fL&WW0`NKfs39S*Xr>`{7v!~#Sb7(}pK>@}FPDTY$s=yp(C9fPdLC#vd zG(1HH4U4;D3yr5&A~L~DUI@k+q|_dKBT}qC8srbD&Qdw0!L3(U8QWeZ3(+3{VH?d1 z`Jv~)wjup(78>2RbQN}!>;PRcwy^nXf(B_R?_33wJx7kHDu^ky6TPvNlcC8xXN_ri zBLkL!@Ynn5a=jYug8{;l9k3UX0jxX2{IWs#8f^}2`RL1Qyw=SZj-ydOiN}>-4@;CU zPELT8Sf+WMgKF}duF2Kb!99RptP{4GNQ`qu#n&FwChn5Fkx6WVC^leJj^(YLJJ6C& zo6P2#*x1spO?7VXoSMkCo`s$T!=?wX>9fjCpW5e^3Y>V6wq$=U{O(oQVR0V1m}%+t z{R;@G$9{~3`~g8wn12qIKlA@J!y)*;q1s;|vPyMJc0mrscRT(Qo|R`$pdB3dGwe+y z-vH=MLc%pIX`aTKRbbI5e>Bt!b8$2k0|Fty1ejUjnZP%yOZ$1SXV=__dTQVSws3a0bazhNg>g}c-iewQ$ zxsT8Y{%_ibcGHzEOu~AvKY43?u(Fp zHK`y6nZqAnO5hMBtWWhsh0RQ%^?~7Dl!_6U0ti3O8LEVeStE)Sw`Tg=af% zr=qbeyogCwmQ$8Q#%gYhg_Jn#E)Yk?Mee90bWE}?fioq)2y5|e?WkH=jIyeSP$slL0bYx zc>cUH3mq=+0M>tPuP;!kD8H{(=Z2k^HVASy2QS*~mIA!G$ktgSAb~qozH$Vux++M( zn_4gT4H*XU4`hN@8-qDt37&R2MK)lT;pKa0TvrjN{IRaCDe!>3zE7H7k^csxfI`cm z62Nexz5A`k7AEO1FUy-dMa^&`pLZ>MaXe~Gw{OA>G;fzVZGUgJ?Y280-aJ4(TsI+q z?1%$SXl%a-^^KqzWZL*6x-Fpe?DIj>)@vo%RjJ4#`l6TLW~6|m9?gH9y)m@B!weI7 zA~2zD6U{g_g{Y3+9=*U|S`MD(*h4e+U0`q%LGP46zv~&K2g50At~$?*{kF3Dlg3!A z)`r$j%2oZzLF8GC7QYFOkf~%nWu+>*X?ZVR#~pQV*IKrWxniZ;ya=>ja1&JevZV15 zA|io`U^Q;l#}mv5li-Rqd!0Ko11ZB#@wt3eE`TigAE9%U(PNDS~HaBnOZSN-MF`wp@^2vjX{+$N>T6?TtY)T?k+mdL&@%_>SE^6w$Zu_k;n=(>{x`tIlu(2e1vomsQP^(}h?EW@N$maB5g9UPr) zawk@+9OJW3Fta${)g{c>NoX#I8^#dX4&Tst(43&rN9lqIrh_VMkA>yTv3*@s`MHhK zk^{MgYZe&VuO%FfT*T*dnwMjyM|}dYSB~+p@Hws{TRV;Xzpwj>Et-Z zc+WinPqtOE$FSxFg*GSJyh)IY2YgtD3KO?#H~vFh_~|r}MAxnV^c8scru+1DP7tybZS47@#JbcW&pn*HFE~kRM+3m#5~cy>(c&EXMjJO|JQlx-_OKY3;s1? z(Eq<~{9h5oTCPq4m>;3)X7@{f{4iEBj)5?O1&|;B4?m-8tgfxA%h>grI4pQI#H=is z#ljHr>bEPBV^Ys9O?JR8%E0aR?RLgo)CrZ~E`@mA^)vEB>7+bK@*_S}fgDOZ6NsWp zLm?qS0=MDnrJNephhVcC%F{j@6akp(MaRVPQ@eY%tq#p{WXaMJuvhm?MoY%jy?s;X zw_4}hln%e)n zm$+-Et*yQ`*}Zl2zx*pQ1oa)_kD68gJpWtGL;rR#PV`^YEM;qIZus|nBTF69O-U8? z>xRlbAuWXu2?+y2(qAB696~csn#>wl8%VqqBtVXthatuYKTX_}w9%}|Vs5!gSrc7O zQ?toA5490CoXnY$%*I*wf=1=SX3O%f=XuM$!N{*smv_6g!g1KBirLCVFa)e z1s(eqV)8nYRrIQhQiZ9c(#as^hpkLeXhz4vz>n}RwH&~l-o>qDoeKBah-X&Y^|p;` zN$5m42bS%Qk?o&yYjIgOGCvLPzEu3R!8ZT6kKWBhEj2`#|)ghzt_6?-*R zcaZto03`UcNI%FBQ|A#QH?)ZaIj;`UEKhQ&mq^|!`*0~S6LvgiQ*gtIK#pB97qHiv zn-*l?iSt)9#4Sf`#jHOQLjXMd6W&Pb_>7FJ8 z-|q;BxkFUpY`eSAy6DQxc6{AZ(&TqYEm8%H7O~x4Ar3*DK}Mx9{2VMaK*K}TTv zqTbILJX=;aZ2PQm^rMWmR4uX<6`R^s<6AK(n>&2DHww=i&mqqB)v28 zLvf*Ub^ARt!l8}OJVGn=*C3(2tH*AF-a{C- zZu3u$cpww&7dS#dqd5&NV;V20Ne$E}ceYLP!4`}uwv6|$7?J`glG?#53#rjN7X~|w zf+(2;>tPa!+0FL1AY$F*J!2abrah%MqdjrhH4Ow^1c1hzcJNnrEcbajbG*dQ>WSr4 zi8p@!vMRlaQ~%^gem(%aCr?@-K+Dk-`E>(*tB;J=CkiT1epDQSe1}59B$a={VTri` z4l2}e35&;Qk0B~XNr^SJ_>px|^Qtjx^pVXrPcx&lT{?LWJ)Ln71;dy?5Sxd?Y>#3n zLoi2t7QNn48Q15uGPGQCiuYWss`2o!BnPgXi!3qSBkR7am+whvEIS!^%;`&A3>TPY79z7yDzF zs=`sovfH0P&@%;cG}cK1J|_Do^)k1FNH;6z|ykop@upM;caB*H=0Wb=DYFKK`t zjuliKJuLQEJ@)CboQiUr+LAvB7J6+Xe?a`Cg4q}6BDoO(G#|o~`atw01U@`t8-ZEw z5kK9;Z|G)93NBImEGD$QS4InG&wMn<6vb_C-zM zfSiPvjMH zTc4QiT1WnoKbU8>Ea47%9X57wSnjBqkpo#?GvVEcj~@_1Hq{Y7j;R;~a^&F~3iPcK->n9X#tY54^c+4RaSAh+l_X~d zsWboFyasRpx^025KJk&h4IdTiZPQ@B#D@d9kFft@!?t*Xf_nvx^<4O(KvX+2(HPHr z`D(7CtKFAgz0mauK_V>h?gG&_*+7fBm#O}~D|t`#QB7!%BK9xBRXO!1h+-z87q)(S zc#4%c7ueS!ANR>cHD4C@I%%_2w6o z3Jvu8$Q>Z<>kGk|<*Kf`*z`6_Na(Lto@!$j|Gr!LPQXk3)PqL~f4ZAvM|*B>nLI>u z1^&>AJTO6L!Uz` z#eT<-aS0d*;B6TF_`|5o3Fw;vC?m|}Rya%QPtQUk2cRXSV0)lSZ`^^B`s(GdMGGw_RkH<&*}~fgqi7iM z*8AEwu4aqM<@0m&c(p9Q9oJFM(WU|My60o1-q>Z<++~PYQRi*2Mk>jaOA7Gk1c)C4 zhLaTuF*2js?-0lDZD+tZ9lYS@6zJ#o0;QBVr9FPM_bX%jwB${!r!oqpOA>>haRbqz z%3b3WTtad14zGevdvG7w-s~%>u}mU@9uoW>i@|#)(*AAr z9%66OEBZd6<$77sL2dLc4l_#O_Yll63cd36R#7lGBUWA%BlB%EOpMBY?rOUWb&E}w z%`i>9s+-r%S|hA{hTRmJa{QkkRx)v*m<8#6kZ#9z+=qI&dtt2*vrZHf95W0=!?5>$V2p9W?|l^eWCmft{(gq zQbIm4)ZVviEa8y5IB>KpaQ3)!2XB3C+zu0rrS!{F^lnDrQT9ZR_s{|!^+M-+*jJ0L z3u_V#@~7~7M3_&xnV4I|@^V*_Lg7MX_;(}|R}}eyfE(l~WFj;3FX(~5K)O!&-V@Hl zVg)(OH>RUcZt&*U>b-}iJuhVAh}5H0((xgb^Ks$?Mh0czKj4bFh();DrDarG*t1zx ziCWz3QZ>v_s`V=>B0>P_>nHeluBH4RZtg#iNLjvV(Ip*OWfU~n7)Tcrj&(0K>BgiP zHKF{J73ZrU6nuMFTIRH*WWn4{rE+-g*_29dV(wkSPARLTDoML~^}UiR%v0Yr#MWIt z*b!0XNz8{8-xkL}rLAqg-1ffSS`QU&! zh}Eqt%vd-_sSKobh2`B;irS#uXSrZp2u11Kh*qG{cIbF;JZZeog1yaHDG9iiGK901 zUM4o`nti~H`D~f=?#OuU7%tnk^UqfgaeJFiJ)vLAYSzZ~raff+gXBOg|AzI4sFMPD-pKuHM_$4Ax*R zzo3ttROA~%=K=#AewoNu;Tyu6ei!K)9qE%%`Z~Q1qJW{N($-oDDlAlIB&#o_a1kDH zw`p2(r8=_E9YplZuxqwXs_8 ziLqt>Y>Z&Bv_?_(n}K=va$p&4sTkV~c5Ce#j&a!x^*sO7E(3`c|Ma~4pj=RIGd{NW zekAppSoRD0Axrc5%o6AqGrS~Z&~;7E!@Et-4QC0p#4GfCSirr8jLI-P-gH`gk*-{0 zra7wTlk?Su!bn8Be%`};Ya*q(1%ayogQ%?di^=J-_)=qj^+;F__r|xdU9_!Zu|X6% z3MH$B`p~Amc26z^w>aU?-4TGU+ob!Lcme!Y_@GOySg^Yr#dt6iY;J4N8K8NPvd(JQ z3L>o+wpU7itz;*f_u#^x5EMJdN%=+R8rO|BS&O1dS2G_HC9>XS7>4EM{kmCP78F7# zv#_(WkoCkKz(}w%*x>SO`FR#ZCEYyJLC?-VHhX+^(br?7&H&;Br^lO?_q{HToEp52 zK9`*=^Orw%;74`5S5vhuD_0QWcoZ&)dKO}{N>{vJ_~w{VnUlZPR^MX0y%CLT$-a16 zYbw+@ROKGXbYrEEVbz2$=eXYBh{XGN{a9(#p*H*2pSPDSXEJlL*O4`!oR+fN;TUAx z`+R(Ue=Z}*f7t9xKr&$Xh~KA{(}SSkhbF@jj_K8wB{G+%vm<7|V|J#uOI-ricJ0wF zW^UD#iDn)JdBQD9>AljIaIyclvMG&sE(aSTw2)isgpc)qi=a@3mXO(6~;p%Y*D zBg6TJmLXZ$IQP-mvaH}4zANZY^MSo@MS6{;W7`Xp^(WRWB3Ai+B*v)T8Y%_w3g-#r zhJ?|V6w!lb1J(6}rKB=7>^|%HcQwyb?Ub#UVD>JWKyb2 zXyDguzQ&uw9>IblY2h*y*#{pl{Ol5=M1`W_;o?IvwZZAQpUh5Dq@;l^_99J>|2eqV z5)==a-x9{l?UA=V=xfiX-t+U!V;AO$ttC2r*o&4}o>mi_(nMX5Ne?wZtyg$cG#kYG zlz}lh%|ngJSt-tFsMePI7CgEMR;_#I14>Aymo*V;F~>MD5oA+3_{c^71L(& z3mMb$kfkT6Efkl}j1=i3F7dbGWSbr zW*Y^u@f4tn`$xmcvh+O~Mz_5*Yp5DYO|dpAQvFF+E|yf;#BU?M9ZYQ38)7n(Okbel z3hBrI2Fv6QH~i(txZY$CrR6()ru{6>Av12LHj zRy{Vj3Rin7blB;xFUupiZ35Jg!DnsP(?rL=1{=Hv#U!+{p5dJcHrYY0o`E1_TF3 z$!gV{?{jKA!2>n*=v~Aa+|ef!dqWzk5=?mc8#R?No6wsxcs58@dm#Ndi`mo!6*3E- zX~aoHI@_RqPW5QHpJH1hg>{Z*Q4~fT$V9n#1j18cYFgSrX(Pcl4Zy(WZAu|YHpuQ| zuEFFLk`owTRemxTqM(-hdvp7xO+Ler`DWuWXW@4M;h%bbc&Pank?VFug^SJ>w_2)_ zEDivV2>*_k&ysuVB~{O%aS&&*frYJh6D8_l=&;0a;BLSMDkT9gb$F4V-3$)% zqwT03Hb?P6j4m2?KdSu%vdN>-LKOk90%m-ebiBB65DYDGeQ#!E*MSwUTfH_6(r4Avc%7F|w8S-oWA zFTk1nTg=>D&9TE0PahKqDIbF*lP3tsn?fMBTN1ofpDBkp07TLwR!F+hm#iv_ViI$A zVLXvsNq+<+XQ63nLm$yF0K{AN9LP2rvHMXSZGop$z?@i1o|4Q(cQ1%>cn7Fu)ICH8 z_PHNw2Cm(Fd_Dbe(l2+5xYxbq!v~tHR7{wL6mUL{`&!ks&{1 z*+L(p^E7wGghj3?Q7^@x?pB4L0HX|y4ez1V^J?moFk(E?UzFlT$*SGfm#!F^D*Wcb z-yRocEy!&VH}?#hhGZn%C{a8pGVELsR#QN*gU-6spil+P?vGbLPweC3PEWaLIGpSS z^^&iK3Ua|@_Bp`Pw}J6>&y3E@sRn{hG1a{8!fX%MvjXzMuHdn8Pjva0W;vA%A0gN& z(zc-|^vLQn%Ol;jRoDU#R;ENxqj@~Qb;GH`0V~8BCNvM&5@bCi-E21o6$fPJ18h{N zQ)t|w_g#CFL5NdLPB#-Uc$I^j=kc!bayT)xdU8_%)JbV!atJBjuo%%$5%BU+%xbEB z!fYD>ONvahxcLWts0CgdQXOX05$RZzXjiDoBD$db(0sBn$ku7dp;@A&y@P3ET=URs z_^I1iZ9RZ|L+W$dJ6nKN5~oEYI>l{)F+U{rUIm^*#%rT805s6)QelZkojzc7?7{dL-HuP(oMY$7&f2_vw zR80habo4v@R1XfM?O9Xu=^WYAqTsGPsx#$@p7sSOl+OQ3=6>+N^If)#Jxpjb!UOyo zQY{G9$c%C0TG|kX?U8fw)1CF>@e3HLTi`MN;_JC4&V(=83B)(%M$9!-D=9eA$PfuO zJ;v+@!AZf2sPHsec)&~qAkQbzUu4#$zD(ZxPe0b?&wnPf|6W-9^YX8T{lE9)|9_44 z|LC*&{|k%%D1OLs%KWJ&dRyZE8W5GU1K==QB@7!51A~M_5hBI|pnj^g+P@U(I1tl{ z4gj_z8%$+Fbdq9Zv_0;6_44WR1!(Q%|0T6o_|%@a#^xj62UdDnxCy_5l>&xxUL^BYyNV0j z`bWQe4VRqUs(`ITc6}fq!(qHJhJmVATpj}JId<){;_5fpU+U9tA{9LUqa6ExsT)mZ z|7&@U^nawjzw!Y}F{x=8DOqK)=>)G3O|QbUSSk55t2{JAdTI4vs~)22Fd8EuwO9LUCQM}2&F>how{y7(D(zFATa`qTDkmXxw&0M@C;tnx9vkQdZ+Cn zI|z#;vh6w3Hm7T5duRJS^Zl)7MjOBm{HqvWG`AnwuIYk160NO29r1Dzm`0Yac57wN zVp-$Ru86&SflGvLv7(od&OCIvpzT8=cc95;18&O@V%5MhooRql&sGeQ37bGSw?Ly; zjgpTb(S6)Y7p-99%bm)LpGi?UBmMh0(2MD1id2IfeUD8Dzg_|uR{}MS+NxuZWd~} zbE+RXAr{yz+|t3o3vJ%#X14jF)O{jVn!73|r>%*@#e^XHuZ~8DxbU=%M5)>&{%=BqhH+B>E%6ox8lbmRq9Ex?8Ewrw3J{8b%^zWwx0e=o1?)SP5y9)V_$vbQW~= z=2!L3i6$$UVJ!{!!DOqHW>BJ1r8eR$39z(uK)cc+CoeR$FiJ^$c>V2#3w6=3DxgEX zu|OUb*6eT?_>G1oj(nQ_UcRqA>K8I}vXgOF1gsevnNbk355#1Tz0pDmR2^alN~6ik z5R<$;{JI)&R7Nku$?0cQMOfj2rqX7lEHs%8^(D)v;m$_oE+f!_A~cH-P)LoWZr4PC zD|WXYpFb%bvu6h-ArG(OI28Qz`&R+@Hn!K@dLH);oKO~vRWMD*mdo*;T6K#AWDOLa zORccQ)|Q7Ky8?SP+0c}V=3dFx{9c!=4jvAUPx1HL#Ee80AS!>YvRbsB6i-Hfs{!`4 zpxAXBrV2~QUSk@XEty--CB5})2Hk0B8C&~5v*zPS^ zXd2|>{m+;B0AS3rG=ltnN(NLEi{vh$sowwA82PF#St z6Nmav^(G_PL`W=|CkclWe>BB(jWV&NGg=Kp=%(6ZW!UA&-VCCZe2EC!nPhuV0VSH0 zz$VyNCHcyTimrwL`)o(6MLdXpdsgzk3O}jgEUtvSobg4 zzQDuaX}{TGJb@S!`7(9q`XISb!YB}7lm%5s5g}VsZNS_W)-aENkV(!p0X zfV<#K(IxIg9EaxJxz`Ww|8@XNohkW9v2E!BY09x>Pc@Smdle^RFg<(|A7iHBAxT$t z!AfXIk)lf}r3c&$BKJbe#UQyGs@nWDC?Uho9av^L0Y+DF#bm=1sE$s5Kpdx~m@&n~ znWUK&w^({>O=EL(YKY1BAm#3ZVRz-_$lD^WX<)f|Gy~r&j%^IP%k|o0F?7GVNiY%-;WPa(@^g--uSoEx^r-wz$cW+M5ttAFe45;Gn zDMD^MPA1<6|4YK75x|vU)`sV%@2c~PYS8pmot&tS>loQ%vLxxvNNTmj&AJn>!_1Z? zIWOhDZBagGMt)iVwxRVHS_ru=vMO_=1$I(k&1~Y#u%dHjhrGUt><~3yT8IP~cTqi76t4 zM{T0Otu+qk(!654rX_VSKnJD>YkBRj0@CiP6J6-QPDCul%}D7}LVM3}9O9$+ zW@Z>dB1Boe*C$}EnO)hz12@2twiF7BHv%0am<#h~5Oc*+`62Of^|Biiq}3T#alLSL z+|d0oyaoFRNBC3{6J_2YZT?{6pkma7VJ$d<(RImQh^$&tQ`Ae=6LG59;@u>~;_*{q zcG5Lab|y$0ZKP>U!T8$+(!HNztyZkxDA9$R6j<-K&kh}&5sabw)CZ?o_lj&Ydx)h+ zPyCMEW&nZKRwOlu-C{4IkS_{A-Nbu22s|P$C6I1pfbilkCqa0GA09y6nSOs<4?T!G z$T!WehIp3}=?isFImkE5uZDED0_lr-&wZ@%O*?<~yBDwcIww_2;8-QtO1BPXU5};v z3PuXt{V|>y&(tNhMJ8l%=4OSaQrM3j`Ou2Be|7BLFs!AWF56K5iKrjB3f#87u%^EB zyU!t6orx1or_&g%$19<2;2nZQhE7)D=*PhJuM}6v2p%B<%q%@xsn02KyEimE+EnYW z9|==EU*LbS=a#U$MEO5=eaDdgIr(6P_*eG)ADi3$8)5s4NJpz`+AfNq__!2)gwFA} z4=l@1U_&Qr%E#s6BbCkka&Z_@U}!QwA0ghikM#5Efy5h?^-CmaQBLZ}yiq2IFa zl>G?KZhL9IC{nL_VsJdpywU2OX|BLV_5datShjGx-{qfL31{@Tp0c}aUn_-!gMmwx zUb%y4FO)q`KQl9(_I)Vu*EZmAX>wg~0cH^3s$}h8q297F!>!g_#a}l^)@-5)^h-^C zs=AKJS=h?2;+a6_8X3Z+ubb(EgNKEMBXjzKw6<PbWD#sfFU1fli_4NGU^b>0I ze@4gE6eveecGXnL)d-0bzT!s2?P!5i1AJO%x@57{I;sAxj1;|_Ur~R-anvG%>p8Fzn4e#?pQC$Ok}2STUTOlDB? zAT_0hSU0Kz)Je!GAe3t&0W*U)o30Q>w4=DCoK-Sz#c_Ga zhB#%Ib3$w`PwXK^;9|y&r&zVXrAoyu*}|NhkFO$+&yfnCA5U~eW6TLjmKt8hyzGFh z@O^+Rwh$pX(^jGIIdi|+Q=Ocq%k-g(f1^oA9rZSd=VMRoP2y$!Tsix6Uo_l{(iRX% zlY5#IjFsgSLCx`=XqRe0| zHp)_~D7x9~72H-p>kE~V(jRImKoulQMv~OOx63pK zn$*xG3)m9?YXBNiAuYC?F$?)@4KQvNU_D5r~df&xZFP-6t@f_+S_Z zS`jbdAjIE$2dAM{qLZ&&&?F~XhXfoyxmu`+3YyjFqICr#eU-EO=US#K^{rSz<^MP6kdJ$BEgyw0F_QGyq zsX$zyeHY7bUS4uVKeHgEA^#d*;S+_rKx4cvuGg8HZ2r03bl~IFg1k1`-b!j$qiphe z;Xd8P?eWy?24i#$R11{=Q4h>)a7)D{QQ_&HlQCKBYr1kI0vV^y9sF$<4`?f5AY9U; zKj($`1qEEbPke#&es!th>>M*=7wqYc%}9`t8e>;Y-`zCQKusoq8vC#^Ar$9*bVy(1 z9cn*jz)^%hF>cc1n{LxW*Z$m2Q32(|$)w8{$EK-Sq2aV`2QE@-V=Ae#MG zuyY6ga`>jqx88j9Pplra&zmmA)<=pr_fUIZ6HO@S1Hes&z9OOc6h`zxe4o?Xh0thX zji)%n#>u-QT*5)@ufKpxyi{ZHZLAD0OeycIV@5Qgzy ze-I$s(UHSS(+xIN5~qT^#Uq#jS*I6*#AA$VE(onckufU_$EQ6ZKi2ppPB$_JJFTbr z`1zE|xXKE!>}QslR&I4{cwf7EwtRoQpX>k}>zl;M$=R?q6*uv+sy;~Vd~Q2fz^=Gk z-z=UVgTbo~l0_y4?6Dqqt6Qc`%UFBAM=#x?!h|I3L}BS3bG{)kXa+*8UjEKGZn^(2l^)E^eN(5j1ra@0G-soi1m#^!oZ%;i*vl2 zR2WwEkfMI7D+^3mC}M?|Uuw_NVYuO0&H~A<#DIQ2q{buVaN%1zwZifg8nW zkcAh4&cVI|3zs%jAHaB5KIgxG>FujuRR*ds(mxIjO0PAS*7bK(oJBV5gA|iXiBiRJGS?eXe}M9Re?`KP5<5 zF%`C+Obwz*Zu>hxel0^v3Z$Bz4@9> ze;$=a%ms{>=Dx&E+F+2@f=5Vm9&HMf5S$q5)F0jSg302plAGI^u6&hHDqlsoV9-Mq z!pkv<*lhrtfC!tLOB|(@)bQg{Y%1vLmho7Jh9}BL-CGyn6hp*+5B+E^!&6C9x2Zd zL`9Vz@6cglCyLHpN1478_Ojj`rBKEMGLvF>WZrjg%=mh=6B(qtlWD|~(bl9!?FyjQ zx4e?TJd(iz_h88t*@p}P|FJH$&Ngb)m%QDccgkQU;4I#!6v8+tk=z{3sJtTO5YM<6 zOupD3k6y`|+k8#FcQ@E#yKAPmj?=sX#?>(yXtUK0e~)ku4G-Mn_(n8v@N=f|z1>pc zv=7z=>RD!-&l~i4Mz|2|IuRE)9Qa=6;Rej~)BHEyr+%j|@BA7r``IxOj4F%x+vK^_lirqc4+oo(JhLu**lq4}&doX6O`=@| zYh?<)Xv+NK^4P%wX7N`ldgSSf68-jiG|6Hl42p`q&y#>tm{#?HY?9hbgSYf}RH)3MOJ4qVEAHS2W8e7`Z(&*W4fmPjkA{1r zE*!{Jq3b-a!%s)Hli$_L!i5%t?M7ORur$`V=_PZ@9AN2Kl>v;laJ82l43X*DVF@zU zFyq>CM+XKUF&FR6;9GEWHb|^7D~^zH-rQL{<2O>`7MYiQ*F20QHLHFdjuoUu6v8Ho z4t{o7^vQ%lMPfjIP*CIbRczv{$GKTKD1V^48g;~xl(EPKTk(4`dtB4jhQnt|Tvws* z^ZgYOySGHrIrPl%&RbPhHNU4U)dif~LZ%F?o+kFR=x*c1Z89Pf9gK%d%YA7zeMh1g zi0z6PmGu3!DxSBIqjsA$up)}&i$kGY-ml1@ciX+C;Y0Fc$eCrTGts2<`Xb>@1Ym2O z&pE!GPzQaxZC2@`V?kKu?k~5|v#xjV041M*_hGgDL5^DzgdZhskd>ak z*DPns3{}`(_#?eOGHN`L-`DuIjmnO3L)m?n))rj?G<^b94L}VEzL3EDeBRGAJ0B2q zR$l2D9WSI#yS49mc(!~3rwcx$^Mt9YVX(+aA5O)c9BP&;VEfqV`!bPV|pB%%mRZa0BIgG{H*>T6UJ zyy6D%gv&A^u{%0R7{4NtQSyR z?7`zTGC8ucuJA>{wZ?3@iamYUWZ#>;>YLi+Ug5XKjA3)PhQjUbNF01OFYPkFk-W44 z!@#w)jum~-mK&tZo09>qS4nzMMW`=}+{inj`19RfCf651gqUm7$lwEObQA#*yza6? zLos^$@@*akN+R)Fph4;W0m%XRp59@}S7b8OeNqFEfKq2*JMgED^5x2ORdjTWOqEQ` z^vsn^bWALCO!P<38u6^l+D0R%WnB*3D|457Bb6Z}@guuz%YsOHw4mhVG}WWk^RiDI z57r9PKhf4udCaujPNyk<{_NV|QXE^rETl(0h1aCXhxM29t3eHy90f4lnop+upK2@X zf3GdT5|J~qw=#2Z0Ialst`h}m3uJi|?homP`e`i8EYh5%c%?exp>RmAR5VplgaCyAOS&&3M3bGLeI|{-cZq0P9xJ7S1p*r1BHT>&y~NuY}hRYa39$k=kLrHt>kwZpj0-hXze0BqOQrj};V zvz?X6#WwX!4>va{H+uouo$>pGYY(Tur~u~CEgpGA`;iYsEWw8&I{iN zn98-}qQu-pm4q1>w5}~|(&6mXujxfze9KxXJR&A6urfJfh2ylJ?T+}JAr+Mpm{|3q zL8q#R$#|)T_Vk_Yz)4-o%fpVVJEO>&76q|ojiiygV7Q$_xyBE0@kvz(W0nQlM0L_f zloj6W=sXzRiW`Int^lGX1kinc={@%UDAC9A|4Y37(t)D;0qMjjL6eXtIB;6y6ygV} zq4@8h`IS)S3%4sGrQ_%r$wBRwK|>Wfv??f+Y)hb7(a4*n6|_!K(=^Siw%XKS29h0` z@9(2Zw_!^ovr;8sH7=Xln5>zM+w&GR>7E)z<~MK++4NwuXp6!##Nm)TT`2HV8>SN& zd+a2n(rI34P!JHYC~qVMxa&YwD#^{vuzl?!pzjdrnL%Ls--8HBFs&jK(b|{S!%@nq z55-s3+gHd9Brr~Ul4$f;g~%y{S1EGyr~6xv)7^xWlqsyiF)v@AwcqaC=10~;DvCsN z95J_wQ(rtICdbOi83uM{qW2*xN=gkhg>Kc0j9^MQaR)g#eR)EUc^X%aQKMpg>jp?rFOBw0R8emIGRftagc>UU{==MWi71}?C21^U3!R#W3V8vskSe-zI9 z{b~GLce#VDy_xlYxyyg60RMRFhXSl<}%O8edJyRK))(jYFK zHXCMV!1IL9=K5R-MpG!xv(mL;N0VF^AUE<|YL$p^79YhqqYhGS7}AG7oWldvz2*jo zf|23w1ZOLnbfbEYo4aCXn0MqRTW2;Um?{K^f~h2;Pw0vd;jtC~buwdYh z*vsg6)Y~MH5sAmHi8Ci*=o0J;5s~V*cwACoB9H2|J_@~zN~-21!@&`=kq|1pc0H)s z7dx#ijmY%+{^bpVPBo^3>?ppCv;3NRN%jdcQKj}xOJFJ82no*B``cZ86OviM+^*NH zNb^W}g+N1lTE5DaX_ZEm76BhxfCQ&rOHWA<1wbk3rG8F_Inh0T@xaZ6v&H_0GKiW# zxcRq=gaQ5ZVnY7A1^PR_{Iw=y=>crP2r@~X^Vxbs{MHxXDz(x5g1`pe9hIvo6L~hW zn;lS&DF8uHMMIL(lcTFDcCsn=rME@*03W!EOrzz*ZLxVV^-A|uYxb0Jp49F(6C~de zg%R=h!2YywUzoKCPa0yLKn?`>B*EA8H2e)|F(B5#%zebvCU;=aO6F3THS0%n4&ScQ_SGw!tmt^P zR$gItYv~Mf&f`_MXd0v`Eh_r%Ou(f@EK0qW1T;%y(&aPEhpC|6s8cwV}<=_koDHL`L9EIe8CGt2EZhP zCrtY1A^lt1(oZJ+weo%}p&zS9QA1XdAK?zoTtgxT%Fhp7=(&0pHxy4&7Ky8*R49_9 z*ooDwbw4n-$6VF=(T5Yr+z{T+ct&aSxP9Lct}ClJIgh;soIj?$~y z@NlYVsay%1d#APRp_i4vctSCxbWz|`5Utzu?Yr0hH@k)#)p89Z%ERVgZ|@oeW(J@)E4Y*%kE}hc&4rD@=xgL=Ot%1PL=J z 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 wJk%``LWJdwk=4s=8PE{2zFKlLrbg + 4.0.0 + cn.dev33 + sa-token-demo-springboot + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.RELEASE + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + + + cn.dev33.sa-token + sa-token-spring + 1.0.0 + system + ${project.basedir}/lib/sa-token-spring-1.0.0.jar + + + + + org.springframework.boot + spring-boot-starter-redis + RELEASE + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + lib/ + com.pj.SaTokenDemoApplication + + + + lib/sa-token-spring-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + package + + copy-dependencies + + + + ${project.build.directory}/lib + + + + + + + + + + \ No newline at end of file diff --git a/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java new file mode 100644 index 0000000..384540c --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java @@ -0,0 +1,18 @@ +package com.pj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import cn.dev33.satoken.spring.SaTokenSetup; + +@SaTokenSetup // 标注启动 sa-token +@SpringBootApplication +public class SaTokenDemoApplication { + + public static void main(String[] args) throws JsonProcessingException { + SpringApplication.run(SaTokenDemoApplication.class, args); // run--> + } + +} \ No newline at end of file diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java new file mode 100644 index 0000000..b7a7059 --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java @@ -0,0 +1,91 @@ +package com.pj.satoken; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +// import org.springframework.stereotype.Component; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.session.SaSession; + +/** + * sa-token持久层的实现类 , 基于redis + */ +// @Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token与redis的集成 +public class SaTokenDaoRedis implements SaTokenDao { + + + // string专用 + @Autowired + StringRedisTemplate stringRedisTemplate; + + // SaSession专用 + RedisTemplate redisTemplate; + @Autowired + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void setRedisTemplate(RedisTemplate redisTemplate) { + RedisSerializer stringSerializer = new StringRedisSerializer(); + redisTemplate.setKeySerializer(stringSerializer); + this.redisTemplate = redisTemplate; + } + + + // 根据key获取value ,如果没有,则返回空 + @Override + public String getValue(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + // 写入指定key-value键值对,并设定过期时间(单位:秒) + @Override + public void setValue(String key, String value, long timeout) { + stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + + // 删除一个指定的key + @Override + public void delKey(String key) { + stringRedisTemplate.delete(key); + } + + + // 根据指定key的session,如果没有,则返回空 + @Override + public SaSession getSaSession(String sessionId) { + return redisTemplate.opsForValue().get(sessionId); + } + + // 将指定session持久化 + @Override + public void saveSaSession(SaSession session, long timeout) { + redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); + } + + // 更新指定session + @Override + public void updateSaSession(SaSession session) { + long expire = redisTemplate.getExpire(session.getId()); + if(expire == -2) { // -2 = 无此键 + return; + } + redisTemplate.opsForValue().set(session.getId(), session, expire, TimeUnit.SECONDS); + } + + // 删除一个指定的session + @Override + public void delSaSession(String sessionId) { + redisTemplate.delete(sessionId); + } + + + + + + + + +} diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpCustom.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpCustom.java new file mode 100644 index 0000000..6de6ce8 --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpCustom.java @@ -0,0 +1,28 @@ +package com.pj.satoken; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.stp.StpInterface; + +/** + * 自定义权限验证接口扩展 + */ +@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 +public class StpCustom implements StpInterface { + + @Override + public List getPermissionCodeList(Object login_id, String login_key) { + List list = new ArrayList(); + list.add("101"); + list.add("user-add"); + list.add("user-delete"); + list.add("user-update"); + list.add("user-get"); + list.add("article-get"); + return list; + } + +} diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/AjaxJson.java b/sa-token-demo-springboot/src/main/java/com/pj/test/AjaxJson.java new file mode 100644 index 0000000..f8c12a1 --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/AjaxJson.java @@ -0,0 +1,163 @@ +package com.pj.test; + +import java.io.Serializable; +import java.util.List; + + +/** + * ajax返回Json的封装 + * 【此类封装了Meta和Body的功能,写法不同,但是返回数据格式相同】 + */ +public class AjaxJson implements Serializable{ + + private static final long serialVersionUID = 1L; // 序列化版本号 + + public static final int CODE_SUCCESS = 200; // 成功状态码 + public static final int CODE_ERROR = 500; // 错误状态码 + public static final int CODE_WARNING = 501; // 警告状态码 + public static final int CODE_NOT_JUR = 403; // 无权限状态码 + public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 + public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 + + public int code; // 状态码 + public String msg; // 描述信息 + public Object data; // 携带对象 + public Long dataCount; // 数据总数 + + /** + * 返回code + * @return + */ + public int getCode() { + return this.code; + } + + /** + * 给msg赋值,连缀风格 + */ + public AjaxJson setMsg(String msg) { + this.msg = msg; + return this; + } + public String getMsg() { + return this.msg; + } + + /** + * 给data赋值,连缀风格 + */ + public AjaxJson setData(Object data) { + this.data = data; + return this; + } + + /** + * 将data还原为指定类型并返回 + */ + @SuppressWarnings("unchecked") + public T getData(Class cs) { + return (T) data; + } + + // ============================ 构建 ================================== + + public AjaxJson(int code, String msg, Object data, Long dataCount) { + this.code = code; + this.msg = msg; + this.data = data; + this.dataCount = dataCount; + } + + // 返回成功 + public static AjaxJson getSuccess() { + return new AjaxJson(CODE_SUCCESS, "ok", null, null); + } + public static AjaxJson getSuccess(String msg) { + return new AjaxJson(CODE_SUCCESS, msg, null, null); + } + public static AjaxJson getSuccess(String msg, Object data) { + return new AjaxJson(CODE_SUCCESS, msg, data, null); + } + public static AjaxJson getSuccessData(Object data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + public static AjaxJson getSuccessArray(Object... data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + + // 返回失败 + public static AjaxJson getError() { + return new AjaxJson(CODE_ERROR, "error", null, null); + } + public static AjaxJson getError(String msg) { + return new AjaxJson(CODE_ERROR, msg, null, null); + } + + // 返回警告 + public static AjaxJson getWarning() { + return new AjaxJson(CODE_ERROR, "warning", null, null); + } + public static AjaxJson getWarning(String msg) { + return new AjaxJson(CODE_WARNING, msg, null, null); + } + + // 返回未登录 + public static AjaxJson getNotLogin() { + return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); + } + + // 返回没有权限的 + public static AjaxJson getNotJur(String msg) { + return new AjaxJson(CODE_NOT_JUR, msg, null, null); + } + + // 返回一个自定义状态码的 + public static AjaxJson get(int code, String msg){ + return new AjaxJson(code, msg, null, null); + } + + // 返回分页和数据的 + public static AjaxJson getPageData(Long dataCount, Object data){ + return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount); + } + + // 返回,根据受影响行数的(大于0=ok,小于0=error) + public static AjaxJson getByLine(int line){ + if(line > 0){ + return getSuccess("ok", line); + } + return getError("error").setData(line); + } + + // 返回,根据布尔值来确定最终结果的 (true=ok,false=error) + public static AjaxJson getByBoolean(boolean b){ + return b ? getSuccess("ok") : getError("error"); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @SuppressWarnings("rawtypes") + @Override + public String toString() { + String data_string = null; + if(data == null){ + + } else if(data instanceof List){ + data_string = "List(length=" + ((List)data).size() + ")"; + } else { + data_string = data.toString(); + } + return "{" + + "\"code\": " + this.getCode() + + ", \"msg\": \"" + this.getMsg() + "\"" + + ", \"data\": " + data_string + + ", \"dataCount\": " + dataCount + + "}"; + } + + + + + +} diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java new file mode 100644 index 0000000..fd63fe4 --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -0,0 +1,83 @@ +package com.pj.test; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import cn.dev33.satoken.session.SaSessionUtil; +import cn.dev33.satoken.stp.StpUtil; + +@RestController +@RequestMapping("/test/") +public class TestController { + + // 测试登录接口, 浏览器访问: http://localhost:8081/test/login + @RequestMapping("login") + public AjaxJson login(@RequestParam(defaultValue="10001") String id) { + System.out.println("======================= 进入方法,测试登录接口 ========================= "); + System.out.println("当前会话的token:" + StpUtil.getTokenValue()); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号:" + StpUtil.getLoginId_defaultNull()); + StpUtil.setLoginId(id); // 在当前会话登录此账号 + System.out.println("登录成功"); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号:" + StpUtil.getLoginId()); + System.out.println("当前登录账号:" + StpUtil.getLoginId_asInt()); // 获取登录id并转为int + + System.out.println("当前token信息:" + StpUtil.getTokenInfo()); // 获取登录id并转为int + + return AjaxJson.getSuccess(); + } + + // 测试权限接口, 浏览器访问: http://localhost:8081/test/jur + @RequestMapping("jur") + public AjaxJson jur() { + System.out.println("======================= 进入方法,测试权限接口 ========================= "); + + System.out.println("是否具有权限101" + StpUtil.hasPermission(101)); + System.out.println("是否具有权限user-add" + StpUtil.hasPermission("user-add")); + System.out.println("是否具有权限article-get" + StpUtil.hasPermission("article-get")); + + System.out.println("没有user-add权限就抛出异常"); + StpUtil.checkPermission("user-add"); + + System.out.println("在【101、102】中只要拥有一个就不会抛出异常"); + StpUtil.checkPermissionOr("101", "102"); + + System.out.println("在【101、102】中必须全部拥有才不会抛出异常"); + StpUtil.checkPermissionAnd("101", "102"); + + System.out.println("权限测试通过"); + + return AjaxJson.getSuccess(); + } + + // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session + @RequestMapping("session") + public AjaxJson session() { + System.out.println("======================= 进入方法,测试会话session接口 ========================= "); + System.out.println("当前是否登录:" + StpUtil.isLogin()); + System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); + System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); + System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); + StpUtil.getSession().setAttribute("name", "张三"); // 写入一个值 + System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); + return AjaxJson.getSuccess(); + } + + // 测试自定义session接口, 浏览器访问: http://localhost:8081/test/session2 + @RequestMapping("session2") + public AjaxJson session2() { + System.out.println("======================= 进入方法,测试自定义session接口 ========================= "); + // 自定义session就是无需登录也可以使用 的session :比如拿用户的手机号当做 key, 来获取 session + System.out.println("自定义 session的id为:" + SaSessionUtil.getSessionById("1895544896").getId()); + System.out.println("测试取值name:" + SaSessionUtil.getSessionById("1895544896").getAttribute("name")); + SaSessionUtil.getSessionById("1895544896").setAttribute("name", "张三"); // 写入值 + System.out.println("测试取值name:" + SaSessionUtil.getSessionById("1895544896").getAttribute("name")); + System.out.println("测试取值name:" + SaSessionUtil.getSessionById("1895544896").getAttribute("name")); + return AjaxJson.getSuccess(); + } + + + +} diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TopController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TopController.java new file mode 100644 index 0000000..a31775b --- /dev/null +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TopController.java @@ -0,0 +1,57 @@ +package com.pj.test; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ModelAttribute; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; + +/** + * 加强版控制器 + */ +@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.zyd.blog.controller.admin") +public class TopController { + + // 在每个控制器之前触发的操作 + @ModelAttribute + public void get(HttpServletRequest request) throws IOException { + + } + + // 全局异常拦截(拦截项目中的所有异常) + @ExceptionHandler + public void handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) + throws Exception { + + e.printStackTrace(); // 打印堆栈,以供调试 + + response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象 + + // 如果是未登录异常 + if (e instanceof NotLoginException) { + String jsonStr = new ObjectMapper().writeValueAsString(AjaxJson.getNotLogin()); + response.getWriter().print(jsonStr); + return; + } + // 如果是权限异常 + if (e instanceof NotPermissionException) { + NotPermissionException ee = (NotPermissionException) e; + String jsonStr = new ObjectMapper().writeValueAsString(AjaxJson.getNotJur("无此权限:" + ee.getCode())); + response.getWriter().print(jsonStr); + return; + } + + // 普通异常输出:500 + 异常信息 + response.getWriter().print(new ObjectMapper().writeValueAsString(AjaxJson.getError(e.getMessage()))); + + } + +} diff --git a/sa-token-demo-springboot/src/main/resources/application.yml b/sa-token-demo-springboot/src/main/resources/application.yml new file mode 100644 index 0000000..792fab7 --- /dev/null +++ b/sa-token-demo-springboot/src/main/resources/application.yml @@ -0,0 +1,44 @@ +# 端口 +server: + port: 8081 + +spring: + # sa-token配置 + sa-token: + # token名称(同时也是cookie名称) + token-name: satoken + # token有效期,单位s 默认30天,-1为永不过期 + timeout: 2592000 + # 在多人登录同一账号时,是否共享会话(为true时共用一个,为false时新登录挤掉旧登录) + is-share: true + # 是否在cookie读取不到token时,继续从请求header里继续尝试读取 + is-read-head: true + # 是否在header读取不到token时,继续从请求题参数里继续尝试读取 + is-read-body: true + # 是否在初始化配置时打印版本字符画 + is-v: true + + + # redis配置 + redis: + # Redis数据库索引(默认为0) + database: 1 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接池最大连接数(使用负值表示没有限制) + pool: + maxActive: 20 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + maxWait: -1 + # 连接池中的最大空闲连接 + maxIdle: 8 + # 连接池中的最小空闲连接 + minIdle: 1 + # 连接超时时间(毫秒) + timeout: 0 + + \ No newline at end of file diff --git a/sa-token-dev/.classpath b/sa-token-dev/.classpath new file mode 100644 index 0000000..cd04a79 --- /dev/null +++ b/sa-token-dev/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sa-token-dev/.gitignore b/sa-token-dev/.gitignore new file mode 100644 index 0000000..ae57b0f --- /dev/null +++ b/sa-token-dev/.gitignore @@ -0,0 +1,6 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ \ No newline at end of file diff --git a/sa-token-dev/.project b/sa-token-dev/.project new file mode 100644 index 0000000..994c19a --- /dev/null +++ b/sa-token-dev/.project @@ -0,0 +1,23 @@ + + + sa-token-dev + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/sa-token-dev/pom.xml b/sa-token-dev/pom.xml new file mode 100644 index 0000000..90a11e1 --- /dev/null +++ b/sa-token-dev/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + cn.dev33 + sa-token-dev + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.RELEASE + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + + + + \ No newline at end of file diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenManager.java b/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenManager.java new file mode 100644 index 0000000..0f25e17 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenManager.java @@ -0,0 +1,78 @@ +package cn.dev33.satoken; + +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.config.SaTokenConfigFactory; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.dao.SaTokenDaoDefault; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpInterfaceDefaultImpl; + +/** + * 管理sa-token所有对象 + * @author kong + * + */ +public class SaTokenManager { + + // 配置文件 Bean + private static SaTokenConfig config; + public static SaTokenConfig getConfig() { + if (config == null) { + initConfig(); + } + return config; + } + public static void setConfig(SaTokenConfig config) { + SaTokenManager.config = config; + if(config.getIsV()) { + SaTokenUtil.printSaToken(); + } + } + public synchronized static void initConfig() { + if (config == null) { + setConfig(SaTokenConfigFactory.createConfig()); + } + } + + // 持久化 Bean + public static SaTokenDao dao; + public static SaTokenDao getDao() { + if (dao == null) { + initDao(); + } + return dao; + } + public static void setDao(SaTokenDao dao) { + SaTokenManager.dao = dao; + } + public synchronized static void initDao() { + if (dao == null) { + setDao(new SaTokenDaoDefault()); + } + } + + + // 权限认证 Bean + public static StpInterface stp; + public static StpInterface getStp() { + if (stp == null) { + initStp(); + } + return stp; + } + public static void setStp(StpInterface stp) { + SaTokenManager.stp = stp; + } + public synchronized static void initStp() { + if (stp == null) { + setStp(new StpInterfaceDefaultImpl()); + } + } + + + + + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenUtil.java b/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenUtil.java new file mode 100644 index 0000000..9359005 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/SaTokenUtil.java @@ -0,0 +1,30 @@ +package cn.dev33.satoken; + +/** + * sa-token 工具类 + */ +public class SaTokenUtil { + + + // sa-token 版本号 + public static final String version = "v1.0.0"; + + // sa-token 开源地址 + public static final String github_url = "https://github.com/click33/sa-token"; + + // 打印 sa-token + public static void printSaToken() { + String str = + "____ ____ ___ ____ _ _ ____ _ _ \r\n" + + "[__ |__| __ | | | |_/ |___ |\\ | \r\n" + + "___] | | | |__| | \\_ |___ | \\| \r\n" + + "sa-token:" + version + " \r\n" + + "GitHub:" + github_url + "\r\n"; + System.out.println(str); + } + + // 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 + public static final String just_created_save_key= "just_created_save_key_"; + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java new file mode 100644 index 0000000..108dea5 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -0,0 +1,65 @@ +package cn.dev33.satoken.config; + +/** + * sa-token 总配置类 + */ +public class SaTokenConfig { + + private String tokenName = "satoken"; // token名称(同时也是cookie名称) + private long timeout = 30 * 24 * 60 * 60; // token有效期,单位s 默认30天,-1为永不过期 + private Boolean isShare = true; // 在多人登录同一账号时,是否共享会话(为true时共用一个,为false时新登录挤掉旧登录) + private Boolean isReadHead = false; // 是否在cookie读取不到token时,继续从请求header里继续尝试读取 + private Boolean isReadBody = false; // 是否在header读取不到token时,继续从请求题参数里继续尝试读取 + + private Boolean isV = true; // 是否在初始化配置时打印版本字符画 + + + public String getTokenName() { + return tokenName; + } + public void setTokenName(String tokenName) { + this.tokenName = tokenName; + } + public long getTimeout() { + return timeout; + } + public void setTimeout(long timeout) { + this.timeout = timeout; + } + public Boolean getIsShare() { + return isShare; + } + public void setIsShare(Boolean isShare) { + this.isShare = isShare; + } + public Boolean getIsReadHead() { + return isReadHead; + } + public void setIsReadHead(Boolean isReadHead) { + this.isReadHead = isReadHead; + } + public Boolean getIsReadBody() { + return isReadBody; + } + public void setIsReadBody(Boolean isReadBody) { + this.isReadBody = isReadBody; + } + public Boolean getIsV() { + return isV; + } + public void setIsV(Boolean isV) { + this.isV = isV; + } + + + @Override + public String toString() { + return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", isShare=" + isShare + + ", isReadHead=" + isReadHead + ", isReadBody=" + isReadBody + ", isV=" + isV + "]"; + } + + + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfigFactory.java b/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfigFactory.java new file mode 100644 index 0000000..ae3d39d --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/config/SaTokenConfigFactory.java @@ -0,0 +1,128 @@ +package cn.dev33.satoken.config; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + 创建一个配置文件 + */ +public class SaTokenConfigFactory { + + + public static String configPath = "sa-token.properties"; // 默认配置文件地址 + + /** + * 根据指定路径获取配置信息 + * @return 一个SaTokenConfig对象 + */ + public static SaTokenConfig createConfig() { + Map map = readPropToMap(configPath); + if(map == null){ + // throw new RuntimeException("找不到配置文件:" + configPath, null); + } + return (SaTokenConfig)initPropByMap(map, new SaTokenConfig()); + } + + + + /** + * 将指定路径的properties配置文件读取到Map中 + * @param propertiesPath 配置文件地址 + * @return 一个Map + */ + private static Map readPropToMap(String propertiesPath){ + Map map = new HashMap(); + try { + InputStream is = SaTokenConfigFactory.class.getClassLoader().getResourceAsStream(propertiesPath); + if(is == null){ + return null; + } + Properties prop = new Properties(); + prop.load(is); + for (String key : prop.stringPropertyNames()) { + map.put(key, prop.getProperty(key)); + } + } catch (IOException e) { + throw new RuntimeException("配置文件(" + propertiesPath + ")加载失败", e); + } + return map; + } + + + /** + * 将 Map 的值映射到 Model 上 + * @param map 属性集合 + * @param obj 对象,或类型 + * @return 返回实例化后的对象 + */ + private static Object initPropByMap(Map map, Object obj){ + + if(map == null){ + map = new HashMap<>(); + } + + // 1、取出类型 + Class cs = null; + if(obj instanceof Class){ // 如果是一个类型,则将obj=null,以便完成静态属性反射赋值 + cs = (Class)obj; + obj = null; + }else{ // 如果是一个对象,则取出其类型 + cs = obj.getClass(); + } + + // 2、遍历类型属性,反射赋值 + for (Field field : cs.getDeclaredFields()) { + String value = map.get(field.getName()); + if (value == null) { + continue; // 如果为空代表没有配置此项 + } + try { + Object valueConvert = getObjectByClass(value, field.getType()); // 转换值类型 + field.setAccessible(true); + field.set(obj, valueConvert); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("属性赋值出错:" + field.getName(), e); + } + } + return obj; + } + + /** + * 将字符串转化为指定数据类型 + * @param str 值 + * @param cs 要转换的类型 + * @return + */ + @SuppressWarnings("unchecked") + private static T getObjectByClass(String str, Class cs){ + Object value = null; + if(str == null){ + value = null; + }else if (cs.equals(String.class)) { + value = str; + } else if (cs.equals(int.class)||cs.equals(Integer.class)) { + value = new Integer(str); + } else if (cs.equals(long.class)||cs.equals(Long.class)) { + value = new Long(str); + } else if (cs.equals(short.class)||cs.equals(Short.class)) { + value = new Short(str); + } else if (cs.equals(float.class)||cs.equals(Float.class)) { + value = new Float(str); + } else if (cs.equals(double.class)||cs.equals(Double.class)) { + value = new Double(str); + } else if (cs.equals(boolean.class)||cs.equals(Boolean.class)) { + value = new Boolean(str); + }else{ + throw new RuntimeException("未能将值:" + str + ",转换类型为:" + cs, null); + } + return (T)value; + } + + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java b/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java new file mode 100644 index 0000000..54e99b5 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java @@ -0,0 +1,61 @@ +package cn.dev33.satoken.dao; + +import cn.dev33.satoken.session.SaSession; + +/** + * sa-token持久层的接口 + */ +public interface SaTokenDao { + + + /** + * 根据key获取value ,如果没有,则返回空 + * @param key 键名称 + * @return + */ + public String getValue(String key); + + /** + * 写入指定key-value键值对,并设定过期时间 (单位:秒) + * @param key 键名称 + * @param value 值 + * @param timeout 过期时间,单位:s + */ + public void setValue(String key, String value, long timeout); + + /** + * 删除一个指定的key + * @param key + */ + public void delKey(String key); + + + + /** + * 根据指定key的session,如果没有,则返回空 + * @param key 键名称 + * @return + */ + public SaSession getSaSession(String sessionId); + + /** + * 将指定session持久化 + * @param key 键名称 + * @param value 值 + * @param timeout 过期时间,单位: s + */ + public void saveSaSession(SaSession session, long timeout); + + /** + * 更新指定session + */ + public void updateSaSession(SaSession session); + + /** + * 删除一个指定的session + * @param key 键名称 + */ + public void delSaSession(String sessionId); + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefault.java b/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefault.java new file mode 100644 index 0000000..ee195db --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefault.java @@ -0,0 +1,62 @@ +package cn.dev33.satoken.dao; + +import java.util.HashMap; +import java.util.Map; + +import cn.dev33.satoken.session.SaSession; + +/** + * sa-token持久层默认的实现类 , 基于内存Map + */ +public class SaTokenDaoDefault implements SaTokenDao { + + /** + * 所有数据集合 + */ + Map dataMap = new HashMap(); + + + @Override + public String getValue(String key) { + return (String)dataMap.get(key); + } + + @Override + public void setValue(String key, String value, long timeout) { + dataMap.put(key, value); + } + + @Override + public void delKey(String key) { + dataMap.remove(key); + } + + + @Override + public SaSession getSaSession(String sessionId) { + return (SaSession)dataMap.get(sessionId); + } + + @Override + public void saveSaSession(SaSession session, long timeout) { + dataMap.put(session.getId(), session); + } + + @Override + public void updateSaSession(SaSession session) { + // 无动作 + } + + @Override + public void delSaSession(String sessionId) { + dataMap.remove(sessionId); + } + + + + + + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotLoginException.java b/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotLoginException.java new file mode 100644 index 0000000..a9d67c8 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotLoginException.java @@ -0,0 +1,21 @@ +package cn.dev33.satoken.exception; + +/** + * 没有登陆抛出的异常 + */ +public class NotLoginException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 6806129545290130142L; + + /** + * 创建一个 + */ + public NotLoginException() { + super("当前账号未登录"); + } + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotPermissionException.java b/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotPermissionException.java new file mode 100644 index 0000000..d7d4d22 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/exception/NotPermissionException.java @@ -0,0 +1,33 @@ +package cn.dev33.satoken.exception; + +/** + * 没有指定权限码,抛出的异常 + */ +public class NotPermissionException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 6806129545290130142L; + + private Object code; + + + /** + * @return 获得权限码 + */ + public Object getCode() { + return code; + } + + public NotPermissionException(Object code) { + super("无此权限:" + code); + this.code = code; + } + +// public NotPermissionException(Object code, String s) { +// super(s); +// this.code = code; +// } + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSession.java b/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSession.java new file mode 100644 index 0000000..bb812c3 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSession.java @@ -0,0 +1,129 @@ +package cn.dev33.satoken.session; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import cn.dev33.satoken.SaTokenManager; + + +/** + * session会话 + * @author kong + * + */ +public class SaSession implements Serializable { + + private static final long serialVersionUID = 1L; + + private String id; // 会话id + private long createTime; // 当前会话创建时间 + private Map dataMap; // 当前会话键值对 + + + /** + * 构建一个 session对象 + * @param id + */ + public SaSession(String id) { + this.id = id; + this.createTime = System.currentTimeMillis(); + this.dataMap = new HashMap(); + } + + /** + * 获取会话id + * @return + */ + public String getId() { + return id; + } + + /** + * 当前会话创建时间 + */ + public long getCreateTime() { + return createTime; + } + + /** + * 写入值 + */ + public void setAttribute(String key, Object value) { + dataMap.put(key, value); + update(); + } + + /** + * 取值 + */ + public Object getAttribute(String key) { + return dataMap.get(key); + } + + /** + * 取值,并指定取不到值时的默认值 + */ + public Object getAttribute(String key, Object default_value) { + Object value = getAttribute(key); + if(value != null) { + return value; + } + return default_value; + } + + + /** + * 移除一个key + */ + public void removeAttribute(String key) { + dataMap.remove(key); + update(); + } + + /** + * 清空所有key + */ + public void clearAttribute() { + dataMap.clear(); + update(); + } + + /** + * 是否含有指定key + */ + public boolean containsAttribute(String key) { + return dataMap.keySet().contains(key); + } + + /** + * 当前session会话所有key + */ + public Set getAttributeKeys() { + return dataMap.keySet(); + } + + /** + * 获取数据集合(如果更新map里的值,请调用session.update()方法避免数据过时 ) + */ + public Map getDataMap() { + return dataMap; + } + + /** + * 将这个session从持久库更新一下 + */ + public void update() { + SaTokenManager.getDao().updateSaSession(this); + } + + +// /** 注销会话(注销后,此session会话将不再存储服务器上) */ +// public void logout() { +// SaTokenManager.getDao().delSaSession(this.id); +// } + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSessionUtil.java b/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSessionUtil.java new file mode 100644 index 0000000..ceb57e2 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/session/SaSessionUtil.java @@ -0,0 +1,44 @@ +package cn.dev33.satoken.session; + +import cn.dev33.satoken.SaTokenManager; + +/** + * sa-session工具类 + * @author kong + * + */ +public class SaSessionUtil { + + // 添加上指定前缀,防止恶意伪造session + public static String session_key = "custom"; + public static String getSessionKey(String sessionId) { + return SaTokenManager.getConfig().getTokenName() + ":" + session_key + ":session:" + sessionId; + } + + /** 指定key的session是否存在 */ + public boolean isExists(String sessionId) { + return SaTokenManager.getDao().getSaSession(getSessionKey(sessionId)) != null; + } + + /** 获取指定key的session, 如果没有,is_create=是否新建并返回 */ + public static SaSession getSessionById(String sessionId, boolean is_create) { + SaSession session = SaTokenManager.getDao().getSaSession(getSessionKey(sessionId)); + if(session == null && is_create) { + session = new SaSession(getSessionKey(sessionId)); + SaTokenManager.getDao().saveSaSession(session, SaTokenManager.getConfig().getTimeout()); + } + return session; + } + /** 获取指定key的session, 如果没有则新建并返回 */ + public static SaSession getSessionById(String sessionId) { + return getSessionById(sessionId, true); + } + + /** 删除指定key的session */ + public static void delSessionById(String sessionId) { + SaTokenManager.getDao().delSaSession(getSessionKey(sessionId)); + } + + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SaTokenSetup.java b/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SaTokenSetup.java new file mode 100644 index 0000000..9f839b4 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SaTokenSetup.java @@ -0,0 +1,21 @@ +package cn.dev33.satoken.spring; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 将此注解加到springboot启动类上,即可完成sa-token与springboot的集成 + */ +@Documented +@Target({java.lang.annotation.ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Configuration +@Import({SpringSaToken.class}) +public @interface SaTokenSetup { + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SpringSaToken.java b/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SpringSaToken.java new file mode 100644 index 0000000..def6d04 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/spring/SpringSaToken.java @@ -0,0 +1,48 @@ +package cn.dev33.satoken.spring; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.SaTokenManager; +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.stp.StpInterface; + +/** + * 与SpringBoot集成, 保证此类被扫描,即可完成sa-token与SpringBoot的集成 + * @author kongyongshun + * + */ +@Component +public class SpringSaToken { + + + // 获取配置Bean + @Bean + @ConfigurationProperties(prefix="spring.sa-token") + public SaTokenConfig getSaTokenConfig() { + return new SaTokenConfig(); + } + + // 注入配置Bean + @Autowired + public void setConfig(SaTokenConfig saTokenConfig){ + SaTokenManager.setConfig(saTokenConfig); + } + + // 注入持久化Bean + @Autowired(required = false) + public void setDao(SaTokenDao dao){ + SaTokenManager.setDao(dao); + } + + // 注入权限认证Bean + @Autowired(required = false) + public void setStp(StpInterface stp){ + SaTokenManager.setStp(stp); + } + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterface.java b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterface.java new file mode 100644 index 0000000..005c37e --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterface.java @@ -0,0 +1,14 @@ +package cn.dev33.satoken.stp; + +import java.util.List; + +/** + * 开放权限验证接口,方便重写 + */ +public interface StpInterface { + + /** 返回指定login_id所拥有的权限码集合 */ + public List getPermissionCodeList(Object login_id, String login_key); + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterfaceDefaultImpl.java b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterfaceDefaultImpl.java new file mode 100644 index 0000000..aed2fb5 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpInterfaceDefaultImpl.java @@ -0,0 +1,16 @@ +package cn.dev33.satoken.stp; + +import java.util.ArrayList; +import java.util.List; + +/** + * 权限验证接口 ,默认实现 + */ +public class StpInterfaceDefaultImpl implements StpInterface { + + @Override + public List getPermissionCodeList(Object login_id, String login_key) { + return new ArrayList(); + } + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpLogic.java new file mode 100644 index 0000000..022f681 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -0,0 +1,306 @@ +package cn.dev33.satoken.stp; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import cn.dev33.satoken.SaTokenManager; +import cn.dev33.satoken.SaTokenUtil; +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.util.SaCookieUtil; +import cn.dev33.satoken.util.SpringMVCUtil; + +/** + * sa-token 权限验证,逻辑 实现类 + *

+ * (stp = sa-token-permission 的缩写 ) + * + */ +public class StpLogic { + + + private String login_key = ""; // 持久化的key前缀,多账号体系时以此值区分,比如:login、user、admin + + public StpLogic(String login_key) { + this.login_key = login_key; + } + + // =================== 获取token 相关 =================== + + + /** 随机生成一个tokenValue */ + public String randomTokenValue() { + return UUID.randomUUID().toString(); + } + + /** 获取当前tokenValue */ + public String getTokenValue(){ + // 0、获取相应对象 + HttpServletRequest request = SpringMVCUtil.getRequest(); + SaTokenConfig config = SaTokenManager.getConfig(); + String key_tokenName = getKey_tokenName(); + + // 1、尝试从request里读取 + if(request.getAttribute(SaTokenUtil.just_created_save_key) != null) { + return String.valueOf(request.getAttribute(SaTokenUtil.just_created_save_key)); + } + + // 2、尝试从cookie里读取 + Cookie cookie = SaCookieUtil.getCookie(request, key_tokenName); + if(cookie != null){ + String tokenValue = cookie.getValue(); + if(tokenValue != null) { + return tokenValue; + } + } + + // 3、尝试从header力读取 + if(config.getIsReadHead() == true){ + String tokenValue = request.getHeader(key_tokenName); + if(tokenValue != null) { + return tokenValue; + } + } + + // 4、尝试从请求体里面读取 + if(config.getIsReadBody() == true){ + String tokenValue = request.getParameter(key_tokenName); + if(tokenValue != null) { + return tokenValue; + } + } + + // 5、都读取不到,那算了吧还是 + return null; + } + + /** 获取指定id的tokenValue */ + public String getTokenValueByLoginId(Object login_id) { + return SaTokenManager.getDao().getValue(getKey_LoginId(login_id)); + } + + /** 获取当前会话的token信息:tokenName与tokenValue */ + public Map getTokenInfo() { + Map map = new HashMap(); + map.put("tokenName", getKey_tokenName()); + map.put("tokenValue", getTokenValue()); + return map; + } + + + // =================== 登录相关操作 =================== + + /** 在当前会话上登录id ,建议的类型:(long | int | String) */ + public void setLoginId(Object login_id) { + + // 1、获取相应对象 + HttpServletRequest request = SpringMVCUtil.getRequest(); + SaTokenConfig config = SaTokenManager.getConfig(); + SaTokenDao dao = SaTokenManager.getDao(); + + // 2、获取tokenValue + String tokenValue = getTokenValueByLoginId(login_id); // 获取旧tokenValue + if(tokenValue == null){ // 为null则创建一个新的 + tokenValue = randomTokenValue(); + } else { + // 不为null, 并且配置不共享,则删掉原来,并且创建新的 + if(config.getIsShare() == false){ + dao.delKey(getKey_TokenValue(tokenValue)); + tokenValue = randomTokenValue(); + } + } + + // 3、持久化 + dao.setValue(getKey_TokenValue(tokenValue), String.valueOf(login_id), config.getTimeout()); // token -> uid + dao.setValue(getKey_LoginId(login_id), tokenValue, config.getTimeout()); // uid -> token + request.setAttribute(SaTokenUtil.just_created_save_key, tokenValue); // 保存到本次request里 + SaCookieUtil.addCookie(SpringMVCUtil.getResponse(), getKey_tokenName(), tokenValue, "/", (int)config.getTimeout()); // cookie注入 + } + + /** 当前会话注销登录 */ + public void logout() { + Object login_id = getLoginId_defaultNull(); + if(login_id != null) { + logoutByLoginId(login_id); + SaCookieUtil.delCookie(SpringMVCUtil.getRequest(), SpringMVCUtil.getResponse(), getKey_tokenName()); // 清除cookie + } + } + + /** 指定login_id的会话注销登录(踢人下线) */ + public void logoutByLoginId(Object login_id) { + + // 获取相应tokenValue + String tokenValue = getTokenValueByLoginId(login_id); + if(tokenValue == null) { + return; + } + + // 清除相关数据 + SaTokenManager.getDao().delKey(getKey_TokenValue(tokenValue)); // 清除token-id键值对 + SaTokenManager.getDao().delKey(getKey_LoginId(login_id)); // 清除id-token键值对 + SaTokenManager.getDao().delKey(getKey_session(login_id)); // 清除其session + // SaCookieUtil.delCookie(SpringMVCUtil.getRequest(), SpringMVCUtil.getResponse(), getKey_tokenName()); // 清除cookie + } + + // 查询相关 + + /** 获取当前会话是否已经登录 */ + public boolean isLogin() { + return getLoginId_defaultNull() != null; + } + + /** 获取当前会话登录id, 如果未登录,则抛出异常 */ + public Object getLoginId() { + Object login_id = getLoginId_defaultNull(); + if(login_id == null) { + throw new NotLoginException(); + } + return login_id; + } + + /** 获取当前会话登录id, 如果未登录,则返回默认值 */ + @SuppressWarnings("unchecked") + public T getLoginId(T default_value) { + Object login_id = getLoginId_defaultNull(); + if(login_id == null) { + return default_value; + } + return (T)login_id; + } + + /** 获取当前会话登录id, 如果未登录,则返回null */ + public Object getLoginId_defaultNull() { + String tokenValue = getTokenValue(); + if(tokenValue != null) { + Object login_id = SaTokenManager.getDao().getValue(getKey_TokenValue(tokenValue)); + if(login_id != null) { + return login_id; + } + } + return null; + } + + /** 获取当前会话登录id, 并转换为String */ + public String getLoginId_asString() { + return String.valueOf(getLoginId()); + } + + /** 获取当前会话登录id, 并转换为int */ + public int getLoginId_asInt() { + // Object login_id = getLoginId(); +// if(login_id instanceof Integer) { +// return (Integer)login_id; +// } + return Integer.valueOf(String.valueOf(getLoginId())); + } + + /** 获取当前会话登录id, 并转换为long */ + public long getLoginId_asLong() { +// Object login_id = getLoginId(); +// if(login_id instanceof Long) { +// return (Long)login_id; +// } + return Long.valueOf(String.valueOf(getLoginId())); + } + + + + // =================== session相关 =================== + + /** 获取指定key的session, 如果没有,is_create=是否新建并返回 */ + protected SaSession getSessionBySessionId(String sessionId, boolean is_create) { + SaSession session = SaTokenManager.getDao().getSaSession(sessionId); + if(session == null && is_create) { + session = new SaSession(sessionId); + SaTokenManager.getDao().saveSaSession(session, SaTokenManager.getConfig().getTimeout()); + } + return session; + } + + /** 获取指定login_id的session */ + public SaSession getSessionByLoginId(Object login_id) { + return getSessionBySessionId(getKey_session(login_id), false); + } + + /** 获取当前会话的session */ + public SaSession getSession() { + return getSessionBySessionId(getKey_session(getLoginId()), true); + } + + + + // =================== 权限验证操作 =================== + + /** 指定login_id是否含有指定权限 */ + public boolean hasPermission(Object login_id, Object pcode) { + List pcodeList = SaTokenManager.getStp().getPermissionCodeList(login_id, login_key); + return !(pcodeList == null || pcodeList.contains(pcode) == false); + } + + /** 当前会话是否含有指定权限 */ + public boolean hasPermission(Object pcode) { + return hasPermission(getLoginId(), pcode); + } + + /** 当前账号是否含有指定权限 , 没有就抛出异常 */ + public void checkPermission(Object pcode) { + if(hasPermission(pcode) == false) { + throw new NotPermissionException(pcode); + } + } + + /** 当前账号是否含有指定权限 , 【指定多个,必须全都有】 */ + public void checkPermissionAnd(Object... pcodeArray){ + Object login_id = getLoginId(); + List pcodeList = SaTokenManager.getStp().getPermissionCodeList(login_id, login_key); + for (Object pcode : pcodeArray) { + if(pcodeList.contains(pcode) == false) { + throw new NotPermissionException(pcode); // 没有权限抛出异常 + } + } + } + + /** 当前账号是否含有指定权限 , 【指定多个,有一个就可以了】 */ + public void checkPermissionOr(Object... pcodeArray){ + Object login_id = getLoginId(); + List pcodeList = SaTokenManager.getStp().getPermissionCodeList(login_id, login_key); + for (Object pcode : pcodeArray) { + if(pcodeList.contains(pcode) == true) { + return; // 有的话提前退出 + } + } + if(pcodeArray.length > 0) { + throw new NotPermissionException(pcodeArray[0]); // 没有权限抛出异常 + } + } + + + // =================== 返回相应key =================== + + /** 获取key:客户端 tokenName */ + public String getKey_tokenName() { + return SaTokenManager.getConfig().getTokenName(); + } + /** 获取key: tokenValue 持久化 */ + public String getKey_TokenValue(String tokenValue) { + return SaTokenManager.getConfig().getTokenName() + ":" + login_key + ":token:" + tokenValue; + } + /** 获取key: id 持久化 */ + public String getKey_LoginId(Object login_id) { + return SaTokenManager.getConfig().getTokenName() + ":" + login_key + ":id:" + login_id; + } + /** 获取key: session 持久化 */ + public String getKey_session(Object login_id) { + return SaTokenManager.getConfig().getTokenName() + ":" + login_key + ":session:" + login_id; + } + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpUtil.java new file mode 100644 index 0000000..40058e4 --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -0,0 +1,131 @@ +package cn.dev33.satoken.stp; + +import java.util.Map; + +import org.springframework.stereotype.Service; + +import cn.dev33.satoken.session.SaSession; + +/** + * 一个默认的实现 + */ +@Service +public class StpUtil { + + // 底层的 StpLogic 对象 + public static StpLogic stpLogic = new StpLogic("login"); + + + // =================== 获取token 相关 =================== + + + /** 获取当前tokenValue */ + public static String getTokenValue() { + return stpLogic.getTokenValue(); + } + + /** 获取指定id的tokenValue */ + public static String getTokenValueByLoginId(Object login_id) { + return stpLogic.getTokenValueByLoginId(login_id); + } + + /** 获取当前会话的token信息:tokenName与tokenValue */ + public static Map getTokenInfo() { + return stpLogic.getTokenInfo(); + } + + // =================== 登录相关操作 =================== + + /** 在当前会话上设置登录id,建议的类型:(long | int | String) */ + public static void setLoginId(Object login_id) { + stpLogic.setLoginId(login_id); + } + + /** 当前会话注销登录 */ + public static void logout() { + stpLogic.logout(); + } + + /** 指定login_id的会话注销登录(踢人下线) */ + public static void logoutByLoginId(Object login_id) { + stpLogic.logoutByLoginId(login_id); + } + + // 查询相关 + + /** 获取当前会话是否已经登录 */ + public static boolean isLogin() { + return stpLogic.isLogin(); + } + + /** 获取当前会话登录id, 如果未登录,则抛出异常 */ + public static Object getLoginId() { + return stpLogic.getLoginId(); + } + + /** 获取当前会话登录id, 如果未登录,则返回默认值 */ + public static T getLoginId(T default_value) { + return stpLogic.getLoginId(default_value); + } + + /** 获取当前会话登录id, 如果未登录,则返回null */ + public static Object getLoginId_defaultNull() { + return stpLogic.getLoginId_defaultNull(); + } + + /** 获取当前会话登录id, 并转换为String */ + public static String getLoginId_asString() { + return stpLogic.getLoginId_asString(); + } + + /** 获取当前会话登录id, 并转换为int */ + public static int getLoginId_asInt() { + return stpLogic.getLoginId_asInt(); + } + + /** 获取当前会话登录id, 并转换为long */ + public static long getLoginId_asLong() { + return stpLogic.getLoginId_asLong(); + } + + // =================== session相关 =================== + + /** 获取指定login_id的session */ + public static SaSession getSessionByLoginId(Object login_id) { + return stpLogic.getSessionByLoginId(login_id); + } + + /** 获取当前会话的session */ + public static SaSession getSession() { + return stpLogic.getSession(); + } + + // =================== 权限验证操作 =================== + + /** 指定login_id是否含有指定权限 */ + public static boolean hasPermission(Object login_id, Object pcode) { + return stpLogic.hasPermission(login_id, pcode); + } + + /** 当前会话是否含有指定权限 */ + public static boolean hasPermission(Object pcode) { + return stpLogic.hasPermission(pcode); + } + + /** 当前账号是否含有指定权限 , 没有就抛出异常 */ + public static void checkPermission(Object pcode) { + stpLogic.checkPermission(pcode); + } + + /** 当前账号是否含有指定权限 , 【指定多个,必须全都有】 */ + public static void checkPermissionAnd(Object... pcodeArray) { + stpLogic.checkPermissionAnd(pcodeArray); + } + + /** 当前账号是否含有指定权限 , 【指定多个,有一个就可以了】 */ + public static void checkPermissionOr(Object... pcodeArray) { + stpLogic.checkPermissionOr(pcodeArray); + } + + +} diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/util/SaCookieUtil.java b/sa-token-dev/src/main/java/cn/dev33/satoken/util/SaCookieUtil.java new file mode 100644 index 0000000..1943d8c --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/util/SaCookieUtil.java @@ -0,0 +1,80 @@ +package cn.dev33.satoken.util; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * cookie工具类 + * @author kong + * + */ +public class SaCookieUtil { + + + /** + * 获取指定cookie + */ + public static Cookie getCookie(HttpServletRequest request, String cookieName) { + Cookie[] cookies = request.getCookies(); + if(cookies != null) { + for(int i = 0; i < cookies.length; i++) { + Cookie cookie = cookies[i]; + if(cookie != null && cookieName.equals(cookie.getName())) { + return cookie; + } + } + } + return null; + } + + + /** + * 添加cookie + */ + public static void addCookie(HttpServletResponse response,String name,String value,String path,int timeout) { + Cookie cookie = new Cookie(name, value); + if(path == null) { + path = "/"; + } + cookie.setPath(path); + cookie.setMaxAge(timeout); + response.addCookie(cookie); + } + + + /** + * 删除cookie + */ + public static void delCookie(HttpServletRequest request,HttpServletResponse response,String name) { + Cookie[] cookies = request.getCookies(); + if(cookies != null){ + for(Cookie cookie : cookies) { + if(cookies != null && (name).equals(cookie.getName())) { + addCookie(response,name,null,null,0); + return; + } + } + } + } + + + /** + * 修改cookie的value值 + */ + public static void updateCookie(HttpServletRequest request,HttpServletResponse response,String name,String value) { + Cookie[] cookies = request.getCookies(); + if(cookies != null){ + for(Cookie cookie : cookies) { + if(cookies != null && (name).equals(cookie.getName())) { + addCookie(response,name,value,cookie.getPath(),cookie.getMaxAge()); + return; + } + } + } + } + + + +} \ No newline at end of file diff --git a/sa-token-dev/src/main/java/cn/dev33/satoken/util/SpringMVCUtil.java b/sa-token-dev/src/main/java/cn/dev33/satoken/util/SpringMVCUtil.java new file mode 100644 index 0000000..124715b --- /dev/null +++ b/sa-token-dev/src/main/java/cn/dev33/satoken/util/SpringMVCUtil.java @@ -0,0 +1,34 @@ +package cn.dev33.satoken.util; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * SpringMVC相关操作 + * @author kong + * + */ +public class SpringMVCUtil { + + // 获取当前会话的 request + public static HttpServletRequest getRequest() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装 + if(servletRequestAttributes == null) { + throw new RuntimeException("当前环境非JavaWeb"); + } + return servletRequestAttributes.getRequest(); + } + + // 获取当前会话的 + public static HttpServletResponse getResponse() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装 + if(servletRequestAttributes == null) { + throw new RuntimeException("当前环境非JavaWeb"); + } + return servletRequestAttributes.getResponse(); + } + +} diff --git a/sa-token-dev/src/main/java/com/pj/SaTokenApplication.java b/sa-token-dev/src/main/java/com/pj/SaTokenApplication.java new file mode 100644 index 0000000..472af75 --- /dev/null +++ b/sa-token-dev/src/main/java/com/pj/SaTokenApplication.java @@ -0,0 +1,18 @@ +package com.pj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import cn.dev33.satoken.SaTokenManager; +import cn.dev33.satoken.spring.SaTokenSetup; + +@SaTokenSetup // 标注启动 sa-token +@SpringBootApplication +public class SaTokenApplication { + + public static void main(String[] args) { + SpringApplication.run(SaTokenApplication.class, args); // run--> + System.out.println(SaTokenManager.getConfig()); + } + +} \ No newline at end of file diff --git a/sa-token-dev/src/main/resources/application.yml b/sa-token-dev/src/main/resources/application.yml new file mode 100644 index 0000000..36a3ade --- /dev/null +++ b/sa-token-dev/src/main/resources/application.yml @@ -0,0 +1,20 @@ +# 端口 +server: + port: 8081 + +spring: + # sa-token配置 + sa-token: + # token名称(同时也是cookie名称) + token-name: satoken + # token有效期,单位s 默认30天,-1为永不过期 + timeout: 2592000 + # 在多人登录同一账号时,是否共享会话(为true时共用一个,为false时新登录挤掉旧登录) + is-share: true + # 是否在cookie读取不到token时,继续从请求header里继续尝试读取 + is-read-head: true + # 是否在header读取不到token时,继续从请求题参数里继续尝试读取 + is-read-body: true + # 是否在初始化配置时打印版本字符画 + is-v: true + \ No newline at end of file diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html new file mode 100644 index 0000000..0a170b2 --- /dev/null +++ b/sa-token-doc/index.html @@ -0,0 +1,11 @@ + + + + + + sa-token在线文档 + + +

sa-token在线文档

+ + \ No newline at end of file -- GitLab