diff --git a/build.gradle b/build.gradle index 4e0e9cb..b00646e 100644 --- a/build.gradle +++ b/build.gradle @@ -120,6 +120,10 @@ dependencies { compile group: 'org.webjars', name: 'jquery', version: '3.3.1-1' compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1' compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0' + compile group: 'org.webjars.npm', name: 'react', version: '16.7.0' + compile group: 'org.webjars.npm', name: 'react-dom', version: '16.7.0' + compile group: 'org.webjars.npm', name: 'material-ui__core', version: '3.5.1' + compile group: 'org.webjars.npm', name: 'babel-standalone', version: '6.26.0' compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0' compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0' diff --git a/src/main/java/ru/ulstu/configuration/Constants.java b/src/main/java/ru/ulstu/configuration/Constants.java index 0a2268a..fb3809c 100644 --- a/src/main/java/ru/ulstu/configuration/Constants.java +++ b/src/main/java/ru/ulstu/configuration/Constants.java @@ -10,7 +10,7 @@ public class Constants { public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$"; public static final String COOKIES_NAME = "JSESSIONID"; - public static final String LOGOUT_URL = "/login?logout"; + public static final String LOGOUT_URL = "/login"; public static final String SESSION_ID_ATTR = "sessionId"; public static final int SESSION_TIMEOUT_SECONDS = 30 * 60; diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java index da498fa..dc97ee4 100644 --- a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java @@ -13,6 +13,7 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import ru.ulstu.user.controller.UserController; @@ -33,17 +34,20 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final UserService userService; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final AuthenticationSuccessHandler authenticationSuccessHandler; + private final AuthenticationFailureHandler authenticationFailureHandler; private final LogoutSuccessHandler logoutSuccessHandler; private final ApplicationProperties applicationProperties; public SecurityConfiguration(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder, AuthenticationSuccessHandler authenticationSuccessHandler, + AuthenticationFailureHandler authenticationFailureHandler, LogoutSuccessHandler logoutSuccessHandler, ApplicationProperties applicationProperties) { this.userService = userService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; this.authenticationSuccessHandler = authenticationSuccessHandler; + this.authenticationFailureHandler = authenticationFailureHandler; this.logoutSuccessHandler = logoutSuccessHandler; this.applicationProperties = applicationProperties; } @@ -75,6 +79,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .and() .formLogin() .loginPage("/login") + .failureHandler(authenticationFailureHandler) .successHandler(authenticationSuccessHandler) .permitAll() .and() diff --git a/src/main/java/ru/ulstu/user/component/UserSessionLoginFailureHandler.java b/src/main/java/ru/ulstu/user/component/UserSessionLoginFailureHandler.java new file mode 100644 index 0000000..12e1670 --- /dev/null +++ b/src/main/java/ru/ulstu/user/component/UserSessionLoginFailureHandler.java @@ -0,0 +1,34 @@ +package ru.ulstu.user.component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.HashMap; + +@Component +public class UserSessionLoginFailureHandler implements AuthenticationFailureHandler { + + private ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { + + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getOutputStream() + .write("Не удается войти.
Пожалуйста, проверьте правильность написания логина и пароля.".getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fb1b116..7603c2c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,5 +34,5 @@ liquibase.change-log=classpath:db/changelog-master.xml # Application Settings ng-tracker.base-url=http://127.0.0.1:8080 ng-tracker.undead-user-login=admin -ng-tracker.dev-mode=true +ng-tracker.dev-mode=false ng-tracker.use-https=false \ No newline at end of file diff --git a/src/main/resources/public/js/react/loginPage/components/login.js b/src/main/resources/public/js/react/loginPage/components/login.js new file mode 100644 index 0000000..db3f96f --- /dev/null +++ b/src/main/resources/public/js/react/loginPage/components/login.js @@ -0,0 +1,100 @@ +const { + Card, + CardHeader, + CardContent, + FormControl, + FormLabel, + TextField, + Button +} = window['material-ui']; + +class LoginForm extends React.Component{ + state = { + isValidUsername: false, + isValidPassword: false, + username: "", + password: "", + errorsMessage: "" + }; + + handleChangeUsername = event => { + this.setState({ + username: event.target.value, + isValidUsername: /^[_'.@A-Za-z0-9-]*$/.test(event.target.value) + }); + }; + + handleChangePassword = event => { + this.setState({ + password: event.target.value, + isValidPassword: (event.target.value.length > 4) + }); + }; + + handleSubmit = loginForm => () => { + if (loginForm.state.isValidUsername && loginForm.state.isValidPassword){ + $.post( + "/login", + loginForm.state, + this.handleResponseAjaxPost(loginForm)("success") + ).fail(this.handleResponseAjaxPost(loginForm)("error")); + } + }; + + handleResponseAjaxPost = loginForm => status => response => { + if (status === "success"){ + window.location.href = "/index"; + loginForm.setState({ + errorsMessage: "" + }); + } else if (status === "error"){ + loginForm.setState({ + errorsMessage: response.responseText, + password: "", + isValidPassword: false + }) + } + }; + + render(){ + return ( + (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0"> + + +
+ + +
+
+
+ + + +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/main/resources/public/js/react/loginPage/components/register.js b/src/main/resources/public/js/react/loginPage/components/register.js new file mode 100644 index 0000000..d2a66fc --- /dev/null +++ b/src/main/resources/public/js/react/loginPage/components/register.js @@ -0,0 +1,62 @@ +const { + Card, + CardHeader, + CardContent, + FormControl, + FormLabel, + TextField, + Button +} = window['material-ui']; + +class RegisterForm extends React.Component{ + state = { + isValidUsername: false, + isValidPassword: false, + username: "", + password: "", + errorsMessage: "" + }; + + handleChangeUsername = event => { + this.setState({ + username: event.target.value, + isValidUsername: (event.target.value.length > 4) + }); + }; + + handleChangePassword = event => { + this.setState({ + password: event.target.value, + isValidPassword: (event.target.value.length > 4) + }); + }; + + handleSubmit = loginForm => () => { + if (loginForm.state.isValidUsername && loginForm.state.isValidPassword){ + $.post( + "/api/1.0/users/register", + loginForm.state, + this.handleResponseAjaxPost(loginForm)("success") + ).fail(this.handleResponseAjaxPost(loginForm)("error")); + } + }; + + handleResponseAjaxPost = loginForm => status => response => { + if (status === "success"){ + window.location.href = "/index"; + loginForm.setState({ + errorsMessage: "" + }); + } else if (status === "error"){ + loginForm.setState({ + errorsMessage: response.responseText, + password: "", + isValidPassword: false + }) + } + }; + + render(){ + return
; + } +} \ No newline at end of file diff --git a/src/main/resources/public/js/react/loginPage/index.js b/src/main/resources/public/js/react/loginPage/index.js new file mode 100644 index 0000000..05929fe --- /dev/null +++ b/src/main/resources/public/js/react/loginPage/index.js @@ -0,0 +1,49 @@ +let {Button, Icon} = window['material-ui']; + +class LoginPage extends React.Component{ + state = { + isLogin: true, + isRegister: false + }; + + handleOpenLogin = loginPage => () => { + loginPage.setState({ + isLogin: true, + isRegister: false + }); + }; + + handleOpenRegister = loginPage => () => { + loginPage.setState({ + isLogin: false, + isRegister: true + }); + } + + render() { + this.form = null; + if (this.state.isLogin) + this.form = ; + else if (this.state.isRegister) + this.form = ; + if ($('.isAuth').length === 0) + return ( +
+
+ + +
+ {this.form} +
+ ); + else + return ( +
+

Ты уже авторизован

+ +
+ ); + } +} + +ReactDOM.render(, document.getElementById("loginForm")); diff --git a/src/main/resources/public/js/react/mainPage/navbar.js b/src/main/resources/public/js/react/mainPage/navbar.js new file mode 100644 index 0000000..1614d3f --- /dev/null +++ b/src/main/resources/public/js/react/mainPage/navbar.js @@ -0,0 +1,118 @@ +const { + Drawer, AppBar, Toolbar, + IconButton, Icon, + Typography, Menu, MenuItem, + List, ListItem, ListItemIcon, + ListItemText, Divider, ListSubheader, + withStyles +} = window['material-ui']; + +const styles = { + root: { + flexGrow: 1, + }, + grow: { + flexGrow: 1, + }, + menuButton: { + marginLeft: -12, + marginRight: 20, + }, +}; + +class Navbar extends React.Component { + state = { + anchorEl: null, + left: false + }; + + handleMenu = event => { + this.setState({ anchorEl: event.currentTarget }); + }; + + handleDrawer = value => () => { + this.setState({ + left: value + }); + }; + + handleClose = () => { + this.setState({ anchorEl: null }); + }; + + render() { + const { classes } = this.props; + const { anchorEl } = this.state; + const open = Boolean(anchorEl); + + let accountContent = null; + if ($('.isAuth').length !== 0) + accountContent = ( +
+ + {$('.isAuth')[0].textContent} account_circle + + + {window.location.href="/acc-info"}}>Мой аккаунт + {window.location.href="/logout"}}>Выход + +
+ ); + + return ( +
+ + + + menu + + +
+ Ссылки}> + {[ + {label: 'НИО-17', link: '#landing'}, + {label: 'Сайт кафедры', link: 'http://is.ulstu.ru'}, + {label: 'КИАС РФФИ', link: 'https://kias.rfbr.ru/'} + ].map(item => ( + {window.location.href=item.link}}> + label_important + + + ))} + +
+
+ + NG-Tracker + + {accountContent} +
+
+
+ ); + } +} + +Navbar.propTypes = { + classes: PropTypes.object.isRequired, +}; + +ReactDOM.render(React.createElement(withStyles(styles)(Navbar), null, null), document.getElementById('react-navbar')); \ No newline at end of file diff --git a/src/main/resources/templates/acc-info.html b/src/main/resources/templates/acc-info.html new file mode 100644 index 0000000..0720627 --- /dev/null +++ b/src/main/resources/templates/acc-info.html @@ -0,0 +1,14 @@ + + + +
+ Login:

+ Имя:

+ Фамилия:

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html index 5bb5c14..fa73c62 100644 --- a/src/main/resources/templates/default.html +++ b/src/main/resources/templates/default.html @@ -1,5 +1,7 @@ - @@ -20,6 +22,7 @@ + @@ -36,33 +39,35 @@ - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - \ No newline at end of file