From 57619bfefc2090d9ac358ecd9ce412aaeff6e3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B0=D1=82=D1=8C=D1=8F=D0=BD=D0=B0=20=D0=90=D1=80?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=BE=D0=BD=D0=BE=D0=B2=D0=B0?= Date: Tue, 13 Jun 2023 15:18:37 +0400 Subject: [PATCH] lab6 vue --- build.gradle | 22 ++- data.mv.db | Bin 86016 -> 45056 bytes front/src/App.vue | 63 +++++++-- front/src/components/Header.vue | 12 +- front/src/pages/error.vue | 9 ++ front/src/pages/login.vue | 83 +++++++++++ front/src/pages/registration.vue | 46 +++++++ front/src/pages/users.vue | 52 +++++++ front/src/router/index.js | 34 ++++- .../is/sbapp/Repository/IUserRepository.java | 8 ++ .../ru/ulstu/is/sbapp/WebConfiguration.java | 13 -- .../is/sbapp/configuration/JwtException.java | 11 ++ .../is/sbapp/configuration/JwtFilter.java | 72 ++++++++++ .../is/sbapp/configuration/JwtProperties.java | 27 ++++ .../is/sbapp/configuration/JwtProvider.java | 108 +++++++++++++++ .../configuration/OpenAPI30Configuration.java | 27 ++++ .../PasswordEncoderConfiguration.java | 14 ++ .../configuration/SecurityConfiguration.java | 83 +++++++++++ .../configuration/TypeConfiguration.java | 20 --- .../sbapp/configuration/WebConfiguration.java | 29 ++++ .../is/sbapp/controllers/AlbumController.java | 3 +- .../sbapp/controllers/ArtistController.java | 3 +- .../sbapp/controllers/SearchController.java | 3 +- .../is/sbapp/controllers/SongController.java | 3 +- .../is/sbapp/controllers/UserController.java | 64 +++++++++ .../ulstu/is/sbapp/controllers/UserDTO.java | 52 +++++++ .../is/sbapp/controllers/UserSignUpDTO.java | 36 +++++ .../ulstu/is/sbapp/database/model/Role.java | 20 +++ .../ulstu/is/sbapp/database/model/User.java | 72 ++++++++++ .../database/service/UserExistsException.java | 7 + .../service/UserNotFoundException.java | 7 + .../sbapp/database/service/UserService.java | 130 ++++++++++++++++++ .../util/validation/ValidationException.java | 3 + src/main/resources/application.properties | 5 +- 34 files changed, 1082 insertions(+), 59 deletions(-) create mode 100644 front/src/pages/error.vue create mode 100644 front/src/pages/login.vue create mode 100644 front/src/pages/registration.vue create mode 100644 front/src/pages/users.vue create mode 100644 src/main/java/ru/ulstu/is/sbapp/Repository/IUserRepository.java delete mode 100644 src/main/java/ru/ulstu/is/sbapp/WebConfiguration.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/JwtException.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/JwtFilter.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/JwtProperties.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/JwtProvider.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java delete mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/TypeConfiguration.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/controllers/UserController.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/controllers/UserDTO.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/controllers/UserSignUpDTO.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/database/model/Role.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/database/model/User.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/database/service/UserExistsException.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/database/service/UserNotFoundException.java create mode 100644 src/main/java/ru/ulstu/is/sbapp/database/service/UserService.java diff --git a/build.gradle b/build.gradle index 7162d35..b560a61 100644 --- a/build.gradle +++ b/build.gradle @@ -12,16 +12,28 @@ repositories { mavenCentral() } +jar { + enabled = false +} + dependencies { + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'com.auth0:java-jwt:4.4.0' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' + implementation 'org.webjars:bootstrap:5.1.3' + implementation 'org.webjars:jquery:3.6.0' + implementation 'org.webjars:font-awesome:6.1.0' implementation 'com.h2database:h2:2.1.210' + implementation 'jakarta.validation:jakarta.validation-api:3.0.0' + implementation 'org.hibernate.validator:hibernate-validator:7.0.1.Final' implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5' - implementation 'org.jetbrains:annotations:24.0.0' - implementation 'org.jetbrains:annotations:24.0.0' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.hibernate.validator:hibernate-validator' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { diff --git a/data.mv.db b/data.mv.db index 353463b313b2a0ff7a48020e72eb63b108d765bb..7079d60beeba1b6bcf563fd0c17621d57d61aebf 100644 GIT binary patch literal 45056 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 literal 86016 zcmeHQU2GiJb)H>vNt)EJWw}n!AS?PuV!|+U=YKY>qy42QQl!LP+S1cver-tWO;Z#F`jaBhZGj?y(H8@GvYWsNf}jEV(zh1qLmz^m=$ZR7b7yvE zxl65FM!G9;XLf$ioO|Zpd(QdJIhQKRqPxB8-Db=7&3A(el4W_(d;9(lY_=D@y};QI z{0g!xSGA1Lu$9Gdckhm~4<|hc_U_%>-HDD1w}XA}?O?B>>as7JvOFt^kKlmdfZ%}O zfZ%}OfZ%}OfZ%}OfZ%}OfZ%}Oz!BiUC8qbYrO^M6K&uE61P25M1P25M1P25M1P25M z1P25M1P25M1P8{;f!N8s54LEsyULAqu`AeIOm1l!{Z+&q<|orS}U6 zmCnKc(mB}qqV(T|lataEnnur+&J|&A#IF~nUldP8TZ^T0)1`B;2R2Urd~$X|IxWp$ z9xNe`t%P(%QP_&XR!p{HP2UOjou!-pRK$!WG))#VT_UnsAq$->T(a=Uf}R;xWQS(iGb_9|TvR9W;3{TD^+ffVPl}TgZZ4xP_2Rn@Ax|1P~0|M7D2{CPFea zn!9*^=l;FGukOD=u^Vtt2IGL$sMRc8S={x!`*)ok?@>io{*qA_QtFV3$x|>*5qX}< z)29##BJ?2(_7MVgN}x&!^e}yjgyB5EF`37-27^WAHD~BdvCRe&F(_>`CL&ls*mP6`3VBLcC==1s;yL)dfy{#tex+wWZi`A6Gp~|m-fQ^clzD{q&{1?n)-Vma1wD!yqF3=@&^*AM z-lIZIGiA-T7Rbe7KK34|+m7MLW^z4fReV(7xPh#g^sYu=JDRBx;;2yMU(Dd1LCrpX zpVIq1*H%ckz7Qm zU>A&nRZt6hAt<;7uizKVBJzsJk7i^i9u^1A$Pk-<#ZxnlZPCSyESfMLof+BRvsnd7 zjnxT(ea31!VT4&Knet@ji!jrLk76Md&&X`YW-~Gvtou)Z^kiCr@L-LQEAykZd8B8L z@uWu^etJe28S8G@=^35$*lf~+Tup{aA(`e>kZYI@`<18(3iO8z?XA)2naZ;pGnmJv(hh#`aeSr0B|H||D_?df1M36 zi5WnQHAic#i94R7sQt(NcV4f^cZyWKKA`^|*eSv|$SK=g|IHZ!`gW0_uv4U1cX&HR zW~^~&UkRE&)7l|MYDn#$oI7x=Vb@oqu_mScM?D?O%h)`oGZs zL9-Y7KMv*WV>b}`|LuzK$Ru-#$STOZM>!rDq5nrCw`xDO_b8q}3jH5U-A|?dpXlH0 ztpX2I98V7>aAd1bQvY|Ty+)(<8eac5sVy#JLvo0BRFh*DACg1Uj=Kc~T)N;FGpuk7 zdOy2Fr2n6j{#iO!iv0hxb7=zr#Q=Ox`uD=g62<_`md?#3c7T_ppA}Cj~-kLjnPqaljTf0bvth=23wNsAReefe46)aXZI2-p34dI0zOs<13^??!hi_ zeY8z1<$l_XpS50491QgMqgyY?xfr8B@C|*i3-tP0j9n-a?1FZYST8E?K`#xTi{Him zeBu8OmY4%x`2R7&fkz1l|34dr`sne2@c&16O@YXylX{f#u+u(z9EePz{|o(J`2W?R z{Qrsm9qa#2qW^n7a$V0im=-*~CG-Dh-KJRoCqBqg(b4e(#8Bw*kz0=R|5v1ckWLh( z;)JA3f2(}?p?~Y)t#<(EuTKB^>&x%H`))K-JT3i4;dBvPVKf3@>eAF76{Rz=if>b` z9l2~JM*sV4OTbp*cmSDhQP8+`eWn{ArW?4}5Hk!`N&)mHhKKuU2)047QU=5Lgn?sO zF~mpafK!QC7p}$H4@eMWEXe@dW%ai63IYDOl?RZIFxo zuvMS+!GR((ike^#4tGvj zC&)2_BP-~+1F;qC`Vcw=R*Clj#|PGl$%~StM*|If?CVPaN6i7ye^$VIga!le5fZ@r zip84=bOF2v;Js5st|U$W@c17VrL*z$h=J-6gFd2o!sM`7(P%TLaZi|jz=ou>Pjr|G z6THZzi?Y-65CCLF{#wP<0o)t~w1!sbbAu;zFnvNN-DEr>m}n#$ss7ZAXG{$KXAeDi z#-zIGfgUi+m{dRoQ&K%!iDB?aHj{xg=~`i?5zm+i{4ex1e&md!>wbe~p-ge|hn+md z>P(PseS`G#s+G9wkqS|;3=%JMb5f3{PQp{_?+gr{r*UKf%9}3_5tlmBgqb6blg++U zWQ|Qkj%LvDvoJ-b`1?fvX0Iqj63703+e1EtNLyo6r0J!Y{-2FY=KTL7#~;T}5Csl<$JYRC?KYsLw^z)>9-D9zB- zeBYn(4P6Um7()Xj>rSX@Gc2xfhWRLl0plLEV-_<2;{A7)-XNNI;NvLbC>;2e7R1Bu z?mc=nb1+03Z|**1amMVZ$r71LXOG^8D=pO??FZGpJ?9ZS>j@Ar5rL#(YM-HY437q8 zD)?B-wqEOCqG-}bc-JQ1%)tJLP_1M|?3|>&g z|3?V}XsCA_^M6Ie|05@DWpWH^mGyf-AW-)`ad!hTXDu2 zNKbX~*>oQGL{A{^v}UK0TB|U-)d)^TaUrR-Moa*BouH>=Sa;?qk_y?DMvp%k1Q za8Z)J{By{C_@OlMxexHfbmDvmFU6POfZ%}OfZ%}OfZ%}OfZ%}OfZ%}OfZ%}Oz*Ect zTJ$Zk>@fWwX&nAvRYMD~<7053XD&hg|BwZQKy+AW5*Pm;)r=$YQo{uy#Sak27-jDH z7(c=NKljnxH>A>8sZgAlEKN<%JU2Uc?D&b3r%peA=7qD;x$T^_R=#<3DAT)T{MN%|^LXt*tf7^H;ZOYb*8nrTOYwZR<*V{=#g; zs`-`1e7Ux=yt3XYFE`hloA6+ba_vUB(QH+>fZ}SqywYf{cUC$#%I)U0t>${2zgYW5 zyVJbVWqZ2yTCIuXmfDqdt(c165d-&$K+Bs5@(y6e>| z&G~ZuQgyRh?*I?qsBYd^Szo@O==#F!!Yk#MkAIW0nB9$gsKe3G?o>BBM?(tzC1zys^&hV{DH& zzVKT4)#LX;AmA}`ng`mmQLb;Sw>z8FsBx0^OEyOL;0U>ZFI`?)Z#1uS$ES%p4-&FN zA0kgcHaj4;T}&_&6W~*{jgywXZP;i3?7{`O<*yDBN__n%C6%_>kQsDRUd&H=*1%2wu#`=7@wYE|1T+r18DmD?R1ADzx z_n17(?7~+@l&GQT9!a8>K%$yMOO%$8DB4I^p1un5wBKKz$YDcC6Wn1mY0~B=Pr);= zt}xqawpQyMrD`-+)mp1vH#=9?n{ReDH!fecpc9Nb*ls=ZOtgojy(SrDaeo;NbnS9$ zWwYH`m^=+n`$?hy(kYOXZ=Lmsj7swsNCp8HToQ>Ye3=Rb4andTaG+a@>CI)tXYj z)>&1Wo2{y4Zfsn=tkf^lo8`T3n4i5ec^26C;~X{~^a{3CbBW3FFPe5nH}A*3+>0bn z)SrEHE}IfYQ0UGayIVL>kO~r#mT%p@6{&c~kNrvUbhHUR6R)5A(S&p?&Pc&fxrh~h zL@G-}q4h`Q4l2RTYH$Ay%Oa5uQ5fi1w{gwwkd?G%<%DTPYsg_6Io1{3hV*Aq5(yvb zGixl10H0BUi_?e7tSprk$$1k(79B1DQ<6l0#fh^#?F@=yq*C&vs)IvqAfmpp>ee*> z5^>}rRR;1%x(h}1z259liBA~OjBe>{!3wLr1qq&r5}7S-Z9t--r0QQPUyuX4`qj23 zo@HdAgg8Brdx_&Xr_$6_>C)CQ!aC8g)0wOCK|G1#J4&Juu33|EiJ;75h)gCmh@SDM zv$P%xlq5{~)(Mt&kZ*mCo(qWrI6ph2E1LMidpKRu#Qa5gt>DFa^-lx85-NS!nY6X= z|6^{6FTnx90l@*m0l@*m0l@*m0l@*m0l@*m0l|T>aDe*%6aRZ`3DPSQFJJ0+7M@%puKR}#6KIh(t*c_$*yIA@ksrbU6^nWa1?+Ir8dn%Isjg;7I zxCC(`kssu@8;P&OgkulKdy&ZRZDr}sgL{y6GFXCKhdov8$KEbuzu;#OcQ3tE`p?2~ z?0AWarCi<%!dt;@FcH5{oGlj0(iC+4OGqXQL>3NNxVZYBg1=S#t&4hm=)_KxE8vLM z;}c~8L_R*(vZLI^iw~Ub3QKK#P(D6V7NB(PWP3t789QP7_+FV4mgPu72HRq?6>ALs z**w1|?^sM_K0B1xvl5j^vU|OZR^p%S>jG6{KXRT6G@s7HI6#M>v#&QaDT#C>y`izM zHqnOCzS?x*nUt41an=rpQqC5|1q5`gxWvF^-UlDVZOn4D4V@whFagBL%_<;KtN z$6x_7uHq0N%ZRZ8fdOThdgwF~I!S(Y4cziPRv@O6#gvn@ivuwYq(GPEQaUrGg>(|; zt$7KZ327jUmag~GX(n{iL~x4B(;5C5!(LbvHlSxpIHWxDDbxUzHcKPYDMJATff=eJ znsX|LHtv14uwz~~9Gl+Y3QqWoQsMVL1Ooyr--qAuDgOSa-t7t}g%+p(o4OD2|4$FO-(xG3&FTNPquQ$f1mpj+Wi&be|EL8s z;|GZAVIIQKpLa@>|9@Wk1L;Ijg2HOb^tZ~FANsc*-g-w;r+@wR$e|Ur)=BB3!f7}{ z!nN0?N>l$^lukXa6`Pr}ZDG!aA|pvWPulD*%-Q$vIy>H@imVHB_OoQp{@a7h*}3n` zp{&^zBy;77xu7#UvAg%RZ6`jUWX#EUSyJJ8Dug@Utw8nSJI*^5G$IzP$9ut17sJDN z%e$4eyi*qm1S^hid8e6bJsFB2KqQ>BSj3TVk_PM1$GRSQ?)M#cJ6KYWVK+YcVe&t$ zlVneD=kD6Bd&Rk%cK8ebe~4a`W4fp{)Pb)tBc833VDF!2;7z}RC7wiAw;$7!0s4&UE7PS7KofIEa zjWA56pDu=0nD#hf3z7c+3e^AW(f>>D7kCT6Y3bi03jkCJgdK3s)IUwNiqh$^*#MX^ zz!Am(VGO81dXM{tx{urv@Ydd!hf6GmFlIdKECXh5~w1w@1qbW*t8Fdk|n@p8FYyhxyCGYN|Z;$kU zQ~I7XSCR_ns?Ph6J~VRrm(DFapGD$wPT#%k0zXgDq0$gDVI4Y)qA245RUYJ|1FZOVw9FzPWy@7if`r8E(Z>)%U!(&Mj zSjPEZo|>39VHbGD>;iFa(={(n4^T@v~Kws#?qSGEKiZ{-sK zll^@mB49FnmN1e6{|^HP!~b*ocWeOg69a%2dXDFZ#?X55um>{zKVbSlU@HM(nQ}xR zz)X^Ki%{W_ga9xE07g@)$oFFdfN85-2ta@h07y5-1ziCBf9|8Xn^NhlR47hNmZqj> zo|~OJcKpQ2Q>ULl^TJu_T=})@?PjM;DzkQLTPtggvVx$Z{rvF{ScUVgHdJ+AY2hOG z&FfG#9jfQHtMlbHRQ)ZVS7fL*uDhBYn6jY;+WGROYWq_8d}uhTsyP9`S`EbyJl8;W zpdmLvp5?1*Xc}6myN2z2xsdrr-?cb62hup z=Ltxv{4x;}TAZ4bmcDJ+XaDTN1-Rv}4iZXM2>FRfrENCkhGsND4E1VP(~Ww!CO6Ek zY3sVGHsO_2YrRrQ*f2;eX@z!paW=xS9{>DpCA=^72_*Q~m3)iu3Gj2c^;)y~SsI@H5n+o*Of z=;{I$n~2oz5aN~8?Is=d?7~+@l&GQT9!a8>K%$yMOH^J(@~9m6S3#ck`^yt)%b}zR z?l77(Y4ek(;2Bs~nC&!MtM!gjHJYnxt<|oZovZ83H#?gfmoHnLq@?@6qV-WX-dk+_ zG(7DmImOm#A4|&K=RfrT(nhJdKE(44Uvqu(dEn(Pm4{wlUVZc0%8i<37}~n2ca|Gg zbf{T+&bM;e*=cIat2L#5t+T2$H(OQ9+}OB!S*c&9H_Ln7l8z+B#~pjWW9noCTUf9!?m$G+T)Bu~_zef*?~aZi$-lS(%W(rQ;nYNsT;g$boe$guhPs z>(VZ<|4$6>(e~GUp9NxY_RTc!09%x#l1$~A8yR*qsB4UoNW42Sn{1A``v$oaO_o#+ zIdeQ1dSrKE;Uc&bKOZ>{rHL>6952O};DF$O;DF$O;DF$O;DF%3)6W5l|L6SgvHw3v z{QtV{8(t8O4@b}N0nGm&GXFm*7Mbz?tIYq;3PaKqfF{P=sfxw<|2;=ZP<$cr{~Nl) zZupo>;{ShJ`2Wkb8|6l`Row!_LbY99X*Ab6E1er<=3VDJ-iPzM_w^kQ@`MTh|NPah z+S*EeeraC#|3{X}XUhL?=)(Um{Qo={$`<~A+;j3sDB=G<7~e3w%T@UQ4@Nlj=ce%g zFU=1C5gg9{FD)Kx6pj}mjTcGbb$KrcZw0qY=dRoe9=aaX^ztr044}5xZNLirV`;7^ zz4V!41sG@`5TF488VIe>_i3!`Vh($ diff --git a/front/src/App.vue b/front/src/App.vue index faa9919..6ec217e 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -1,16 +1,57 @@ - - + + \ No newline at end of file diff --git a/front/src/components/Header.vue b/front/src/components/Header.vue index 6c54313..9ad7516 100644 --- a/front/src/components/Header.vue +++ b/front/src/components/Header.vue @@ -15,6 +15,12 @@ + + @@ -46,6 +52,10 @@ \ No newline at end of file diff --git a/front/src/pages/error.vue b/front/src/pages/error.vue new file mode 100644 index 0000000..f6d5da1 --- /dev/null +++ b/front/src/pages/error.vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/front/src/pages/login.vue b/front/src/pages/login.vue new file mode 100644 index 0000000..394c12b --- /dev/null +++ b/front/src/pages/login.vue @@ -0,0 +1,83 @@ + + + \ No newline at end of file diff --git a/front/src/pages/registration.vue b/front/src/pages/registration.vue new file mode 100644 index 0000000..e75a2e7 --- /dev/null +++ b/front/src/pages/registration.vue @@ -0,0 +1,46 @@ + + \ No newline at end of file diff --git a/front/src/pages/users.vue b/front/src/pages/users.vue new file mode 100644 index 0000000..3543375 --- /dev/null +++ b/front/src/pages/users.vue @@ -0,0 +1,52 @@ + + + \ No newline at end of file diff --git a/front/src/router/index.js b/front/src/router/index.js index 046f7ce..3450bef 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -2,14 +2,22 @@ import artists from "../pages/artists.vue" import albums from "../pages/albums.vue" import songs from "../pages/songs.vue" import find from "../pages/find.vue" +import users from "../pages/users.vue"; +import login from "../pages/login.vue"; +import registration from "../pages/registration.vue"; +import error from "../pages/error.vue"; import {createRouter, createWebHistory} from "vue-router" const routes = [ - {path: '/artists', component: artists}, - {path: '/albums', component: albums}, - {path: '/songs', component: songs}, - {path: '/find', component: find}, + {path: '/songs', component: songs, meta: { requiresAuth: true }}, + {path: '/albums', component: albums, meta: { requiresAuth: true }}, + {path: '/artists', component: artists, meta: { requiresAuth: true }}, + {path: '/find', component: find, meta: { requiresAuth: true }}, + { path: "/users", component: users, meta: { requiresAuth: true, requiresAdmin: true }}, + { path: "/login", component: login}, + { path: "/registration", component: registration}, + { path: "/error", component: error, meta: { requiresAuth: true }}, ] const router = createRouter({ @@ -18,4 +26,22 @@ const router = createRouter({ routes }) +router.beforeEach((to, from, next) => { + const isAuthenticated = localStorage.getItem("token"); + if (to.matched.some((route) => route.meta.requiresAuth)) { + if (!isAuthenticated) { + next("/login"); + return; + } + } + const isAdmin = localStorage.getItem("role") === "ADMIN"; + if (to.matched.some((route) => route.meta.requiresAdmin)) { + if (!isAdmin) { + next("/error"); + return; + } + } + next(); +}); + export default router; \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/Repository/IUserRepository.java b/src/main/java/ru/ulstu/is/sbapp/Repository/IUserRepository.java new file mode 100644 index 0000000..038ad52 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/Repository/IUserRepository.java @@ -0,0 +1,8 @@ +package ru.ulstu.is.sbapp.Repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.is.sbapp.database.model.User; + +public interface IUserRepository extends JpaRepository { + User findOneByLoginIgnoreCase(String login); +} diff --git a/src/main/java/ru/ulstu/is/sbapp/WebConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/WebConfiguration.java deleted file mode 100644 index 35da531..0000000 --- a/src/main/java/ru/ulstu/is/sbapp/WebConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.ulstu.is.sbapp; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -class WebConfiguration implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedMethods("*"); - } -} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/JwtException.java b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtException.java new file mode 100644 index 0000000..0361eca --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtException.java @@ -0,0 +1,11 @@ +package ru.ulstu.is.sbapp.configuration; + +public class JwtException extends RuntimeException { + public JwtException(Throwable throwable) { + super(throwable); + } + + public JwtException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/JwtFilter.java b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtFilter.java new file mode 100644 index 0000000..bdad217 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtFilter.java @@ -0,0 +1,72 @@ +package ru.ulstu.is.sbapp.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; +import ru.ulstu.is.sbapp.database.service.UserService; + +import java.io.IOException; + +public class JwtFilter extends GenericFilterBean { + private static final String AUTHORIZATION = "Authorization"; + public static final String TOKEN_BEGIN_STR = "Bearer "; + + private final UserService userService; + + public JwtFilter(UserService userService) { + this.userService = userService; + } + + private String getTokenFromRequest(HttpServletRequest request) { + String bearer = request.getHeader(AUTHORIZATION); + if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) { + return bearer.substring(TOKEN_BEGIN_STR.length()); + } + return null; + } + + private void raiseException(ServletResponse response, int status, String message) throws IOException { + if (response instanceof final HttpServletResponse httpResponse) { + httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); + httpResponse.setStatus(status); + final byte[] body = new ObjectMapper().writeValueAsBytes(message); + response.getOutputStream().write(body); + } + } + + @Override + public void doFilter(ServletRequest request, + ServletResponse response, + FilterChain chain) throws IOException, ServletException { + if (request instanceof final HttpServletRequest httpRequest) { + final String token = getTokenFromRequest(httpRequest); + if (StringUtils.hasText(token)) { + try { + final UserDetails user = userService.loadUserByToken(token); + final UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (JwtException e) { + raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); + return; + } catch (Exception e) { + e.printStackTrace(); + raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + String.format("Internal error: %s", e.getMessage())); + return; + } + } + } + chain.doFilter(request, response); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProperties.java b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProperties.java new file mode 100644 index 0000000..e407e7c --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProperties.java @@ -0,0 +1,27 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true) +public class JwtProperties { + private String devToken = ""; + private Boolean isDev = true; + + public String getDevToken() { + return devToken; + } + + public void setDevToken(String devToken) { + this.devToken = devToken; + } + + public Boolean isDev() { + return isDev; + } + + public void setDev(Boolean dev) { + isDev = dev; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProvider.java b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProvider.java new file mode 100644 index 0000000..0659755 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/JwtProvider.java @@ -0,0 +1,108 @@ +package ru.ulstu.is.sbapp.configuration; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.Optional; +import java.util.UUID; + +@Component +public class JwtProvider { + private final static Logger LOG = LoggerFactory.getLogger(JwtProvider.class); + + private final static byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + private final static String ISSUER = "auth0"; + + private final Algorithm algorithm; + private final JWTVerifier verifier; + + public JwtProvider(JwtProperties jwtProperties) { + if (!jwtProperties.isDev()) { + LOG.info("Generate new JWT key for prod"); + try { + final MessageDigest salt = MessageDigest.getInstance("SHA-256"); + salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); + LOG.info("Use generated JWT key for prod \n{}", bytesToHex(salt.digest())); + algorithm = Algorithm.HMAC256(bytesToHex(salt.digest())); + } catch (NoSuchAlgorithmException e) { + throw new JwtException(e); + } + } else { + LOG.info("Use default JWT key for dev \n{}", jwtProperties.getDevToken()); + algorithm = Algorithm.HMAC256(jwtProperties.getDevToken()); + } + verifier = JWT.require(algorithm) + .withIssuer(ISSUER) + .build(); + } + + private static String bytesToHex(byte[] bytes) { + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } + + public String generateToken(String login) { + final Date issueDate = Date.from(LocalDate.now() + .atStartOfDay(ZoneId.systemDefault()) + .toInstant()); + final Date expireDate = Date.from(LocalDate.now() + .plusDays(15) + .atStartOfDay(ZoneId.systemDefault()) + .toInstant()); + var temp = JWT.create(); + var temp2 = temp.withIssuer(ISSUER); + var temp3 = temp2.withIssuedAt(issueDate); + var temp4 = temp3.withExpiresAt(expireDate); + var temp5 = temp4.withSubject(login); + var temp6 = temp5.sign(algorithm); + return temp6; + } + + private DecodedJWT validateToken(String token) { + try { + return verifier.verify(token); + } catch (JWTVerificationException e) { + throw new JwtException(String.format("Token verification error: %s", e.getMessage())); + } + } + + public boolean isTokenValid(String token) { + if (!StringUtils.hasText(token)) { + return false; + } + try { + validateToken(token); + return true; + } catch (JwtException e) { + LOG.error(e.getMessage()); + return false; + } + } + + public Optional getLoginFromToken(String token) { + try { + return Optional.ofNullable(validateToken(token).getSubject()); + } catch (JwtException e) { + LOG.error(e.getMessage()); + return Optional.empty(); + } + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java new file mode 100644 index 0000000..9516c54 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java @@ -0,0 +1,27 @@ +package ru.ulstu.is.sbapp.configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenAPI30Configuration { + public static final String API_PREFIX = "/api/1.0"; + + @Bean + public OpenAPI customizeOpenAPI() { + final String securitySchemeName = JwtFilter.TOKEN_BEGIN_STR; + return new OpenAPI() + .addSecurityItem(new SecurityRequirement() + .addList(securitySchemeName)) + .components(new Components() + .addSecuritySchemes(securitySchemeName, new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..fadfa18 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public PasswordEncoder createPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java new file mode 100644 index 0000000..b3362c0 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java @@ -0,0 +1,83 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +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.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import ru.ulstu.is.sbapp.controllers.UserController; +import ru.ulstu.is.sbapp.database.model.Role; +import ru.ulstu.is.sbapp.database.service.UserService; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity( + securedEnabled = true +) +public class SecurityConfiguration { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static final String LOGIN_URL = "/login"; + public static final String SPA_URL_MASK = "/{path:[^\\.]*}"; + + private final UserService userService; + private final JwtFilter jwtFilter; + + public SecurityConfiguration(UserService userService) + { + this.userService = userService; + this.jwtFilter = new JwtFilter(userService); + createAdminOnStartup(); + } + + private void createAdminOnStartup() { + final String admin = "admin"; + if (userService.findByLogin(admin) == null) { + log.info("Admin user successfully created"); + userService.addUser(admin, admin, admin, Role.ADMIN); + } + } + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.cors() + .and() + .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeHttpRequests() + .requestMatchers("", SPA_URL_MASK).permitAll() + .requestMatchers("/", SPA_URL_MASK).permitAll() + .requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll() + .requestMatchers(HttpMethod.POST, UserController.URL_SIGN_UP).permitAll() + .requestMatchers(HttpMethod.POST, UserController.URL_WHO_AM_I).permitAll() + .anyRequest() + .authenticated() + .and() + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .anonymous(); + return http.userDetailsService(userService).build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring() + .requestMatchers(HttpMethod.OPTIONS, "/**") + .requestMatchers("/*.js") + .requestMatchers("/*.html") + .requestMatchers("/*.css") + .requestMatchers("/assets/**") + .requestMatchers("/favicon.ico") + .requestMatchers("/.js", "/.css") + .requestMatchers("/swagger-ui/index.html") + .requestMatchers("/webjars/**") + .requestMatchers("/swagger-resources/**") + .requestMatchers("/v3/api-docs/**") + .requestMatchers("/h2-console/**"); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/TypeConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/TypeConfiguration.java deleted file mode 100644 index c61c87e..0000000 --- a/src/main/java/ru/ulstu/is/sbapp/configuration/TypeConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.ulstu.is.sbapp.configuration; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import ru.ulstu.is.sbapp.interfaces.DoubleType; -import ru.ulstu.is.sbapp.interfaces.StringType; - -@Configuration -public class TypeConfiguration { - - @Bean(value = "str") - public StringType createStrType(){ - return new StringType(); - } - - @Bean(value = "double") - public DoubleType createDoubleType(){ - return new DoubleType(); - } -} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java new file mode 100644 index 0000000..43f40d5 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java @@ -0,0 +1,29 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfiguration implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController(SecurityConfiguration.SPA_URL_MASK).setViewName("forward:/"); + registry.addViewController("/notFound").setViewName("forward:/"); + } + @Bean + public WebServerFactoryCustomizer containerCustomizer() { + return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notFound")); + } + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedMethods("*"); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/AlbumController.java b/src/main/java/ru/ulstu/is/sbapp/controllers/AlbumController.java index bcce229..f66cf45 100644 --- a/src/main/java/ru/ulstu/is/sbapp/controllers/AlbumController.java +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/AlbumController.java @@ -3,6 +3,7 @@ package ru.ulstu.is.sbapp.controllers; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import ru.ulstu.is.sbapp.configuration.OpenAPI30Configuration; import ru.ulstu.is.sbapp.database.model.Artist; import ru.ulstu.is.sbapp.database.model.Song; import ru.ulstu.is.sbapp.database.service.AlbumService; @@ -11,7 +12,7 @@ import java.util.List; import java.util.Map; @RestController -@RequestMapping("/album") +@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/album") public class AlbumController { private final AlbumService albumService; diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/ArtistController.java b/src/main/java/ru/ulstu/is/sbapp/controllers/ArtistController.java index c0f724f..7259af9 100644 --- a/src/main/java/ru/ulstu/is/sbapp/controllers/ArtistController.java +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/ArtistController.java @@ -2,12 +2,13 @@ package ru.ulstu.is.sbapp.controllers; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.*; +import ru.ulstu.is.sbapp.configuration.OpenAPI30Configuration; import ru.ulstu.is.sbapp.database.service.ArtistService; import java.util.List; @RestController -@RequestMapping("/artist") +@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/artist") public class ArtistController { private final ArtistService artistService; diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/SearchController.java b/src/main/java/ru/ulstu/is/sbapp/controllers/SearchController.java index 470cf15..45ae60a 100644 --- a/src/main/java/ru/ulstu/is/sbapp/controllers/SearchController.java +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/SearchController.java @@ -4,13 +4,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import ru.ulstu.is.sbapp.configuration.OpenAPI30Configuration; import ru.ulstu.is.sbapp.database.service.FindByNameService; import java.util.List; import java.util.Map; @RestController -@RequestMapping("/find") +@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/find") public class SearchController { private final FindByNameService findService; diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/SongController.java b/src/main/java/ru/ulstu/is/sbapp/controllers/SongController.java index 7267a95..e8fb5f8 100644 --- a/src/main/java/ru/ulstu/is/sbapp/controllers/SongController.java +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/SongController.java @@ -2,13 +2,14 @@ package ru.ulstu.is.sbapp.controllers; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.*; +import ru.ulstu.is.sbapp.configuration.OpenAPI30Configuration; import ru.ulstu.is.sbapp.database.service.AlbumService; import ru.ulstu.is.sbapp.database.service.SongService; import java.util.List; @RestController -@RequestMapping("/song") +@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/song") public class SongController { private final SongService songService; private final AlbumService albumService; diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/UserController.java b/src/main/java/ru/ulstu/is/sbapp/controllers/UserController.java new file mode 100644 index 0000000..533e445 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/UserController.java @@ -0,0 +1,64 @@ +package ru.ulstu.is.sbapp.controllers; + +import jakarta.validation.Valid; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.*; +import ru.ulstu.is.sbapp.database.model.User; +import ru.ulstu.is.sbapp.database.model.Role; +import ru.ulstu.is.sbapp.database.service.UserService; +import ru.ulstu.is.sbapp.database.util.validation.ValidationException; + +import java.util.List; + +@RestController +public class UserController { + public static final String URL_LOGIN = "/jwt/login"; + public static final String URL_SIGN_UP = "/sign_up"; + public static final String URL_WHO_AM_I = "/who_am_i"; + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + @PostMapping(URL_LOGIN) + public String login(@RequestBody @Valid UserDTO userDto) { + return userService.loginAndGetToken(userDto); + } + @GetMapping(URL_WHO_AM_I) + public String role(@RequestParam("userLogin") String userLogin) { + return userService.findByLogin(userLogin).getUserRole().name(); + } + @PostMapping(URL_SIGN_UP) + public String signUp(@RequestBody @Valid UserSignUpDTO userSignupDto) { + try { + final User user = userService.addUser(userSignupDto.getLogin(), + userSignupDto.getPassword(), userSignupDto.getPasswordConfirm(), Role.USER); + return "created " + user.getLogin(); + } catch (ValidationException e) { + return e.getMessage(); + } + } + @GetMapping("/{id}") + @Secured({Role.AsString.ADMIN}) + public UserDTO getUser(@PathVariable Long id) { + return new UserDTO(userService.findUser(id)); + } + @GetMapping("/") + @Secured({Role.AsString.ADMIN}) + public List getUsers() { + return userService.findAllUsers().stream() + .map(UserDTO::new) + .toList(); + } + + @PutMapping("/{id}") + public UserDTO updateUser(@RequestBody @Valid UserDTO userDto){ + return new UserDTO(userService.updateUser(userDto)); + } + + + @DeleteMapping("/{id}") + public void deleteUser(@PathVariable Long id) { + userService.deleteUser(id); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/UserDTO.java b/src/main/java/ru/ulstu/is/sbapp/controllers/UserDTO.java new file mode 100644 index 0000000..04dde8f --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/UserDTO.java @@ -0,0 +1,52 @@ +package ru.ulstu.is.sbapp.controllers; + +import ru.ulstu.is.sbapp.database.model.User; +import ru.ulstu.is.sbapp.database.model.Role; + +public class UserDTO { + private Long id; + private Role role; + private String login; + private String password; + + public UserDTO(){} + + public UserDTO(User user) { + this.id=user.getId(); + this.login= user.getLogin(); + this.password = user.getPassword(); + this.role = user.getUserRole(); + } + + public Long getId() { + return id; + } + + public String getLogin() + { + return login; + } + + public String getPassword() + { + return password; + } + + public Role getUserRole() { + return role; + } + + public void setId(Long id) { + this.id = id; + } + + public void setLogin(String login) + { + this.login = login; + } + + public void setPassword(String password) + { + this.password = password; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/controllers/UserSignUpDTO.java b/src/main/java/ru/ulstu/is/sbapp/controllers/UserSignUpDTO.java new file mode 100644 index 0000000..471579e --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/controllers/UserSignUpDTO.java @@ -0,0 +1,36 @@ +package ru.ulstu.is.sbapp.controllers; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UserSignUpDTO { + private String login; + + private String password; + + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public String getPasswordConfirm() { + return passwordConfirm; + } + + public String getPassword() { + return password; + } + + public void setLogin(String login) { + this.login = login; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/database/model/Role.java b/src/main/java/ru/ulstu/is/sbapp/database/model/Role.java new file mode 100644 index 0000000..4000824 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/database/model/Role.java @@ -0,0 +1,20 @@ +package ru.ulstu.is.sbapp.database.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum Role implements GrantedAuthority { + ADMIN, + USER; + + private static final String PREFIX = "ROLE_"; + + @Override + public String getAuthority() { + return PREFIX + this.name(); + } + + public static final class AsString { + public static final String ADMIN = PREFIX + "ADMIN"; + public static final String USER = PREFIX + "USER"; + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/database/model/User.java b/src/main/java/ru/ulstu/is/sbapp/database/model/User.java new file mode 100644 index 0000000..b32a3d4 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/database/model/User.java @@ -0,0 +1,72 @@ +package ru.ulstu.is.sbapp.database.model; + +import jakarta.persistence.*; +import ru.ulstu.is.sbapp.controllers.UserSignUpDTO; + +import java.util.Objects; + +@Entity +@Table(name = "users") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String login; + + private String password; + + private Role role; + + public User(){ + } + + public User(UserSignUpDTO userDto){ + this.login = userDto.getLogin(); + this.password = userDto.getPassword(); + this.role = Role.USER; + } + + public User(String login, String password, Role role){ + this.login = login; + this.password = password; + this.role = role; + } + + public Long getId(){return id;} + + public String getLogin(){return login;} + + public void setLogin(String login){this.login = login;} + + public String getPassword(){return password;} + + public void setPassword(String password){this.password = password;} + + public Role getUserRole() { + return role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", login='" + login + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/database/service/UserExistsException.java b/src/main/java/ru/ulstu/is/sbapp/database/service/UserExistsException.java new file mode 100644 index 0000000..bb9fa81 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/database/service/UserExistsException.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.database.service; + +public class UserExistsException extends RuntimeException { + public UserExistsException(String login) { + super(String.format("User '%s' already exists", login)); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/database/service/UserNotFoundException.java b/src/main/java/ru/ulstu/is/sbapp/database/service/UserNotFoundException.java new file mode 100644 index 0000000..8f7c2af --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/database/service/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.database.service; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(Long id) { + super(String.format("User not found '%s'", id)); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/database/service/UserService.java b/src/main/java/ru/ulstu/is/sbapp/database/service/UserService.java new file mode 100644 index 0000000..052cdb6 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/database/service/UserService.java @@ -0,0 +1,130 @@ +package ru.ulstu.is.sbapp.database.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import ru.ulstu.is.sbapp.Repository.IUserRepository; +import ru.ulstu.is.sbapp.configuration.JwtException; +import ru.ulstu.is.sbapp.configuration.JwtProvider; +import ru.ulstu.is.sbapp.controllers.UserDTO; +import ru.ulstu.is.sbapp.controllers.UserSignUpDTO; +import ru.ulstu.is.sbapp.database.model.User; +import ru.ulstu.is.sbapp.database.model.Role; +import ru.ulstu.is.sbapp.database.util.validation.ValidationException; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Service +public class UserService implements UserDetailsService { + private final IUserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final JwtProvider jwtProvider; + + public UserService(IUserRepository userRepository, + PasswordEncoder passwordEncoder, + JwtProvider jwtProvider){ + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.jwtProvider = jwtProvider; + } + public User findByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + public Page findAllPages(int page, int size) { + return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending())); + } + @Transactional + public User addUser(UserSignUpDTO userDto){ + final User user = new User(userDto); + userRepository.save(user); + return user; + } + @Transactional + public User addUser(String login, String password, + String passwordConfirm, + Role role){ + if (findByLogin(login) != null) { + throw new ValidationException(String.format("User '%s' already exists", login)); + } + if (!Objects.equals(password, passwordConfirm)) { + throw new ValidationException("Passwords not equals"); + } + final User user = new User(login,passwordEncoder.encode(password),role); + return userRepository.save(user); + } + @Transactional(readOnly = true) + public User findUser(Long id) { + final Optional user = userRepository.findById(id); + return user.orElseThrow(() -> new UserNotFoundException(id)); + } + + @Transactional(readOnly = true) + public List findAllUsers() { + return userRepository.findAll(); + } + + @Transactional + public User updateUser(UserDTO userDto) { + final User currentUser = findUser(userDto.getId()); + currentUser.setLogin(userDto.getLogin()); + currentUser.setPassword(userDto.getPassword()); + return userRepository.save(currentUser); + } + @Transactional + public User updateUser(Long id,String login, String password) { + if (!StringUtils.hasText(login) || !StringUtils.hasText(password)) { + throw new IllegalArgumentException("User name, login or password is null or empty"); + } + final User currentUser = findUser(id); + currentUser.setLogin(login); + currentUser.setPassword(password); + return userRepository.save(currentUser); + } + @Transactional + public void deleteUser(Long id) { + userRepository.deleteById(id); + } + + @Transactional + public void deleteAllUsers() { + userRepository.deleteAll(); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User userEntity = findByLogin(username); + if (userEntity == null) { + throw new UsernameNotFoundException(username); + } + return new org.springframework.security.core.userdetails.User( + userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getUserRole())); + } + public String loginAndGetToken(UserDTO userDto) { + final User user = findByLogin(userDto.getLogin()); + if (user == null) { + throw new UserNotFoundException(userDto.getId()); + } + if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) { + throw new UserNotFoundException(user.getId()); + } + return jwtProvider.generateToken(user.getLogin()); + } + public UserDetails loadUserByToken(String token) throws UsernameNotFoundException { + if (!jwtProvider.isTokenValid(token)) { + throw new JwtException("Bad token"); + } + final String userLogin = jwtProvider.getLoginFromToken(token) + .orElseThrow(() -> new JwtException("Token is not contain Login")); + return loadUserByUsername(userLogin); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/database/util/validation/ValidationException.java b/src/main/java/ru/ulstu/is/sbapp/database/util/validation/ValidationException.java index 16871de..37aa8e6 100644 --- a/src/main/java/ru/ulstu/is/sbapp/database/util/validation/ValidationException.java +++ b/src/main/java/ru/ulstu/is/sbapp/database/util/validation/ValidationException.java @@ -3,6 +3,9 @@ package ru.ulstu.is.sbapp.database.util.validation; import java.util.Set; public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } public ValidationException(Set errors) { super(String.join("\n", errors)); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index da7b0b1..8162553 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.main.banner-mode=off -#server.port=8080 +server.port=8080 spring.datasource.url=jdbc:h2:file:./data spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa @@ -9,3 +9,6 @@ spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.settings.trace=false spring.h2.console.settings.web-allow-others=false +jwt.dev-token=my-secret-jwt +jwt.dev=true +jwt.secret = my-secret-jwt