Kotlin + рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ + Vue.js рдкрд░ рд╡реЗрдм рдЕрдиреБрдкреНрд░рдпреЛрдЧ

рд╢реБрдн рджреЛрдкрд╣рд░, рд╣реЗрдмрд░ рдХреЗ рдкреНрд░рд┐рдп рдирд┐рд╡рд╛рд╕рд┐рдпреЛрдВ!

рдмрд╣реБрдд рдкрд╣рд▓реЗ рдирд╣реАрдВ, рдореБрдЭреЗ рд╡рд┐рд╢реЗрд╖ рддрдХрдиреАрдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреЗ рдмрд┐рдирд╛ рдПрдХ рдЫреЛрдЯреА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХрд╛ рдЕрд╡рд╕рд░ рдорд┐рд▓рд╛ред рдпрд╣реА рд╣реИ, рдореИрдВ рдЕрдкрдиреЗ рд╡рд┐рд╡реЗрдХ рдкрд░ рдкреНрд░реМрджреНрдпреЛрдЧрд┐рдХреА рд╕реНрдЯреИрдХ рдХрд╛ рдЪрдпрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрд╡рддрдВрддреНрд░ рдерд╛ред рдЗрд╕рд▓рд┐рдП рдореИрдВрдиреЗ рдлреИрд╢рдиреЗрдмрд▓, рдпреБрд╡рд╛, рд╣реЛрдирд╣рд╛рд░, рд▓реЗрдХрд┐рди рд╡реНрдпрд╡рд╣рд╛рд░ рдореЗрдВ рдореЗрд░реЗ рд▓рд┐рдП рдЕрдкрд░рд┐рдЪрд┐рдд "рдорд╣рд╕реВрд╕" рдХрд░рдиреЗ рдХреЗ рдЕрд╡рд╕рд░ рдХреЛ рдпрд╛рдж рдирд╣реАрдВ рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди рд╡реНрдпрд╡рд╣рд╛рд░ рдореЗрдВ рдЕрдкрд░рд┐рдЪрд┐рдд рдФрд░ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкрд░рд┐рдЪрд┐рдд рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдФрд░ рдПрдХ рд╕реАрдзреА рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдкрд░ рдпрд╣ рд╕рдм рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░ рд░рд╣рд╛ рдерд╛ред

рдЬрдм рдореИрдВрдиреЗ рд╢реБрд░реВ рдХрд┐рдпрд╛, рддреЛ рдореИрдВрдиреЗ рд▓рд╛рдкрд░рд╡рд╛рд╣реА рд╕реЗ рдпрд╣ рдорд╛рдирд╛ рдХрд┐ рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рдХрдИ рд▓реЗрдЦ рдФрд░ рдореИрдиреБрдЕрд▓ рд╣реЛрдВрдЧреЗред рд╕рд╛рдордЧреНрд░реА рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдкрд░реНрдпрд╛рдкреНрдд рд╣реИрдВ, рдФрд░ рд╡реЗ рд╕рднреА рдЕрдЪреНрдЫреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдХреЗрд╡рд▓ рдкрд╣рд▓реЗ REST рдирд┐рдпрдВрддреНрд░рдХ рддрдХред рдлрд┐рд░ рд╡рд┐рд░реЛрдзрд╛рднрд╛рд╕ рдХреА рдХрдард┐рдирд╛рдЗрдпрд╛рдВ рд╢реБрд░реВ рд╣реЛрддреА рд╣реИрдВред рд▓реЗрдХрд┐рди рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдореЗрдВ, рдореИрдВ рдЙрд╕ рдкреГрд╖реНрда рдкрд░ рдЖрд░реЗрдЦрдг рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рддрд░реНрдХ рд░рдЦрдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛ рдЬреЛ рд╕рд░реНрд╡рд░ рджреНрд╡рд╛рд░рд╛ рд╡рд╛рдкрд╕ рд▓реМрдЯрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред

рдЗрд╕реЗ рдХрд┐рд╕реА рддрд░рд╣ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж, рдореИрдВрдиреЗ рдЕрдкрдирд╛ рдЦреБрдж рдХрд╛ рдореИрдиреБрдЕрд▓ рд▓рд┐рдЦрдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛, рдЬреЛ рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ, рдХрд┐рд╕реА рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛ред

рдХреНрдпрд╛ рдФрд░ рдХрд┐рд╕рдХреЗ рд▓рд┐рдП рд▓реЗрдЦ рд╣реИ


рдпрд╣ рд╕рд╛рдордЧреНрд░реА рдХреЛрдЯрд▓рд┐рди + рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рдкрд░ рдмреИрдХрдПрдВрдб рдФрд░ Vue.js. рдкрд░ рдПрдХ рджреГрд╢реНрдп рдХреЗ рд╕рд╛рде рдПрдХ рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдХреА "рддреНрд╡рд░рд┐рдд рд╢реБрд░реБрдЖрдд" рдХреЗ рд▓рд┐рдП рдПрдХ рдорд╛рд░реНрдЧрджрд░реНрд╢рд┐рдХрд╛ рд╣реИред рдореБрдЭреЗ рддреБрд░рдВрдд рдХрд╣рдирд╛ рд╣реЛрдЧрд╛ рдХрд┐ рдореИрдВ рдЙрдирдХреЗ рд▓рд┐рдП "рдбреВрдм" рдирд╣реАрдВ рд░рд╣рд╛ рд╣реВрдВ рдФрд░ рдЗрд╕ рд╕реНрдЯреИрдХ рдХреЗ рдХрд┐рд╕реА рднреА рдЕрд╕рдорд╛рди рд▓рд╛рдн рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдирд╣реАрдВ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред рдЗрд╕ рд▓реЗрдЦ рдХрд╛ рдЙрджреНрджреЗрд╢реНрдп рдЕрдиреБрднрд╡ рд╕рд╛рдЭрд╛ рдХрд░рдирд╛ рд╣реИред

рд╕рд╛рдордЧреНрд░реА рдХреЛ рдЬрд╛рд╡рд╛, рд╕реНрдкреНрд░рд┐рдВрдЧ рдлреНрд░реЗрдорд╡рд░реНрдХ / рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ, рд░рд┐рдПрдХреНрдЯ / рдПрдВрдЧреБрд▓рд░ рдпрд╛ рдХрдо рд╕реЗ рдХрдо рд╢реБрджреНрдз рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ рд╕рд╛рде рдЕрдиреБрднрд╡ рдХреЗ рд╕рд╛рде рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдЙрди рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреБрдХреНрдд рд╣реИ рдЬрд┐рдирдХреЗ рдкрд╛рд╕ рдРрд╕рд╛ рдЕрдиреБрднрд╡ рдирд╣реАрдВ рд╣реИ - рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдиреМрд╕рд┐рдЦрд┐рдП рдкреНрд░реЛрдЧреНрд░рд╛рдорд░, рд▓реЗрдХрд┐рди, рдореБрдЭреЗ рдбрд░ рд╣реИ, рддреЛ рдЖрдкрдХреЛ рдЕрдкрдиреЗ рджрдо рдкрд░ рдХреБрдЫ рд╡рд┐рд╡рд░рдгреЛрдВ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рд╣реЛрдЧрд╛ред рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдЗрд╕ рдЧрд╛рдЗрдб рдХреЗ рдХреБрдЫ рдкрд╣рд▓реБрдУрдВ рдкрд░ рдЕрдзрд┐рдХ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рд╡рд┐рдЪрд╛рд░ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП, рд▓реЗрдХрд┐рди рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЕрдиреНрдп рдкреНрд░рдХрд╛рд╢рдиреЛрдВ рдореЗрдВ рдРрд╕рд╛ рдХрд░рдирд╛ рдмреЗрд╣рддрд░ рд╣реИ, рддрд╛рдХрд┐ рд╡рд┐рд╖рдп рд╕реЗ рдЬреНрдпрд╛рджрд╛ рд╡рд┐рдЪрд▓рди рди рдХрд░реЗрдВ рдФрд░ рд▓реЗрдЦ рдХреЛ рдмреЛрдЭрд┐рд▓ рди рдмрдирд╛рдПрдВред

рд╢рд╛рдпрдж рдпрд╣ рдХрд┐рд╕реА рдХреЛ рдЗрд╕ рд╡рд┐рд╖рдп рдореЗрдВ рдЧреЛрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреЗ рдмрд┐рдирд╛ рдХреЛрдЯрд▓рд┐рди рдкрд░ рдмреИрдХрдПрдВрдб рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХрд╛ рдПрдХ рд╡рд┐рдЪрд╛рд░ рдмрдирд╛рдиреЗ рдореЗрдВ рдорджрдж рдХрд░реЗрдЧрд╛, рдФрд░ рдХрд┐рд╕реА - рдХрд╛рдо рдХреЗ рд╕рдордп рдХреЛ рдХрдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рддреИрдпрд╛рд░ рдХрд┐рдП рдЧрдП рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХрдВрдХрд╛рд▓ рдХреЗ рд░реВрдк рдореЗрдВ рд▓реЗрдирд╛ред

рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рдЪрд░рдгреЛрдВ рдХреЗ рд╡рд░реНрдгрди рдХреЗ рдмрд╛рд╡рдЬреВрдж, рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдореЗрд░реА рд░рд╛рдп рдореЗрдВ, рд▓реЗрдЦ рдореЗрдВ рдПрдХ рдкреНрд░рдпреЛрдЧрд╛рддреНрдордХ рд╕рдореАрдХреНрд╖рд╛ рдЪрд░рд┐рддреНрд░ рд╣реИред рдЕрдм рдпрд╣ рджреГрд╖реНрдЯрд┐рдХреЛрдг, рдФрд░ рд╕рд╡рд╛рд▓ рд╣реА рджреЗрдЦрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдПрдХ рд╣рд┐рдкрд╕реНрдЯрд░ рд╡рд┐рдЪрд╛рд░ рдХреЗ рд░реВрдк рдореЗрдВ рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИ - рдПрдХ рд╣реА рд╕реНрдерд╛рди рдкрд░ рдХрдИ рдлреИрд╢рдиреЗрдмрд▓ рд╢рдмреНрджреЛрдВ рдХреЛ рдЗрдХрдЯреНрдард╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред рд▓реЗрдХрд┐рди рднрд╡рд┐рд╖реНрдп рдореЗрдВ, рд╢рд╛рдпрдж, рдпрд╣ рдЙрджреНрдпрдо рд╡рд┐рдХрд╛рд╕ рдореЗрдВ рдЕрдкрдиреЗ рд╕реНрдерд╛рди рдкрд░ рдХрдмреНрдЬрд╛ рдХрд░ рд▓реЗрдЧрд╛ред рд╢рд╛рдпрдж рд╣рдорд╛рд░реЗ рдмреАрдЪ рд╢реБрд░реБрдЖрддреА (рдФрд░ рдирд┐рд░рдВрддрд░) рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рд╣реИрдВ, рдЬрд┐рдиреНрд╣реЗрдВ рдРрд╕реЗ рд╕рдордп рдореЗрдВ рд░рд╣рдирд╛ рдФрд░ рдХрд╛рдо рдХрд░рдирд╛ рд╣реИ рдЬрдм рдХреЛрдЯрд▓рд┐рди рдФрд░ рд╡реАрдпреВ.рдЬреИрд╕реЗ рд▓реЛрдХрдкреНрд░рд┐рдп рд╣реЛрдВрдЧреЗ рдФрд░ рдЬрд╛рд╡рд╛ рдФрд░ рд░рд┐рдПрдХреНрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рдорд╛рдВрдЧ рдореЗрдВ рд╣реИрдВред рд╕рдм рдХреЗ рдмрд╛рдж, Kotlin рдФрд░ Vue.js рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдЙрдЪреНрдЪ рдЙрдореНрдореАрджреЗрдВ рд╣реИрдВред

рдЙрд╕ рд╕рдордп рдХреЗ рджреМрд░рд╛рди рдЬрдм рдореИрдВрдиреЗ рдЗрд╕ рдЧрд╛рдЗрдб рдХреЛ рд▓рд┐рдЦрд╛ рдерд╛, рдЗрд╕реА рддрд░рд╣ рдХреЗ рдкреНрд░рдХрд╛рд╢рди, рдЬреИрд╕реЗ рдХрд┐ рдпрд╣ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдиреЗрдЯрд╡рд░реНрдХ рдкрд░ рджрд┐рдЦрд╛рдИ рджреЗрдиреЗ рд▓рдЧрд╛ рд╣реИред рдореИрдВ рджреЛрд╣рд░рд╛рддрд╛ рд╣реВрдВ, рдкрд░реНрдпрд╛рдкреНрдд рд╕рд╛рдордЧреНрд░реА рд╣реИ рдЬрд╣рд╛рдВ рдкрд╣рд▓реЗ рдЖрд░рдИрдПрд╕рдЯреА рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рдХрд╛рд░реНрд░рд╡рд╛рдИ рдХрд╛ рдХреНрд░рдо рд╕рдордЭрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рддрд░реНрдХ рджреЗрдЦрдирд╛ рджрд┐рд▓рдЪрд╕реНрдк рд╣реЛрдЧрд╛ - рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рднреВрдорд┐рдХрд╛рдУрдВ рджреНрд╡рд╛рд░рд╛ рдЕрд▓рдЧрд╛рд╡ рдХреЗ рд╕рд╛рде рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди, рдЬреЛ рдПрдХ рдЖрд╡рд╢реНрдпрдХ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рд╣реИред рдпрд╣реА рдореИрдВрдиреЗ рдЕрдкрдиреЗ рдиреЗрддреГрддреНрд╡ рдореЗрдВ рдЬреЛрдбрд╝рд╛ред

рд╕рд╛рдордЧреНрд░реА




рддреНрд╡рд░рд┐рдд рд╕рдВрджрд░реНрдн


Kotlin рдПрдХ рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рднрд╛рд╖рд╛ рд╣реИ рдЬреЛ JVM рдХреЗ рд╢реАрд░реНрд╖ рдкрд░ рдЪрд▓рддреА рд╣реИ рдФрд░ рдЗрд╕реЗ рдЕрдВрддрд░рд░рд╛рд╖реНрдЯреНрд░реАрдп рдХрдВрдкрдиреА JetBrains рджреНрд╡рд╛рд░рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
Vue.js рдПрдХрд▓-рдкреГрд╖реНрда рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рд╢реАрд▓-рд╢реИрд▓реА рдХреЗ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдврд╛рдВрдЪрд╛ рд╣реИред


рд╡рд┐рдХрд╛рд╕ рдХреЗ рдЙрдкрдХрд░рдг


рдПрдХ рд╡рд┐рдХрд╛рд╕ рдХреЗ рдорд╛рд╣реМрд▓ рдХреЗ рд░реВрдк рдореЗрдВ, рдореИрдВ рдЬреЗрдЯрд▓рдмреНрд░рд┐рдиреНрд╕ рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХреЗ рдорд╛рд╣реМрд▓ рдХреЛ рдЗрдВрдЯреЗрд▓реАрдЬ рдЖрдИрдбреАрдИрдП - рдЬреЛ рдХрд┐ рдЬрд╛рд╡рд╛ рд╕рдореБрджрд╛рдп рдореЗрдВ рд╡реНрдпрд╛рдкрдХ рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ, рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рд╕рд┐рдлрд╛рд░рд┐рд╢ рдХрд░реЗрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕рдореЗрдВ рдХреЛрдЯрд▓рд┐рди рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдЙрдкрдХрд░рдг рдФрд░ рд╡рд┐рд╢реЗрд╖рддрд╛рдПрдВ рд╣реИрдВ рдЬреЛ рдЬрд╛рд╡рд╛ рдХреЛрдб рдХреЛ рдХреЛрдЯрд▓рд┐рди рдХреЛрдб рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рддрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЖрдкрдХреЛ рдпрд╣ рдЙрдореНрдореАрдж рдирд╣реАрдВ рдХрд░рдиреА рдЪрд╛рд╣рд┐рдП рдХрд┐ рдЗрд╕ рддрд░рд╣ рд╕реЗ рдЖрдк рдкреВрд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдФрд░ рдЕрдЪрд╛рдирдХ рд╕рдм рдХреБрдЫ рдЕрдкрдиреЗ рдЖрдк рд╕реЗ рдХрд╛рдо рдХрд░реЗрдЧрд╛ред

IntelliJ IDEA рдЕрдВрддрд┐рдо рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рдЦреБрд╢ рдорд╛рд▓рд┐рдХ Vue.js. рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреБрдХреНрдд рдкреНрд▓рдЧрдЗрди рд╕реНрдерд╛рдкрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрджрд┐ рдЖрдк рдлреНрд░реАрдмреА рдореВрд▓реНрдп рдФрд░ рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рдмреАрдЪ рдПрдХ рд╕рдордЭреМрддрд╛ рдХреА рддрд▓рд╛рд╢ рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рддреЛ рдореИрдВ рд╡реЗрдЯреВрд░ рдкреНрд▓рдЧрдЗрди рдХреЗ рд╕рд╛рде Microsoft рд╡рд┐рдЬрд╝реБрдЕрд▓ рдХреЛрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЕрддреНрдпрдзрд┐рдХ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВред

рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдХрдИ рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рд╕реНрдкрд╖реНрдЯ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рд┐рд░реНрдл рдорд╛рдорд▓реЗ рдореЗрдВ, рдореИрдВ рдЖрдкрдХреЛ рдпрд╛рдж рджрд┐рд▓рд╛рддрд╛ рд╣реВрдВ рдХрд┐ рдПрдирдкреАрдПрдо рдкреИрдХреЗрдЬ рдкреНрд░рдмрдВрдзрдХ рдХреЛ рд╡реАрд╡реАрдПрд╕ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИред Vue.js рдХреЗ рд▓рд┐рдП рд╕реНрдерд╛рдкрдирд╛ рдирд┐рд░реНрджреЗрд╢ Vue CLI рд╡реЗрдмрд╕рд╛рдЗрдЯ рдкрд░ рджреЗрдЦреЗ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред

рдЗрд╕ рдЧрд╛рдЗрдб рдореЗрдВ рдЬрд╛рд╡рд╛ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХрд▓реЗрдХреНрдЯрд░ рдХреЗ рд░реВрдк рдореЗрдВ рдорд╛рд╡реЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, PostgreSQL рдбреЗрдЯрд╛рдмреЗрд╕ рд╕рд░реНрд╡рд░ рдХреЗ рд░реВрдк рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред


рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬреЗрд╢рди


рдирд╛рдо рд╕реЗ рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдмрдирд╛рдПрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдХреЛрдЯрд▓рд┐рди-рд╕реНрдкреНрд░рд┐рдВрдЧ-рд╡реНрдпреВ ред рд╣рдорд╛рд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рджреЛ рдореЙрдбреНрдпреВрд▓ рд╣реЛрдВрдЧреЗ - рдмреИрдХрдПрдВрдб рдФрд░ рдлреНрд░рдВрдЯреЗрдВрдб ред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рджреГрд╢реНрдпрдкрдЯрд▓ рдПрдХрддреНрд░ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рдлрд┐рд░, рдЕрд╕реЗрдВрдмрд▓реА рдХреЗ рджреМрд░рд╛рди рдмреИрдХрдПрдВрдб рдЦреБрдж рд╣реА index.html, favicon.ico рдФрд░ рд╕рднреА рд╕реНрдЯреИрдЯрд┐рдХ рдлрд╛рдЗрд▓реНрд╕ (* .js, * .css, рдЗрдореЗрдЬреЗрдЬ рдЖрджрд┐) рдХреЛ рдХреЙрдкреА рдХрд░ рд▓реЗрдЧрд╛ред

рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд░реВрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдореЗрдВ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рджреЛ рд╕рдмрдлреЛрд▓реНрдбрд░реНрд╕ рд╣реЛрдВрдЧреЗ - / рдмреИрдХреЗрдВрдб рдФрд░ / рдлреНрд░рдВрдЯреЗрдВрдб ред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЙрдиреНрд╣реЗрдВ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЬрд▓реНрджреА рдордд рдХрд░реЛред

рдмреИрдХрдПрдВрдб рдореЙрдбреНрдпреВрд▓ рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рдиреЗ рдХреЗ рдХрдИ рддрд░реАрдХреЗ рд╣реИрдВ:

  • рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ (рд╕рдореБрд░рд╛рдИ рдкрде)
  • рд╕реНрдкреНрд░рд┐рдВрдЧ рдЯреВрд▓ рд╕реВрдЯ рдпрд╛ IntelliJ IDEA рдЕрд▓реНрдЯреАрдореЗрдЯ рдПрдбрд┐рд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдкреНрд░реЛрдЬреЗрдХреНрдЯ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛
  • рд╕реНрдкреНрд░рд┐рдВрдЧ рдЗрдирд┐рдЧреНрдирд┐рдЬрд╝рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ , рдЖрд╡рд╢реНрдпрдХ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдирд╛ - рдпрд╣ рд╢рд╛рдпрдж рд╕рдмрд╕реЗ рдЖрдо рддрд░реАрдХрд╛ рд╣реИ

рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдкреНрд░рд╛рдердорд┐рдХ рд╡рд┐рдиреНрдпрд╛рд╕ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:

рдмреИрдХрдПрдВрдб рдореЙрдбреНрдпреВрд▓ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди
  • рдкрд░рд┐рдпреЛрдЬрдирд╛: рдорд╛рд╡реЗрди рдкрд░рд┐рдпреЛрдЬрдирд╛
  • рднрд╛рд╖рд╛: рдХреЛрдЯрд▓рд┐рди
  • рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ: 2.1.6
  • рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдЯрд╛рдбреЗрдЯрд╛: рдЬрд╛рд╡рд╛ 8, JAR рдкреИрдХреЗрдЬрд┐рдВрдЧ
  • рдирд┐рд░реНрднрд░рддрд╛рдПрдБ: рд╕реНрдкреНрд░рд┐рдВрдЧ рд╡реЗрдм рд╕реНрдЯрд╛рд░реНрдЯрд░, рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рдПрдХреНрдЯреНрдпреВрдПрдЯрд░, рд╕реНрдкреНрд░рд┐рдВрдЧ рдмреВрдЯ рджреЗрд╡рдЯреВрд▓



pom.xml рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрдирд╛ рдЪрд╛рд╣рд┐рдП:

pom.xml - рдмреИрдХрдПрдВрдб
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.kotlin-spring-vue</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.kotlin-spring-vue</groupId> <artifactId>backend</artifactId> <version>0.0.1-SNAPSHOT</version> <name>backend</name> <description>Backend module for Kotlin + Spring Boot + Vue.js</description> <properties> <java.version>1.8</java.version> <kotlin.version>1.2.71</kotlin.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <rest-assured.version>3.3.0</rest-assured.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.kotlinspringvue.backend.BackendApplicationKt</mainClass> </configuration> </plugin> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> </compilerPlugins> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy Vue.js frontend content</id> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>src/main/resources/public</outputDirectory> <overwrite>true</overwrite> <resources> <resource> <directory>${project.parent.basedir}/frontend/target/dist</directory> <includes> <include>static/</include> <include>index.html</include> <include>favicon.ico</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> 

рдзреНрдпрд╛рди рджреЗрдВ:

  • рдореБрдЦреНрдп рд╡рд░реНрдЧ рдХрд╛ рдирд╛рдо рдХреЗрдЯреА рдореЗрдВ рд╕рдорд╛рдкреНрдд рд╣реЛрддрд╛ рд╣реИ
  • Project_root / frontend / target / dist рд╕реЗ src / main / resource / public рдХреЗ рд▓рд┐рдП рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреА рдирдХрд▓ рдХрд░рдирд╛
  • рд╕реНрдкреНрд░рд┐рдВрдЧ-рдмреВрдЯ-рд╕реНрдЯрд╛рд░реНрдЯрд░-рдкреИрд░реЗрдВрдЯ рджреНрд╡рд╛рд░рд╛ рдкреНрд░рд╕реНрддреБрдд рдкреИрд░реЗрдВрдЯ рдкреНрд░реЛрдЬреЗрдХреНрдЯ (рдореВрд▓) рдХреЛ рдореБрдЦреНрдп pom.xml рд╕реНрддрд░ рдкрд░ рд▓реЗ рдЬрд╛рдпрд╛ рдЧрдпрд╛


рдлреНрд░рдВрдЯрдПрдВрдб рдореЙрдбреНрдпреВрд▓ рдХреЛ рдЗрдирд┐рд╢рд┐рдпрд▓рд╛рдЗрдЬрд╝ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд░реВрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдкрд░ рдЬрд╛рдПрдБ рдФрд░ рдХрдорд╛рдВрдб рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░реЗрдВ:

 $ vue create frontend 

рдлрд┐рд░ рдЖрдк рд╕рднреА рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХрд╛ рдЪрдпрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ - рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рдпрд╣ рдкрд░реНрдпрд╛рдкреНрдд рд╣реЛрдЧрд╛ред

рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ, рдореЙрдбреНрдпреВрд▓ рдХреЛ / рдбрд┐рд╕реНрдЯрд░реНрдм рд╕рдмрдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдПрдХрддреНрд░ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛, рд╣рд╛рд▓рд╛рдБрдХрд┐ рд╣рдореЗрдВ рдПрдХрддреНрд░рд┐рдд рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ / рд▓рдХреНрд╖реНрдп рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рджреЗрдЦрдирд╛ рд╣реЛрдЧрд╛ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, vue.config.js рдлрд╝рд╛рдЗрд▓ рдХреЛ рд╕реАрдзреЗ / рдлреНрд░рдВрдЯрдПрдВрдб рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЗ рд╕рд╛рде рдмрдирд╛рдПрдВ:

 module.exports = { outputDir: 'target/dist', assetsDir: 'static' } 

рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдлреЙрд░реНрдо рдХреА pom.xml рдлрд╝рд╛рдЗрд▓ рдХреЛ рдлреНрд░рдВрдЯреЗрдВрдб рдореЙрдбреНрдпреВрд▓ рдореЗрдВ рд░рдЦреЗрдВ:

pom.xml - рджреГрд╢реНрдпрдкрдЯрд▓
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>frontend</artifactId> <parent> <groupId>com.kotlin-spring-vue</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <frontend-maven-plugin.version>1.6</frontend-maven-plugin.version> </properties> <build> <plugins> <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>${frontend-maven-plugin.version}</version> <executions> <!-- Install our node and npm version to run npm/node scripts--> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> <configuration> <nodeVersion>v11.8.0</nodeVersion> </configuration> </execution> <!-- Install all project dependencies --> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> <!-- optional: default phase is "generate-resources" --> <phase>generate-resources</phase> <!-- Optional configuration which provides for running any npm command --> <configuration> <arguments>install</arguments> </configuration> </execution> <!-- Build and minify static files --> <execution> <id>npm run build</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>run build</arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> 


рдФрд░ рдЕрдВрдд рдореЗрдВ, рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рдореВрд▓ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ pom.xml рдбрд╛рд▓реЗрдВ:
pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kotlin-spring-vue</groupId> <artifactId>demo</artifactId> <packaging>pom</packaging> <version>0.0.1-SNAPSHOT</version> <name>kotlin-spring-vue</name> <description>Kotlin + Spring Boot + Vue.js</description> <modules> <module>frontend</module> <module>backend</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <main.basedir>${project.basedir}</main.basedir> </properties> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <!-- Prepares the property pointing to the JaCoCo runtime agent which is passed as VM argument when Maven the Surefire plugin is executed. --> <execution> <id>pre-unit-test</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <!-- Ensures that the code coverage report for unit tests is created after unit tests have been run. --> <execution> <id>post-unit-test</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.eluder.coveralls</groupId> <artifactId>coveralls-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> <configuration> <jvmTarget>1.8</jvmTarget> </configuration> </plugin> </plugins> </build> </project> 

рдЬрд╣рд╛рдВ рд╣рдо рдЕрдкрдиреЗ рджреЛ рдореЙрдбреНрдпреВрд▓ рджреЗрдЦрддреЗ рд╣реИрдВ - рдлреНрд░рдВрдЯрдПрдВрдб рдФрд░ рдмреИрдХрдПрдВрдб , рдФрд░ рдкреЗрд░реЗрдВрдЯ - рд╕реНрдкреНрд░рд┐рдВрдЧ-рдмреВрдЯ-рд╕реНрдЯрд╛рд░реНрдЯрд░-рдкреИрд░реЗрдВрдЯ ред

рдорд╣рддреНрд╡рдкреВрд░реНрдг: рдореЙрдбреНрдпреВрд▓ рдХреЛ рдЗрд╕ рдХреНрд░рдо рдореЗрдВ рдЗрдХрдЯреНрдард╛ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП - рдкрд╣рд▓реЗ рдлреНрд░рдВрдЯреЗрдВрдб, рдлрд┐рд░ рдмреИрдХрдПрдВрдбред

рдЕрдм рд╣рдо рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

 $ mvn install 

рдФрд░, рдЕрдЧрд░ рд╕рдм рдХреБрдЫ рдЗрдХрдЯреНрдард╛ рд╣реЛ рдЧрдпрд╛ рд╣реИ, рддреЛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЪрд▓рд╛рдПрдВ:

 $ mvn --projects backend spring-boot:run 

рдбрд┐рдлрд╝реЙрд▓реНрдЯ Vue.js рдкреГрд╖реНрда http: // localhost: 8080 / : рдкрд░ рдЙрдкрд▓рдмреНрдз рд╣реЛрдЧрд╛ред




рдЕрдиреНрдп рдПрдкреАрдЖрдИ


рдЕрдм рдХреБрдЫ рд╕рд░рд▓ REST рд╕реЗрд╡рд╛ рдмрдирд╛рддреЗ рд╣реИрдВред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, "рдирдорд╕реНрдХрд╛рд░, [рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо]!" (рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд╡рд┐рд╢реНрд╡ рд╣реИ), рдЬреЛ рдЧрд┐рдирддрд╛ рд╣реИ рдХрд┐ рд╣рдордиреЗ рдЗрд╕реЗ рдХрд┐рддрдиреА рдмрд╛рд░ рдЦреАрдВрдЪрд╛ рд╣реИред
рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдПрдХ рдбреЗрдЯрд╛ рд╕рдВрд░рдЪрдирд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдПрдХ рд╕рдВрдЦреНрдпрд╛ рдФрд░ рдПрдХ рд╕реНрдЯреНрд░рд┐рдВрдЧ рд╣реЛрддреА рд╣реИ - рдПрдХ рд╡рд░реНрдЧ рдЬрд┐рд╕рдХрд╛ рдПрдХрдорд╛рддреНрд░ рдЙрджреНрджреЗрд╢реНрдп рдбреЗрдЯрд╛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдирд╛ рд╣реИред рдХреЛрдЯрд▓рд┐рди рдХреЗ рдкрд╛рд╕ рдЗрд╕рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛ рдХрдХреНрд╖рд╛рдПрдВ рд╣реИрдВ ред рдФрд░ рд╣рдорд╛рд░реА рдХрдХреНрд╖рд╛ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрд╛рдИ рджреЗрдЧреА:

 data class Greeting(val id: Long, val content: String) 

рд╡рд╣ рд╕рдм рд╣реИред рдЕрдм рд╣рдо рд╕реЗрд╡рд╛ рдХреЛ рд╕реАрдзреЗ рд▓рд┐рдЦ рд╕рдХрддреЗ рд╣реИрдВред

рдиреЛрдЯ: рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рд▓рд┐рдП, рдпрд╣ рд╕рднреА рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рдПрдХ рдЕрд▓рдЧ рдорд╛рд░реНрдЧ / рдПрдкреАрдЖрдИ рдХреЗ рд▓рд┐рдП рд▓реЗ рдЬрд╛рдПрдЧрд╛, рдХрдХреНрд╖рд╛ рдХреА рдШреЛрд╖рдгрд╛ рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ @RequestMapping рдПрдиреЛрдЯреЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП:

 import org.springframework.web.bind.annotation.* import com.kotlinspringvue.backend.model.Greeting import java.util.concurrent.atomic.AtomicLong @RestController @RequestMapping("/api") class BackendController() { val counter = AtomicLong() @GetMapping("/greeting") fun greeting(@RequestParam(value = "name", defaultValue = "World") name: String) = Greeting(counter.incrementAndGet(), "Hello, $name") } 

рдЕрдм рдЖрд╡реЗрджрди рдХреЛ рдкреБрдирдГ рдЖрд░рдВрдн рдХрд░реЗрдВ рдФрд░ рдкрд░рд┐рдгрд╛рдо рджреЗрдЦреЗрдВ http: // localhost: 8080 / api / рдЕрднрд┐рд╡рд╛рджрди? рдирд╛рдо = рд╡рд╛рджрд┐рдо :

 {"id":1,"content":"Hello, Vadim"} 

рд╣рдо рдкреГрд╖реНрда рдХреЛ рд░реАрдлрд╝реНрд░реЗрд╢ рдХрд░реЗрдВрдЧреЗ рдФрд░ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВрдЧреЗ рдХрд┐ рдХрд╛рдЙрдВрдЯрд░ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:

 {"id":2,"content":"Hello, Vadim"} 

рдЕрдм рдкреГрд╖реНрда рдкрд░ рдкрд░рд┐рдгрд╛рдо рдХреЛ рдЦреВрдмрд╕реВрд░рддреА рд╕реЗ рдЖрдХрд░реНрд╖рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рджреГрд╢реНрдпрдкрдЯрд▓ рдкрд░ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВред
рд╣рдорд╛рд░реЗ рдкреГрд╖реНрда рдореЗрдВ "рдкреГрд╖реНрдареЛрдВ" (рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдорд╛рд░реНрдЧреЛрдВ рдФрд░ рдШрдЯрдХреЛрдВ рдкрд░ - рдХреНрдпреЛрдВрдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХреЗрд╡рд▓ рдПрдХ рдкреГрд╖реНрда рд╣реИ) рдкрд░ рдиреЗрд╡рд┐рдЧреЗрд╢рди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реАрдпреВ-рд░рд╛рдЙрдЯрд░ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:

 $ npm install --save vue-router 

рд░рд╛рдКрдЯрд░ рдХреЛ рдЬреЛрдбрд╝реЗрдВред ss / src - рдпрд╣ рдШрдЯрдХ рд░реВрдЯрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реЛрдЧрд╛:

router.js
 import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Greeting from '@/components/Greeting' Vue.use(Router) export default new Router({ mode: 'history', routes: [ { path: '/', name: 'Greeting', component: Greeting }, { path: '/hello-world', name: 'HelloWorld', component: HelloWorld } ] }) 


рдиреЛрдЯ: рд░реВрдЯ рд░реВрдЯ ("/") рд╣рдореЗрдВ рдШрдЯрдХ рдЧреНрд░реАрдЯрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдЙрдкрд▓рдмреНрдз рд╣реЛрдЧрд╛ред рдЖрдЧреЗ, рдЬрд┐рд╕реЗ рд╣рдо рдереЛрдбрд╝реА рджреЗрд░ рдмрд╛рдж рд▓рд┐рдЦреЗрдВрдЧреЗред

рдЕрдм рд╣рдо рдЕрдкрдирд╛ рд░рд╛рдЙрдЯрд░ рдЖрдпрд╛рдд рдХрд░реЗрдВрдЧреЗред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкрд░рд┐рд╡рд░реНрддрди рдХрд░реЗрдВ
main.js
 import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App), }).$mount('#app') 


рддреЛ
App.vue
 <template> <div id="app"> <router-view></router-view> </div> </template> <script> export default { name: 'app' } </script> <style> </style> 


рд╕рд░реНрд╡рд░ рдЕрдиреБрд░реЛрдз рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, AXIOS HTTP рдХреНрд▓рд╛рдЗрдВрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ:

 $ npm install --save axios 

рд╣рд░ рдмрд╛рд░ рдПрдХ рд╣реА рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЛ рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдирд╣реАрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЕрдиреБрд░реЛрдз рдорд╛рд░реНрдЧ "/ рдПрдкреАрдЖрдИ" рд╣реИ) рдкреНрд░рддреНрдпреЗрдХ рдШрдЯрдХ рдореЗрдВ, рдореИрдВ рдЙрдиреНрд╣реЗрдВ рдЕрд▓рдЧ http-common.js рдШрдЯрдХ рдореЗрдВ рдбрд╛рд▓рдиреЗ рдХреА рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ:

 import axios from 'axios' export const AXIOS = axios.create({ baseURL: `/api` }) 

рдиреЛрдЯ: рдХрдВрд╕реЛрд▓ ( рдХрдВрд╕реЛрд▓.рд▓реЙрдЧ () ) рдкрд░ рдЖрдЙрдЯрдкреБрдЯ рдХрд░рддреЗ рд╕рдордп рдЪреЗрддрд╛рд╡рдирд┐рдпреЛрдВ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВ рдкреИрдХреЗрдЬ рдореЗрдВ рдЗрд╕ рд▓рд╛рдЗрди рдХреЛ рд▓рд┐рдЦрдиреЗ рдХреА рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ ред

 "rules": { "no-console": "off" } 

рдЕрдм, рдЕрдВрдд рдореЗрдВ, рдШрдЯрдХ ( / src / рдШрдЯрдХреЛрдВ рдореЗрдВ ) рдмрдирд╛рдПрдБ

Greeting.vue
 import {AXIOS} from './http-common' <template> <div id="greeting"> <h3>Greeting component</h3> <p>Counter: {{ counter }}</p> <p>Username: {{ username }}</p> </div> </template> <script> export default { name: 'Greeting', data() { return { counter: 0, username: '' } }, methods: { loadGreeting() { AXIOS.get('/greeting', { params: { name: 'Vadim' } }) .then(response => { this.$data.counter = response.data.id; this.$data.username = response.data.content; }) .catch(error => { console.log('ERROR: ' + error.response.data); }) } }, mounted() { this.loadGreeting(); } } </script> 


рдиреЛрдЯ:

  • рдХреНрд╡реЗрд░реА рдкреИрд░рд╛рдореАрдЯрд░ рд╣рд╛рд░реНрдбрдХреЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд┐рд░реНрдл рдпрд╣ рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рд╡рд┐рдзрд┐ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддреА рд╣реИ
  • рдкреГрд╖реНрда рд▓реЛрдб рдХрд░рдиреЗ ( рдорд╛рдЙрдВрдЯреЗрдб () ) рдХреЗ рддреБрд░рдВрдд рдмрд╛рдж рдбреЗрдЯрд╛ рд▓реЛрдб рдХрд░рдиреЗ рдФрд░ рд▓реЛрдб рдХрд░рдиреЗ (рд▓реЛрдб рдХрд░рдиреЗ рдХреА рдХреНрд░рд┐рдпрд╛) рдХреЛ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ
  • рд╣рдордиреЗ http- рд╕рд╛рдорд╛рдиреНрдп рд╕реЗ рдЕрдкрдиреА рдХрд╕реНрдЯрдо рд╕реЗрдЯрд┐рдВрдЧ рдХреЗ рд╕рд╛рде рдкрд╣рд▓реЗ рд╕реЗ рд╣реА axios рдЖрдпрд╛рдд рдХрд┐рдпрд╛ рд╣реИ



рдбреЗрдЯрд╛рдмреЗрд╕ рдХрдиреЗрдХреНрд╢рди


рдЕрдм PostgreSQL рдФрд░ рд╕реНрдкреНрд░рд┐рдВрдЧ рдбреЗрдЯрд╛ рдХреЗ рдЙрджрд╛рд╣рд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХрд░рдиреЗ рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреЛ рджреЗрдЦреЗрдВред

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдПрдХ рдкрд░реАрдХреНрд╖рдг рдкреНрд▓реЗрдЯ рдмрдирд╛рдПрдВ:

 CREATE TABLE public."person" ( id serial NOT NULL, name character varying, PRIMARY KEY (id) ); 

рдФрд░ рдЗрд╕реЗ рдбреЗрдЯрд╛ рд╕реЗ рднрд░реЗрдВ:

 INSERT INTO person (name) VALUES ('John'), ('Griselda'), ('Bobby'); 

рдмреИрдХрдПрдВрдб рдореЙрдбреНрдпреВрд▓ рдХреЗ pom.xml рдХреЛ рдкреВрд░рдХ рдХрд░реЗрдВ:
 <properties> ... <postgresql.version>42.2.5</postgresql.version> ... </properties> ... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>${postgresql.version}</version> </dependency> ... <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> </compilerPlugins> </configuration> ... <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${kotlin.version}</version> </dependency> 


рдЕрдм рд╣рдо рдбреЗрдЯрд╛рдмреЗрд╕ рдХрдиреЗрдХреНрд╢рди рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЗ рд╕рд╛рде рдмреИрдХрдПрдВрдб рдореЙрдбреНрдпреВрд▓ рдХреЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди.рдкреНрд░реЛрдкрд░реЗрдЯреНрд╕ рдлрд╝рд╛рдЗрд▓ рдХреЛ рдкреВрд░рдХ рдХрд░реЗрдВрдЧреЗ:

 spring.datasource.url=${SPRING_DATASOURCE_URL} spring.datasource.username=${SPRING_DATASOURCE_USERNAME} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} spring.jpa.generate-ddl=true spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 

рдиреЛрдЯ: рдЗрд╕ рд░реВрдк рдореЗрдВ, рдкрд╣рд▓реЗ рддреАрди рдкреИрд░рд╛рдореАрдЯрд░ рдкрд░реНрдпрд╛рд╡рд░рдг рдЪрд░ рдХреЛ рд╕рдВрджрд░реНрднрд┐рдд рдХрд░рддреЗ рд╣реИрдВред рдореИрдВ рдкрд░реНрдпрд╛рд╡рд░рдг рдЪрд░ рдпрд╛ рд╕реНрдЯрд╛рд░реНрдЯрдЕрдк рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд╕рдВрд╡реЗрджрдирд╢реАрд▓ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЛ рдкрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдЕрддреНрдпрдзрд┐рдХ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВред рд▓реЗрдХрд┐рди, рдпрджрд┐ рдЖрдк рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рд╣реИрдВ рдХрд┐ рд╡реЗ рдХрдкрдЯреА рд╣рдорд▓рд╛рд╡рд░реЛрдВ рдХреЗ рд╣рд╛рдереЛрдВ рдореЗрдВ рдирд╣реАрдВ рдкрдбрд╝реЗрдВрдЧреЗ, рддреЛ рдЖрдк рдЙрдирд╕реЗ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдкреВрдЫ рд╕рдХрддреЗ рд╣реИрдВред

рдЖрдЗрдП рдСрдмреНрдЬреЗрдХреНрдЯ-рд░рд┐рд▓реЗрд╢рдирд▓ рдореИрдкрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдПрдХ рдЗрдХрд╛рдИ (рдПрдВрдЯрд┐рдЯреА-рдХреНрд▓рд╛рд╕) рдмрдирд╛рдПрдВ:

Person.kt
 import javax.persistence.Column import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id import javax.persistence.Table @Entity @Table (name="person") data class Person( @Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long, @Column(nullable = false) val name: String ) 



рдФрд░ рд╣рдорд╛рд░реЗ рдЯреЗрдмрд▓ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ CRUD рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА:

Repository.kt
 import com.kotlinspringvue.backend.jpa.Person import org.springframework.stereotype.Repository import org.springframework.data.repository.CrudRepository import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.repository.query.Param @Repository interface PersonRepository: CrudRepository<Person, Long> {} 

рдиреЛрдЯ: рд╣рдо findAll() рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ, рдЬрд┐рд╕реЗ рдлрд┐рд░ рд╕реЗ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рд╢рд░реАрд░ рдХреЛ рдЦрд╛рд▓реА рдЫреЛрдбрд╝ рджреЗрдВрдЧреЗред

рдФрд░ рдЕрдВрдд рдореЗрдВ, рд╣рдо рдЕрдкрдиреЗ рдХрдВрдЯреНрд░реЛрд▓рд░ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░реЗрдВрдЧреЗ рдХрд┐ рд╡реЗ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдХреИрд╕реЗ рдХрд╛рдо рдХрд░реЗрдВ:

BackendController.kt
 import com.kotlinspringvue.backend.repository.PersonRepository import org.springframework.beans.factory.annotation.Autowired тАж @Autowired lateinit var personRepository: PersonRepository тАж @GetMapping("/persons") fun getPersons() = personRepository.findAll() 


рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЪрд▓рд╛рдПрдБ, рд▓рд┐рдВрдХ рдХрд╛ рдЕрдиреБрд╕рд░рдг рдХрд░реЗрдВ https: // localhost: 8080 / api / рд╡реНрдпрдХреНрддрд┐ рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рд╕рдм рдХреБрдЫ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:

 [{"id":1,"name":"John"},{"id":2,"name":"Griselda"},{"id":3,"name":"Bobby"}] 


рдкреНрд░рдорд╛рдгреАрдХрд░рдг


рдЕрдм рд╣рдо рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЗ рд▓рд┐рдП рдЖрдЧреЗ рдмрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ - рдЙрди рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рдмреБрдирд┐рдпрд╛рджреА рдХрд╛рд░реНрдпреЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдЬрд╣рд╛рдВ рдбреЗрдЯрд╛ рдкрд╣реБрдВрдЪ рд╡рд┐рднреЗрджрд┐рдд рд╣реИред

JWT (JSON рд╡реЗрдм рдЯреЛрдХрди) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╕рд░реНрд╡рд░ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВред

рдмреЗрд╕рд┐рдХ рдСрдереЗрдВрдЯрд┐рдХреЗрд╢рди рдХреНрдпреЛрдВ рдирд╣реАрдВ?

  • рдореЗрд░реА рд░рд╛рдп рдореЗрдВ, рдмреЗрд╕рд┐рдХ рдСрдереЗрдВрдЯрд┐рдХреЗрд╢рди рдЕрдкреЗрдХреНрд╖рд╛рдХреГрдд рд╕реБрд░рдХреНрд╖рд┐рдд рдЙрдкрдпреЛрдЧ рдХреЗ рдорд╛рд╣реМрд▓ рдореЗрдВ рднреА рдЖрдзреБрдирд┐рдХ рдЦрддрд░реЗ рдХреА рдЪреБрдиреМрддреА рдХреЛ рдкреВрд░рд╛ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред
  • рдЖрдк рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рдмрд╣реБрдд рдЕрдзрд┐рдХ рд╕рд╛рдордЧреНрд░реА рдкрд╛ рд╕рдХрддреЗ рд╣реИрдВред

OAuth рдмреЙрдХреНрд╕ рд╕реЗ рдмрд╛рд╣рд░ рдХреНрдпреЛрдВ рдирд╣реАрдВ рд╣реИ OAuth рд╕реБрд░рдХреНрд╖рд╛?
  • рдХреНрдпреЛрдВрдХрд┐ OAuth рдореЗрдВ рдЕрдзрд┐рдХ рд╕рд╛рдорд╛рди рд╣реИред
  • рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЛ рдмрд╛рд╣рд░реА рдкрд░рд┐рд╕реНрдерд┐рддрд┐рдпреЛрдВ рд╕реЗ рддрдп рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ: рдЧреНрд░рд╛рд╣рдХ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛рдПрдВ, рд╡рд╛рд╕реНрддреБрдХрд╛рд░ рдХреА рдЗрдЪреНрдЫрд╛ рдЖрджрд┐ред
  • рдпрджрд┐ рдЖрдк рдПрдХ рдиреМрд╕рд┐рдЦрд┐рдП рдбреЗрд╡рд▓рдкрд░ рд╣реИрдВ, рддреЛ рдПрдХ рд░рдгрдиреАрддрд┐рдХ рдкрд░рд┐рдкреНрд░реЗрдХреНрд╖реНрдп рдореЗрдВ рд╕реБрд░рдХреНрд╖рд╛ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЗ рд╕рд╛рде рдФрд░ рдЕрдзрд┐рдХ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рдЧрд╣рд░реА рдЦреБрджрд╛рдИ рдХрд░рдирд╛ рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛ред

рдмреИрдХрдПрдВрдб


рдореЗрд╣рдорд╛рдиреЛрдВ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рджреЛ рд╕рдореВрд╣ рд╣реЛрдВрдЧреЗ - рд╕рд╛рдорд╛рдиреНрдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдФрд░ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХред рдЖрдЗрдП рддреАрди рддрд╛рд▓рд┐рдХрд╛рдПрдБ рдмрдирд╛рдПрдБ: рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ - рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рднреВрдорд┐рдХрд╛рдПрдБ - рднреВрдорд┐рдХрд╛рдПрдБ рдФрд░ users_roles рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП - рдкрд╣рд▓реЗ рджреЛ рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред

рддрд╛рд▓рд┐рдХрд╛рдПрдБ рдмрдирд╛рдПрдБ, рдмрд╛рдзрд╛рдПрдБ рдЬреЛрдбрд╝реЗрдВ рдФрд░ рд░реЛрд▓реНрд╕ рддрд╛рд▓рд┐рдХрд╛ рдХреЛ рдЖрдмрд╛рдж рдХрд░реЗрдВ
 CREATE TABLE public.users ( id serial NOT NULL, username character varying, first_name character varying, last_name character varying, email character varying, password character varying, enabled boolean, PRIMARY KEY (id) ); CREATE TABLE public.roles ( id serial NOT NULL, name character varying, PRIMARY KEY (id) ); CREATE TABLE public.users_roles ( id serial NOT NULL, user_id integer, role_id integer, PRIMARY KEY (id) ); ALTER TABLE public.users_roles ADD CONSTRAINT users_roles_users_fk FOREIGN KEY (user_id) REFERENCES public.users (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE public.users_roles ADD CONSTRAINT users_roles_roles_fk FOREIGN KEY (role_id) REFERENCES public.roles (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE; INSERT INTO roles (name) VALUES ('ROLE_USER'), ('ROLE_ADMIN'); 


рдЖрдЗрдП рдПрдВрдЯрд┐рдЯреА рдХреНрд▓рд╛рд╕реЗрд╕ рдмрдирд╛рдПрдВ:
User.kt
 import javax.persistence.* @Entity @Table(name = "users") data class User ( @Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = 0, @Column(name="username") var username: String?=null, @Column(name="first_name") var firstName: String?=null, @Column(name="last_name") var lastName: String?=null, @Column(name="email") var email: String?=null, @Column(name="password") var password: String?=null, @Column(name="enabled") var enabled: Boolean = false, @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")], inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] ) var roles: Collection<Role>? = null ) 

рдиреЛрдЯ: рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдФрд░ рднреВрдорд┐рдХрд╛ рддрд╛рд▓рд┐рдХрд╛ рдХрдИ-рд╕реЗ-рдХрдИ рд╕рдВрдмрдВрдзреЛрдВ рдореЗрдВ рд╣реИрдВ - рдПрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреА рдХрдИ рднреВрдорд┐рдХрд╛рдПрдВ рд╣реЛ рд╕рдХрддреА рд╣реИрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдФрд░ рдПрдХ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ), рдФрд░ рдХрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдПрдХ рднреВрдорд┐рдХрд╛ рд╕реМрдВрдкреА рдЬрд╛ рд╕рдХрддреА рд╣реИред

рд╡рд┐рдЪрд╛рд░ рдХреЗ рд▓рд┐рдП рд╕реВрдЪрдирд╛: рдПрдХ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╣реИ рдЬрд╣рд╛рдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдЕрд▓рдЧ-рдЕрд▓рдЧ рд╢рдХреНрддрд┐рдпрд╛рдВ (рдкреНрд░рд╛рдзрд┐рдХрд░рдг) рджреА рдЬрд╛рддреА рд╣реИрдВ, рдЬрдмрдХрд┐ рдПрдХ рднреВрдорд┐рдХрд╛ рд╢рдХреНрддрд┐рдпреЛрдВ рдХреЗ рдПрдХ рд╕рдореВрд╣ рдХрд╛ рдЕрд░реНрде рд╣реИред рдЖрдк рднреВрдорд┐рдХрд╛рдУрдВ рдФрд░ рдЕрдиреБрдорддрд┐рдпреЛрдВ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдпрд╣рд╛рдБ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ: рд╡рд╕рдВрдд рд╕реБрд░рдХреНрд╖рд╛ рдореЗрдВ рдЕрдиреБрджрд╛рдирд┐рдд рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдмрдирд╛рдо рднреВрдорд┐рдХрд╛ ред

Role.kt
 import javax.persistence.* @Entity @Table(name = "roles") data class Role ( @Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long, @Column(name="name") val name: String ) 


рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдмрдирд╛рдПрдВ:

UsersRepository.kt
 import java.util.Optional import com.kotlinspringvue.backend.jpa.User import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.query.Param import org.springframework.data.jpa.repository.JpaRepository import javax.transaction.Transactional interface UserRepository: JpaRepository<User, Long> { fun existsByUsername(@Param("username") username: String): Boolean fun findByUsername(@Param("username") username: String): Optional<User> fun findByEmail(@Param("email") email: String): Optional<User> @Transactional fun deleteByUsername(@Param("username") username: String) } 


RolesRepository.kt
 import com.kotlinspringvue.backend.jpa.Role import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.query.Param import org.springframework.data.jpa.repository.JpaRepository interface RoleRepository : JpaRepository<Role, Long> { fun findByName(@Param("name") name: String): Role } 


рдореЗрдВ рдирдИ рдирд┐рд░реНрднрд░рддрд╛рдПрдВ рдЬреЛрдбрд╝реЗрдВ
рдмреИрдХреЗрдВрдб рдореЙрдбреНрдпреВрд▓ pom.xml
 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.6</version> </dependency> 


рдФрд░ application.properties рдореЗрдВ рдЯреЛрдХрди рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдирдП рдкреИрд░рд╛рдореАрдЯрд░ рдЬреЛрдбрд╝реЗрдВ:
 assm.app.jwtSecret=jwtAssmSecretKey assm.app.jwtExpiration=86400 

рдЕрдм рд╣рдо рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдФрд░ рдкрдВрдЬреАрдХрд░рдг рдкреНрд░рдкрддреНрд░реЛрдВ рд╕реЗ рдЖрдиреЗ рд╡рд╛рд▓реЗ рдбреЗрдЯрд╛ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрдХреНрд╖рд╛рдПрдВ рдмрдирд╛рдПрдВрдЧреЗ:

LoginUser.kt
 class LoginUser : Serializable { @JsonProperty("username") var username: String? = null @JsonProperty("password") var password: String? = null constructor() {} constructor(username: String, password: String) { this.username = username this.password = password } companion object { private const val serialVersionUID = -1764970284520387975L } } 


NewUser.kt
 import com.fasterxml.jackson.annotation.JsonProperty import java.io.Serializable class NewUser : Serializable { @JsonProperty("username") var username: String? = null @JsonProperty("firstName") var firstName: String? = null @JsonProperty("lastName") var lastName: String? = null @JsonProperty("email") var email: String? = null @JsonProperty("password") var password: String? = null constructor() {} constructor(username: String, firstName: String, lastName: String, email: String, password: String, recaptchaToken: String) { this.username = username this.firstName = firstName this.lastName = lastName this.email = email this.password = password } companion object { private const val serialVersionUID = -1764970284520387975L } } 


рдЪрд▓реЛ рд╕рд░реНрд╡рд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реЗрд╖ рдХрдХреНрд╖рд╛рдПрдВ рдмрдирд╛рддреЗ рд╣реИрдВ - рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдЯреЛрдХрди рдФрд░ рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ (рд╕реНрдЯреНрд░рд┐рдВрдЧ) рд▓реМрдЯрд╛рддреЗ рд╣реИрдВ:

JwtRespons.kt
 import org.springframework.security.core.GrantedAuthority class JwtResponse(var accessToken: String?, var username: String?, val authorities: Collection<GrantedAuthority>) { var type = "Bearer" } 


ResponseMessage.kt
 class ResponseMessage(var message: String?) 


рд╣рдореЗрдВ "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдореМрдЬреВрдж" рдЕрдкрд╡рд╛рдж рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред
UserAlreadyExistException.kt
 class UserAlreadyExistException : RuntimeException { constructor() : super() {} constructor(message: String, cause: Throwable) : super(message, cause) {} constructor(message: String) : super(message) {} constructor(cause: Throwable) : super(cause) {} companion object { private val serialVersionUID = 5861310537366287163L } } 


рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рднреВрдорд┐рдХрд╛рдУрдВ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рд╕реЗрд╡рд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдЬреЛ UserDetailsService рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рддреА рд╣реИ:

UserDetailsServiceImpl.kt
 import com.kotlinspringvue.backend.repository.UserRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import java.util.stream.Collectors @Service class UserDetailsServiceImpl: UserDetailsService { @Autowired lateinit var userRepository: UserRepository @Throws(UsernameNotFoundException::class) override fun loadUserByUsername(username: String): UserDetails { val user = userRepository.findByUsername(username).get() ?: throw UsernameNotFoundException("User '$username' not found") val authorities: List<GrantedAuthority> = user.roles!!.stream().map({ role -> SimpleGrantedAuthority(role.name)}).collect(Collectors.toList<GrantedAuthority>()) return org.springframework.security.core.userdetails.User .withUsername(username) .password(user.password) .authorities(authorities) .accountExpired(false) .accountLocked(false) .credentialsExpired(false) .disabled(false) .build() } } 


JWT рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рддреАрди рд╡рд░реНрдЧреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:
JwtAuthEntryPoint - рдкреНрд░рд╛рдзрд┐рдХрд░рдг рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓рдиреЗ рдФрд░ рд╡реЗрдм рд╕реБрд░рдХреНрд╖рд╛ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ рдЖрдЧреЗ рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП:

JwtAuthEntryPoint.kt
 import javax.servlet.ServletException import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.security.core.AuthenticationException import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.stereotype.Component @Component class JwtAuthEntryPoint : AuthenticationEntryPoint { @Throws(IOException::class, ServletException::class) override fun commence(request: HttpServletRequest, response: HttpServletResponse, e: AuthenticationException) { logger.error("Unauthorized error. Message - {}", e!!.message) response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid credentials") } companion object { private val logger = LoggerFactory.getLogger(JwtAuthEntryPoint::class.java) } } 


JwtProvider - рдЯреЛрдХрди рдХреЛ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдФрд░ рдорд╛рдиреНрдп рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╕рд╛рде рд╣реА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдЙрд╕рдХреЗ рдЯреЛрдХрди рджреНрд╡рд╛рд░рд╛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рддрд╛ рд╣реИ:

JwtProvider.kt
 import io.jsonwebtoken.* import org.springframework.beans.factory.annotation.Autowired import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.security.core.Authentication import org.springframework.stereotype.Component import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import com.kotlinspringvue.backend.repository.UserRepository import java.util.Date @Component public class JwtProvider { private val logger: Logger = LoggerFactory.getLogger(JwtProvider::class.java) @Autowired lateinit var userRepository: UserRepository @Value("\${assm.app.jwtSecret}") lateinit var jwtSecret: String @Value("\${assm.app.jwtExpiration}") var jwtExpiration:Int?=0 fun generateJwtToken(username: String): String { return Jwts.builder() .setSubject(username) .setIssuedAt(Date()) .setExpiration(Date((Date()).getTime() + jwtExpiration!! * 1000)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact() } fun validateJwtToken(authToken: String): Boolean { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken) return true } catch (e: SignatureException) { logger.error("Invalid JWT signature -> Message: {} ", e) } catch (e: MalformedJwtException) { logger.error("Invalid JWT token -> Message: {}", e) } catch (e: ExpiredJwtException) { logger.error("Expired JWT token -> Message: {}", e) } catch (e: UnsupportedJwtException) { logger.error("Unsupported JWT token -> Message: {}", e) } catch (e: IllegalArgumentException) { logger.error("JWT claims string is empty -> Message: {}", e) } return false } fun getUserNameFromJwtToken(token: String): String { return Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody().getSubject() } } 


JwtAuthTokenFilter - рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдкреНрд░рдорд╛рдгрд┐рдд рдХрд░рдиреЗ рдФрд░ рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП:

JwtAuthTokenFilter.kt
 import java.io.IOException import javax.servlet.FilterChain import javax.servlet.ServletException import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.web.authentication.WebAuthenticationDetailsSource import org.springframework.web.filter.OncePerRequestFilter import com.kotlinspringvue.backend.service.UserDetailsServiceImpl class JwtAuthTokenFilter : OncePerRequestFilter() { @Autowired private val tokenProvider: JwtProvider? = null @Autowired private val userDetailsService: UserDetailsServiceImpl? = null @Throws(ServletException::class, IOException::class) override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { try { val jwt = getJwt(request) if (jwt != null && tokenProvider!!.validateJwtToken(jwt)) { val username = tokenProvider.getUserNameFromJwtToken(jwt) val userDetails = userDetailsService!!.loadUserByUsername(username) val authentication = UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()) authentication.setDetails(WebAuthenticationDetailsSource().buildDetails(request)) SecurityContextHolder.getContext().setAuthentication(authentication) } } catch (e: Exception) { logger.error("Can NOT set user authentication -> Message: {}", e) } filterChain.doFilter(request, response) } private fun getJwt(request: HttpServletRequest): String? { val authHeader = request.getHeader("Authorization") return if (authHeader != null && authHeader.startsWith("Bearer ")) { authHeader.replace("Bearer ", "") } else null } companion object { private val logger = LoggerFactory.getLogger(JwtAuthTokenFilter::class.java) } } 


рдЕрдм рд╣рдо рд╡реЗрдм рд╕реБрд░рдХреНрд╖рд╛ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╕реЗрдо рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

WebSecurityConfig.kt
 import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.beans.factory.annotation.Autowired import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import com.kotlinspringvue.backend.jwt.JwtAuthEntryPoint import com.kotlinspringvue.backend.jwt.JwtAuthTokenFilter import com.kotlinspringvue.backend.service.UserDetailsServiceImpl @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) class WebSecurityConfig : WebSecurityConfigurerAdapter() { @Autowired internal var userDetailsService: UserDetailsServiceImpl? = null @Autowired private val unauthorizedHandler: JwtAuthEntryPoint? = null @Bean fun bCryptPasswordEncoder(): BCryptPasswordEncoder { return BCryptPasswordEncoder() } @Bean fun authenticationJwtTokenFilter(): JwtAuthTokenFilter { return JwtAuthTokenFilter() } @Throws(Exception::class) override fun configure(authenticationManagerBuilder: AuthenticationManagerBuilder) { authenticationManagerBuilder .userDetailsService(userDetailsService) .passwordEncoder(bCryptPasswordEncoder()) } @Bean @Throws(Exception::class) override fun authenticationManagerBean(): AuthenticationManager { return super.authenticationManagerBean() } @Throws(Exception::class) override protected fun configure(http: HttpSecurity) { http.csrf().disable().authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter::class.java) } } 


:

AuthController.kt
 import javax.validation.Valid import java.util.* import java.util.stream.Collectors import org.springframework.security.core.Authentication import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import com.kotlinspringvue.backend.model.LoginUser import com.kotlinspringvue.backend.model.NewUser import com.kotlinspringvue.backend.web.response.JwtResponse import com.kotlinspringvue.backend.web.response.ResponseMessage import com.kotlinspringvue.backend.jpa.User import com.kotlinspringvue.backend.repository.UserRepository import com.kotlinspringvue.backend.repository.RoleRepository import com.kotlinspringvue.backend.jwt.JwtProvider @CrossOrigin(origins = ["*"], maxAge = 3600) @RestController @RequestMapping("/api/auth") class AuthController() { @Autowired lateinit var authenticationManager: AuthenticationManager @Autowired lateinit var userRepository: UserRepository @Autowired lateinit var roleRepository: RoleRepository @Autowired lateinit var encoder: PasswordEncoder @Autowired lateinit var jwtProvider: JwtProvider @PostMapping("/signin") fun authenticateUser(@Valid @RequestBody loginRequest: LoginUser): ResponseEntity<*> { val userCandidate: Optional <User> = userRepository.findByUsername(loginRequest.username!!) if (userCandidate.isPresent) { val user: User = userCandidate.get() val authentication = authenticationManager.authenticate( UsernamePasswordAuthenticationToken(loginRequest.username, loginRequest.password)) SecurityContextHolder.getContext().setAuthentication(authentication) val jwt: String = jwtProvider.generateJwtToken(user.username!!) val authorities: List<GrantedAuthority> = user.roles!!.stream().map({ role -> SimpleGrantedAuthority(role.name)}).collect(Collectors.toList<GrantedAuthority>()) return ResponseEntity.ok(JwtResponse(jwt, user.username, authorities)) } else { return ResponseEntity(ResponseMessage("User not found!"), HttpStatus.BAD_REQUEST) } } @PostMapping("/signup") fun registerUser(@Valid @RequestBody newUser: NewUser): ResponseEntity<*> { val userCandidate: Optional <User> = userRepository.findByUsername(newUser.username!!) if (!userCandidate.isPresent) { if (usernameExists(newUser.username!!)) { return ResponseEntity(ResponseMessage("Username is already taken!"), HttpStatus.BAD_REQUEST) } else if (emailExists(newUser.email!!)) { return ResponseEntity(ResponseMessage("Email is already in use!"), HttpStatus.BAD_REQUEST) } // Creating user's account val user = User( 0, newUser.username!!, newUser.firstName!!, newUser.lastName!!, newUser.email!!, encoder.encode(newUser.password), true ) user!!.roles = Arrays.asList(roleRepository.findByName("ROLE_USER")) userRepository.save(user) return ResponseEntity(ResponseMessage("User registered successfully!"), HttpStatus.OK) } else { return ResponseEntity(ResponseMessage("User already exists!"), HttpStatus.BAD_REQUEST) } } private fun emailExists(email: String): Boolean { return userRepository.findByUsername(email).isPresent } private fun usernameExists(username: String): Boolean { return userRepository.findByUsername(username).isPresent } } 

:

  • signin тАФ , , , , (, authorities тАФ )
  • signup тАФ , , , users ROLE_USER


, , BackendController : , ( ROLE_USER ROLE_ADMIN) (ROLE_USER).

BackendController.kt
 import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.core.Authentication import com.kotlinspringvue.backend.repository.UserRepository import com.kotlinspringvue.backend.jpa.User тАж @Autowired lateinit var userRepository: UserRepository тАж @GetMapping("/usercontent") @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") @ResponseBody fun getUserContent(authentication: Authentication): String { val user: User = userRepository.findByUsername(authentication.name).get() return "Hello " + user.firstName + " " + user.lastName + "!" } @GetMapping("/admincontent") @PreAuthorize("hasRole('ADMIN')") @ResponseBody fun getAdminContent(): String { return "Admin's content" } 


рджреГрд╢реНрдпрдкрдЯрд▓


:

  • Home
  • SignIn
  • SignUp
  • AdminPage
  • UserPage

( ):

 <template> <div> </div> </template> <script> </script> <style> </style> 


id=┬л_┬╗ div template export default {name: '[component_name]'} script .

:

router.js
 import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import SignIn from '@/components/SignIn' import SignUp from '@/components/SignUp' import AdminPage from '@/components/AdminPage' import UserPage from '@/components/UserPage' Vue.use(Router) export default new Router({ mode: 'history', routes: [ { path: '/', name: 'Home', component: Home }, { path: '/home', name: 'Home', component: Home }, { path: '/login', name: 'SignIn', component: SignIn }, { path: '/register', name: 'SignUp', component: SignUp }, { path: '/user', name: 'UserPage', component: UserPage }, { path: '/admin', name: 'AdminPage', component: AdminPage } ] }) 


рд╣рдо рд╕рд░реНрд╡рд░ рдХреЛ рдХреНрд╡реЗрд░реА рдХрд░рддреЗ рд╕рдордп рдЯреЛрдХрди рд╕реНрдЯреЛрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП Vuex рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ рдФрд░ рдЙрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ ред Vuex рдПрдХ рд░рд╛рдЬреНрдп рдкреНрд░рдмрдВрдзрди рдкреИрдЯрд░реНрди + Vue.js. рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╣реИ рдпрд╣ рдирд┐рдпрдореЛрдВ рдХреЗ рд╕рд╛рде рд╕рднреА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдШрдЯрдХреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдХреЗрдВрджреНрд░реАрдХреГрдд рдбреЗрдЯрд╛ рд╡реЗрдпрд░рд╣рд╛рдЙрд╕ рдХреЗ рд░реВрдк рдореЗрдВ рдХрд╛рд░реНрдп рдХрд░рддрд╛ рд╣реИ рддрд╛рдХрд┐ рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХреЗ рдХрд┐ рд░рд╛рдЬреНрдп рдХреЛ рдХреЗрд╡рд▓ рдЕрдиреБрдорд╛рдирд┐рдд рд░реВрдк рд╕реЗ рдмрджрд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

 $ npm install --save vuex 

Src / store рдореЗрдВ рдПрдХ рдЕрд▓рдЧ рдлрд╝рд╛рдЗрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рд╕реНрдЯреЛрд░ рдЬреЛрдбрд╝реЗрдВ :

index.js
 import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state = { token: localStorage.getItem('user-token') || '', role: localStorage.getItem('user-role') || '', username: localStorage.getItem('user-name') || '', authorities: localStorage.getItem('authorities') || '', }; const getters = { isAuthenticated: state => { if (state.token != null && state.token != '') { return true; } else { return false; } }, isAdmin: state => { if (state.role === 'admin') { return true; } else { return false; } }, getUsername: state => { return state.username; }, getAuthorities: state => { return state.authorities; }, getToken: state => { return state.token; } }; const mutations = { auth_login: (state, user) => { localStorage.setItem('user-token', user.token); localStorage.setItem('user-name', user.name); localStorage.setItem('user-authorities', user.roles); state.token = user.token; state.username = user.username; state.authorities = user.roles; var isUser = false; var isAdmin = false; for (var i = 0; i < user.roles.length; i++) { if (user.roles[i].authority === 'ROLE_USER') { isUser = true; } else if (user.roles[i].authority === 'ROLE_ADMIN') { isAdmin = true; } } if (isUser) { localStorage.setItem('user-role', 'user'); state.role = 'user'; } if (isAdmin) { localStorage.setItem('user-role', 'admin'); state.role = 'admin'; } }, auth_logout: () => { state.token = ''; state.role = ''; state.username = ''; state.authorities = []; localStorage.removeItem('user-token'); localStorage.removeItem('user-role'); localStorage.removeItem('user-name'); localStorage.removeItem('user-authorities'); } }; const actions = { login: (context, user) => { context.commit('auth_login', user) }, logout: (context) => { context.commit('auth_logout'); } }; export const store = new Vuex.Store({ state, getters, mutations, actions }); 

, :

  • store тАФ , тАФ , , ( тАФ (authorities): тАФ , , admin user тАФ
  • getters тАФ
  • mutations тАФ
  • actions тАФ ,

: (mutations) тАФ .

рд╣рдо рдЙрдЪрд┐рдд рдмрджрд▓рд╛рд╡ рдХрд░реЗрдВрдЧреЗ

main.js
 import { store } from './store'; ... new Vue({ router, store, render: h => h(App) }).$mount('#app') 


рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореИрдВ рдПрдХ рдкреНрд░рдпреЛрдЧрд╛рддреНрдордХ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдореЗрдВ рднреА рд╕реБрдВрджрд░ рдФрд░ рд╕рд╛рдл-рд╕реБрдерд░рд╛ рджрд┐рдЦрддрд╛ рд╣реВрдВред рд▓реЗрдХрд┐рди рдпрд╣, рдЬреИрд╕рд╛ рдХрд┐ рд╡реЗ рдХрд╣рддреЗ рд╣реИрдВ, рд╕реНрд╡рд╛рдж рдХрд╛ рдорд╛рдорд▓рд╛ рд╣реИ, рдФрд░ рдмреБрдирд┐рдпрд╛рджреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ:

 $ npm install --save bootstrap bootstrap-vue 

рдмреВрдЯрд╕реНрдЯреНрд░реИрдк рдореЗрдВ main.js
 import BootstrapVue from 'bootstrap-vue' import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css' тАж Vue.use(BootstrapVue) 


рдЕрдм рдРрдк рдХрдВрдкреЛрдиреЗрдВрдЯ рдкрд░ рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВ:

  • рд╕рднреА рдЕрдзрд┐рдХреГрдд рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП "рд▓реЙрдЧ рдЖрдЙрдЯ" рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝реЗрдВ
  • рд▓реЙрдЧрдЖрдЙрдЯ рдХреЗ рдмрд╛рдж рд╣реЛрдо рдкреЗрдЬ рдкрд░ рдПрдХ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реАрдбрд╛рдпрд░реЗрдХреНрдЯ рдЬреЛрдбрд╝реЗрдВ
  • рд╣рдо рд╕рднреА рдЕрдзрд┐рдХреГрдд рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдиреЗрд╡рд┐рдЧреЗрд╢рди рдореЗрдиреВ рдмрдЯрди "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛" рдФрд░ "рд▓реЙрдЧрдЖрдЙрдЯ" рджрд┐рдЦрд╛рдПрдВрдЧреЗ рдФрд░ рдЕрдирдзрд┐рдХреГрдд рд░реВрдк рд╕реЗ "рд▓реЙрдЧрд┐рди" рдХрд░реЗрдВрдЧреЗ
  • рд╣рдо рдиреЗрд╡рд┐рдЧреЗрд╢рди рдореЗрдиреВ рдХрд╛ "рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ" рдмрдЯрди рдХреЗрд╡рд▓ рдЕрдзрд┐рдХреГрдд рдкреНрд░рд╢рд╛рд╕рдХреЛрдВ рдХреЛ рджрд┐рдЦрд╛рдПрдВрдЧреЗ

рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП:

рд▓реЙрдЧрдЖрдЙрдЯ () рд╡рд┐рдзрд┐ рдЬреЛрдбрд╝реЗрдВ
 methods: { logout() { this.$store.dispatch('logout'); this.$router.push('/') } } 


рдФрд░ рдЯреЗрдореНрдкрд▓реЗрдЯ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ
 <template> <div id="app"> <b-navbar style="width: 100%" type="dark" variant="dark"> <b-navbar-brand id="nav-brand" href="#">Kotlin+Spring+Vue</b-navbar-brand> <router-link to="/"><img height="30px" src="./assets/img/kotlin-logo.png" alt="Kotlin+Spring+Vue"/></router-link> <router-link to="/"><img height="30px" src="./assets/img/spring-boot-logo.png" alt="Kotlin+Spring+Vue"/></router-link> <router-link to="/"><img height="30px" src="./assets/img/vuejs-logo.png" alt="Kotlin+Spring+Vue"/></router-link> <router-link to="/user" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated">User</router-link> <router-link to="/admin" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated && this.$store.getters.isAdmin">Admin</router-link> <router-link to="/register" class="nav-link text-light" v-if="!this.$store.getters.isAuthenticated">Register</router-link> <router-link to="/login" class="nav-link text-light" v-if="!this.$store.getters.isAuthenticated">Login</router-link> <a href="#" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated" v-on:click="logout">Logout </a> </b-navbar> <router-view></router-view> </div> </template> 

:

  • store , . , , (┬лv-if┬╗)
  • Kotlin, Spring Boot Vue.js, /assets/img/ . , ( )


рдЕрджреНрдпрддрди рдШрдЯрдХ:

Home.vue
 <template> <div div="home"> <b-jumbotron> <template slot="header">Kotlin + Spring Boot + Vue.js</template> <template slot="lead"> This is the demo web-application written in Kotlin using Spring Boot and Vue.js for frontend </template> <hr class="my-4" /> <p v-if="!this.$store.getters.isAuthenticated"> Login and start </p> <router-link to="/login" v-if="!this.$store.getters.isAuthenticated"> <b-button variant="primary">Login</b-button> </router-link> </b-jumbotron> </div> </template> <script> </script> <style> </style> 


SignIn.vue
 <template> <div div="signin"> <div class="login-form"> <b-card title="Login" tag="article" style="max-width: 20rem;" class="mb-2" > <div> <b-alert :show="dismissCountDown" dismissible variant="danger" @dismissed="dismissCountDown=0" @dismiss-count-down="countDownChanged" > {{ alertMessage }} </b-alert> </div> <div> <b-form-input type="text" placeholder="Username" v-model="username" /> <div class="mt-2"></div> <b-form-input type="password" placeholder="Password" v-model="password" /> <div class="mt-2"></div> </div> <b-button v-on:click="login" variant="primary">Login</b-button> <hr class="my-4" /> <b-button variant="link">Forget password?</b-button> </b-card> </div> </div> </template> <script> import {AXIOS} from './http-common' export default { name: 'SignIn', data() { return { username: '', password: '', dismissSecs: 5, dismissCountDown: 0, alertMessage: 'Request error', } }, methods: { login() { AXIOS.post(`/auth/signin`, {'username': this.$data.username, 'password': this.$data.password}) .then(response => { this.$store.dispatch('login', {'token': response.data.accessToken, 'roles': response.data.authorities, 'username': response.data.username}); this.$router.push('/home') }, error => { this.$data.alertMessage = (error.response.data.message.length < 150) ? error.response.data.message : 'Request error. Please, report this error website owners'; console.log(error) }) .catch(e => { console.log(e); this.showAlert(); }) }, countDownChanged(dismissCountDown) { this.dismissCountDown = dismissCountDown }, showAlert() { this.dismissCountDown = this.dismissSecs }, } } </script> <style> .login-form { margin-left: 38%; margin-top: 50px; } </style> 

:

  • POST-
  • storage
  • ┬л┬╗ Bootstrap
  • , /home


SignUp.vue
 <template> <div div="signup"> <div class="login-form"> <b-card title="Register" tag="article" style="max-width: 20rem;" class="mb-2" > <div> <b-alert :show="dismissCountDown" dismissible variant="danger" @dismissed="dismissCountDown=0" @dismiss-count-down="countDownChanged" > {{ alertMessage }} </b-alert> </div> <div> <b-alert variant="success" :show="successfullyRegistered"> You have been successfully registered! Now you can login with your credentials <hr /> <router-link to="/login"> <b-button variant="primary">Login</b-button> </router-link> </b-alert> </div> <div> <b-form-input type="text" placeholder="Username" v-model="username" /> <div class="mt-2"></div> <b-form-input type="text" placeholder="First Name" v-model="firstname" /> <div class="mt-2"></div> <b-form-input type="text" placeholder="Last name" v-model="lastname" /> <div class="mt-2"></div> <b-form-input type="text" placeholder="Email" v-model="email" /> <div class="mt-2"></div> <b-form-input type="password" placeholder="Password" v-model="password" /> <div class="mt-2"></div> <b-form-input type="password" placeholder="Confirm Password" v-model="confirmpassword" /> <div class="mt-2"></div> </div> <b-button v-on:click="register" variant="primary">Register</b-button> </b-card> </div> </div> </template> <script> import {AXIOS} from './http-common' export default { name: 'SignUp', data () { return { username: '', firstname: '', lastname: '', email: '', password: '', confirmpassword: '', dismissSecs: 5, dismissCountDown: 0, alertMessage: '', successfullyRegistered: false } }, methods: { register: function () { if (this.$data.username === '' || this.$data.username == null) { this.$data.alertMessage = 'Please, fill "Username" field'; this.showAlert(); } else if (this.$data.firstname === '' || this.$data.firstname == null) { this.$data.alertMessage = 'Please, fill "First name" field'; this.showAlert(); } else if (this.$data.lastname === '' || this.$data.lastname == null) { this.$data.alertMessage = 'Please, fill "Last name" field'; this.showAlert(); } else if (this.$data.email === '' || this.$data.email == null) { this.$data.alertMessage = 'Please, fill "Email" field'; this.showAlert(); } else if (!this.$data.email.includes('@')) { this.$data.alertMessage = 'Email is incorrect'; this.showAlert(); } else if (this.$data.password === '' || this.$data.password == null) { this.$data.alertMessage = 'Please, fill "Password" field'; this.showAlert(); } else if (this.$data.confirmpassword === '' || this.$data.confirmpassword == null) { this.$data.alertMessage = 'Please, confirm password'; this.showAlert(); } else if (this.$data.confirmpassword !== this.$data.password) { this.$data.alertMessage = 'Passwords are not match'; this.showAlert(); } else { var newUser = { 'username': this.$data.username, 'firstName': this.$data.firstname, 'lastName': this.$data.lastname, 'email': this.$data.email, 'password': this.$data.password }; AXIOS.post('/auth/signup', newUser) .then(response => { console.log(response); this.successAlert(); }, error => { this.$data.alertMessage = (error.response.data.message.length < 150) ? error.response.data.message : 'Request error. Please, report this error website owners' this.showAlert(); }) .catch(error => { console.log(error); this.$data.alertMessage = 'Request error. Please, report this error website owners'; this.showAlert(); }); } }, countDownChanged(dismissCountDown) { this.dismissCountDown = dismissCountDown }, showAlert() { this.dismissCountDown = this.dismissSecs }, successAlert() { this.username = ''; this.firstname = ''; this.lastname = ''; this.email = ''; this.password = ''; this.confirmpassword = ''; this.successfullyRegistered = true; } } } </script> <style> .login-form { margin-left: 38%; margin-top: 50px; } </style> 

:

  • POST-
  • Bootstrap
  • , Bootstrap-


UserPage.vue
 <template> <div div="userpage"> <h2>{{ pageContent }}</h2> </div> </template> <script> import {AXIOS} from './http-common' export default { name: 'UserPage', data() { return { pageContent: '' } }, methods: { loadUserContent() { const header = {'Authorization': 'Bearer ' + this.$store.getters.getToken}; AXIOS.get('/usercontent', { headers: header }) .then(response => { this.$data.pageContent = response.data; }) .catch(error => { console.log('ERROR: ' + error.response.data); }) } }, mounted() { this.loadUserContent(); } } </script> <style> </style> 

:
  • , storage


Admin.vue
 <template> <div div="adminpage"> <h2>{{ pageContent }}</h2> </div> </template> <script> import {AXIOS} from './http-common' export default { name: 'AdminPage', data() { return { pageContent: '' } }, methods: { loadUserContent() { const header = {'Authorization': 'Bearer ' + this.$store.getters.getToken}; AXIOS.get('/admincontent', { headers: header }) .then(response => { this.$data.pageContent = response.data; }) .catch(error => { console.log('ERROR: ' + error.response.data); }) } }, mounted() { this.loadUserContent(); } } </script> <style> </style> 

, UserPage .

рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓реЙрдиреНрдЪ


рд╣рдо рдЕрдкрдиреЗ рдкрд╣рд▓реЗ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдХреЛ рдкрдВрдЬреАрдХреГрдд рдХрд░реЗрдВрдЧреЗ:





рдорд╣рддреНрд╡рдкреВрд░реНрдг: рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ, рд╕рднреА рдирдП рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд┐рдпрдорд┐рдд рд╣реИрдВред рдЖрдЗрдП рдкрд╣рд▓реЗ рдкреНрд░рд╢рд╛рд╕рдХ рдХреЛ рдЙрд╕рдХрд╛ рдЕрдзрд┐рдХрд╛рд░ рджреЗрдВ:

 INSERT INTO users_roles (user_id, role_id) VALUES (1, 2); 

рддреЛ:

  1. рдЖрдЗрдП рдПрдХ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдХреЗ рд░реВрдк рдореЗрдВ рд▓реЙрдЧ рдЗрди рдХрд░реЗрдВ
  2. рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреГрд╖реНрда рдХреА рдЬрд╛рдБрдЪ рдХрд░реЗрдВ:

  3. рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдкреГрд╖реНрда рдЬрд╛рдВрдЪреЗрдВ:

  4. рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдЦрд╛рддреЗ рд╕реЗ рд▓реЙрдЧ рдЖрдЙрдЯ рдХрд░реЗрдВ
  5. рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЦрд╛рддрд╛ рдкрдВрдЬреАрдХреГрдд рдХрд░реЗрдВ
  6. рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреГрд╖реНрда рдХреА рдЙрдкрд▓рдмреНрдзрддрд╛ рдХреА рдЬрд╛рдБрдЪ рдХрд░реЗрдВ
  7. рдЖрдЗрдП REST API: http: // localhost: 8080 / api / admincontent рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВ

 ERROR 77100 --- [nio-8080-exec-2] ckbackend.jwt.JwtAuthEntryPoint : Unauthorized error. Message - Full authentication is required to access this resource 


рд╕реБрдзрд╛рд░ рдХрд░рдиреЗ рдХреЗ рддрд░реАрдХреЗ


рдЖрдо рддреМрд░ рдкрд░, рдХрд┐рд╕реА рднреА рд╡реНрдпрд╡рд╕рд╛рдп рдореЗрдВ рд╣рдореЗрд╢рд╛ рдЙрдирдореЗрдВ рд╕реЗ рдмрд╣реБрдд рд╕рд╛рд░реЗ рд╣реЛрддреЗ рд╣реИрдВред рдореИрдВ рд╕рдмрд╕реЗ рд╕реНрдкрд╖реНрдЯ рд╕реВрдЪреА рджреВрдВрдЧрд╛:

  • рдЧреНрд░реЗрдбрд▓ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ (рдпрджрд┐ рдЖрдк рдЗрд╕реЗ рд╕реБрдзрд╛рд░ рдорд╛рдирддреЗ рд╣реИрдВ)
  • рдпреВрдирд┐рдЯ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╕рд╛рде рдХреЛрдб рдХреЛ рддреБрд░рдВрдд рдХрд╡рд░ рдХрд░реЗрдВ (рдпрд╣ рдХреЛрдИ рд╕рдВрджреЗрд╣ рдирд╣реАрдВ рд╣реИ, рдЕрдЪреНрдЫрд╛ рдЕрднреНрдпрд╛рд╕ рд╣реИ)
  • рдмрд╣реБрдд рд╢реБрд░реБрдЖрдд рд╕реЗ, CI / CD рдкрд╛рдЗрдкрд▓рд╛рдЗрди рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░реЗрдВ: рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдЬрдЧрд╣ рдХреЛрдб, рдЖрд╡реЗрджрди рдХреЛ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВ, рд╡рд┐рдзрд╛рдирд╕рднрд╛ рдФрд░ рддреИрдирд╛рддреА рдХреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдХрд░реЗрдВ
  • PUT рдФрд░ DELETE рдЕрдиреБрд░реЛрдз рдЬреЛрдбрд╝реЗрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛ рдФрд░ рдЦрд╛рддреЛрдВ рдХреЛ рд╣рдЯрд╛рдирд╛)
  • рдЦрд╛рддрд╛ рд╕рдХреНрд░рд┐рдпрдг / рдирд┐рд╖реНрдХреНрд░рд┐рдпрдХрд░рдг рд▓рд╛рдЧреВ рдХрд░реЗрдВ
  • рдЯреЛрдХрди рд╕реНрдЯреЛрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрдерд╛рдиреАрдп рднрдВрдбрд╛рд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рди рдХрд░реЗрдВ - рдпрд╣ рд╕реБрд░рдХреНрд╖рд┐рдд рдирд╣реАрдВ рд╣реИ
  • OAuth рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ
  • рдирдпрд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрдВрдЬреАрдХреГрдд рдХрд░рддреЗ рд╕рдордп рдИрдореЗрд▓ рдкрддреЗ рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ
  • рд╕реНрдкреИрдо рд╕реБрд░рдХреНрд╖рд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ, рдЙрджрд╛ред ReCAPTCHA


рдЙрдкрдпреЛрдЧреА рд▓рд┐рдВрдХ




рдЗрд╕ рд╕рд╛рдордЧреНрд░реА рдХреЛ рдпрд╣рд╛рдВ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рд╣реИред

Source: https://habr.com/ru/post/hi467161/


All Articles