完成登录与注册;添加用户信息部分

This commit is contained in:
Saturneric 2020-03-20 22:23:38 +08:00
parent 95d59748b0
commit c70895dcc6
17 changed files with 600 additions and 15 deletions

View File

@ -8,6 +8,10 @@ import javax.validation.constraints.NotEmpty;
@Data
public class RegisterForm {
@NotEmpty
@Length(min = 4, max = 16)
private String nickName;
@NotEmpty
@Length(min = 6, max = 24)
private String username;

View File

@ -1,8 +1,6 @@
package com.bktus.iserver.configure;
import com.bktus.iserver.configure.security.IServerPasswordEncoder;
import com.bktus.iserver.configure.security.IServerSecurityAuthenticationProvider;
import com.bktus.iserver.configure.security.IServerUsernamePasswordAuthenticationFilter;
import com.bktus.iserver.configure.security.*;
import com.bktus.iserver.service.IServerUserDetailService;
import com.bktus.iserver.service.IUserService;
import org.springframework.context.annotation.Bean;
@ -28,6 +26,12 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private IServerPasswordEncoder passwordEncoder;
@Resource
private IServerAuthenticationSuccessHandler successHandler;
@Resource
private IServerAuthenticationFailureHandler failureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
@ -59,7 +63,6 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
web
.ignoring()
.antMatchers(
"/",
"/build/**",
"/dist/**",
"/plugins/**",
@ -72,6 +75,8 @@ public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
IServerUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
IServerUsernamePasswordAuthenticationFilter filter = new IServerUsernamePasswordAuthenticationFilter();
filter.setAllowSessionCreation(true);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationFailureHandler(failureHandler);
filter.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/login", "POST"));

View File

@ -0,0 +1,19 @@
package com.bktus.iserver.configure.security;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class IServerAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}

View File

@ -0,0 +1,39 @@
package com.bktus.iserver.configure.security;
import com.bktus.iserver.model.User;
import com.bktus.iserver.service.IUserService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Date;
@Component
public class IServerAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Resource
IUserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
// 添加Session
HttpSession session = request.getSession();
SecurityContext securityContext = SecurityContextHolder.getContext();
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
User user = (User) authentication.getPrincipal();
user.getUserInfo().setLoginDate(new Date());
userService.save(user);
super.onAuthenticationSuccess(request, response, authentication);
}
}

View File

@ -35,7 +35,7 @@ public class IServerSecurityAuthenticationProvider implements AuthenticationProv
throw new BadCredentialsException("Password IS Uncorrected");
}
return new UsernamePasswordAuthenticationToken(username, password, details.getAuthorities());
return new UsernamePasswordAuthenticationToken(details, password, details.getAuthorities());
}
@Override

View File

@ -0,0 +1,14 @@
package com.bktus.iserver.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "/error")
public class ErrorController {
@RequestMapping(value = "/")
public String printErrorView(){
return "500";
}
}

View File

@ -1,14 +1,25 @@
package com.bktus.iserver.controller;
import com.bktus.iserver.model.User;
import org.apache.tomcat.util.descriptor.web.ContextHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.security.Principal;
@Controller
@RequestMapping(value = "/")
public class IndexController {
@RequestMapping(value = "/")
public String indexView(){
public String indexView(Model model, Authentication authentication){
User user = (User) authentication.getPrincipal();
model.addAttribute("username", authentication.getName());
model.addAttribute("nickname", user.getUserInfo().getNickName());
return "index";
}

View File

@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import java.util.Date;
@Controller
@RequestMapping(value = "/register")
@ -35,7 +36,8 @@ public class RegisterController {
}
User user = userService.createUser(registerForm.getUsername(), registerForm.getPassword());
user.getUserInfo().setRegisterDate(new Date());
user.getUserInfo().setNickName(registerForm.getNickName());
userService.save(user);
return "login";

View File

@ -0,0 +1,23 @@
package com.bktus.iserver.controller;
import com.bktus.iserver.model.User;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "profile/{username}")
public String printProfileView(Model model, @PathVariable String username, Authentication authentication){
User user = (User) authentication.getPrincipal();
model.addAttribute("username", user.getUsername());
model.addAttribute("nickname", user.getUserInfo().getNickName());
model.addAttribute("user", user);
return "profile";
}
}

View File

@ -27,6 +27,9 @@ public class User implements UserDetails {
private boolean enabled = true;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserInfo userInfo = new UserInfo();
@ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private Set<Role> roles = new HashSet<>();

View File

@ -0,0 +1,21 @@
package com.bktus.iserver.model;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@Table(name = "user_info")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nickName = "";
private Date registerDate;
private Date loginDate;
}

View File

@ -1,4 +1,4 @@
server.port=8080
server.port=80
spring.thymeleaf.prefix=classpath:templates/
spring.thymeleaf.suffix=.html

View File

@ -9,19 +9,19 @@
<th:block th:fragment="sidebar-head">
<a href="index3.html" class="brand-link">
<img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
<img th:src="@{/dist/img/AdminLTELogo.png}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
style="opacity: .8">
<span class="brand-text font-weight-light">IServer</span>
</a>
</th:block>
<th:block th:fragment="sidebar-user-pannel">
<th:block th:fragment="sidebar-user-pannel(username, nickname)">
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
<div class="image">
<img src="dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">
<img th:src="@{/dist/img/user2-160x160.jpg}" class="img-circle elevation-2" alt="User Image">
</div>
<div class="info">
<a href="#" class="d-block">Alexander Pierce</a>
<a th:href="@{'/user/profile/' + ${username}} " href="#" class="d-block"><span th:text="${nickname}"></span></a>
</div>
</div>
</th:block>

View File

@ -14,7 +14,7 @@
<!-- /.navbar -->
<!-- Main Sidebar Container -->
<th:block th:include="layout::sidebar"></th:block>
<th:block th:include="layout::sidebar(${username}, ${nickname})"></th:block>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">

View File

@ -159,7 +159,7 @@
</nav>
</th:block>
<th:block th:fragment="sidebar">
<th:block th:fragment="sidebar(username, nickname)">
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo -->
<th:block th:include="component::sidebar-head"></th:block>
@ -167,7 +167,7 @@
<!-- Sidebar -->
<div class="sidebar">
<!-- Sidebar user panel (optional) -->
<th:block th:include="component::sidebar-user-pannel" ></th:block>
<th:block th:include="component::sidebar-user-pannel(${username}, ${nickname})" ></th:block>
<!-- Sidebar Menu -->
<th:block th:include="component::sidebar-menu"></th:block>
<!-- /.sidebar-menu -->

View File

@ -0,0 +1,436 @@
<!DOCTYPE html>
<html lang="zh-cn"
xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>AdminLTE 3 | User Profile</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Font Awesome -->
<link rel="stylesheet" href="../../plugins/fontawesome-free/css/all.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="../../dist/css/adminlte.min.css">
<!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head>
<body class="hold-transition sidebar-mini">
<div class="wrapper">
<!-- Navbar -->
<th:block th:include="layout::navbar"></th:block>
<!-- /.navbar -->
<!-- Main Sidebar Container -->
<th:block th:include="layout::sidebar(${username}, ${nickname})"></th:block>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Profile</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active">User Profile</li>
</ol>
</div>
</div>
</div><!-- /.container-fluid -->
</section>
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<!-- Profile Image -->
<div class="card card-primary card-outline">
<div class="card-body box-profile">
<div class="text-center">
<img class="profile-user-img img-fluid img-circle"
src="../../dist/img/user4-128x128.jpg"
alt="User profile picture">
</div>
<h3 class="profile-username text-center" th:text="${nickname}"></h3>
<p class="text-muted text-center" th:text="${username}"></p>
<ul class="list-group list-group-unbordered mb-3">
<li class="list-group-item">
<b>注册时间</b> <a class="float-right" th:text="${user.getUserInfo().getRegisterDate()}"></a>
</li>
<li class="list-group-item">
<b>登录时间</b> <a class="float-right" th:text="${user.getUserInfo().getLoginDate()}"></a>
</li>
<li class="list-group-item">
<b>Friends</b> <a class="float-right">13,287</a>
</li>
</ul>
<a href="#" class="btn btn-primary btn-block"><b>Follow</b></a>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
<!-- About Me Box -->
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">About Me</h3>
</div>
<!-- /.card-header -->
<div class="card-body">
<strong><i class="fas fa-book mr-1"></i> Education</strong>
<p class="text-muted">
B.S. in Computer Science from the University of Tennessee at Knoxville
</p>
<hr>
<strong><i class="fas fa-map-marker-alt mr-1"></i> Location</strong>
<p class="text-muted">Malibu, California</p>
<hr>
<strong><i class="fas fa-pencil-alt mr-1"></i> Skills</strong>
<p class="text-muted">
<span class="tag tag-danger">UI Design</span>
<span class="tag tag-success">Coding</span>
<span class="tag tag-info">Javascript</span>
<span class="tag tag-warning">PHP</span>
<span class="tag tag-primary">Node.js</span>
</p>
<hr>
<strong><i class="far fa-file-alt mr-1"></i> Notes</strong>
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam fermentum enim neque.</p>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
<!-- /.col -->
<div class="col-md-9">
<div class="card">
<div class="card-header p-2">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link active" href="#activity" data-toggle="tab">Activity</a></li>
<li class="nav-item"><a class="nav-link" href="#timeline" data-toggle="tab">Timeline</a></li>
<li class="nav-item"><a class="nav-link" href="#settings" data-toggle="tab">Settings</a></li>
</ul>
</div><!-- /.card-header -->
<div class="card-body">
<div class="tab-content">
<div class="active tab-pane" id="activity">
<!-- Post -->
<div class="post">
<div class="user-block">
<img class="img-circle img-bordered-sm" src="../../dist/img/user1-128x128.jpg" alt="user image">
<span class="username">
<a href="#">Jonathan Burke Jr.</a>
<a href="#" class="float-right btn-tool"><i class="fas fa-times"></i></a>
</span>
<span class="description">Shared publicly - 7:30 PM today</span>
</div>
<!-- /.user-block -->
<p>
Lorem ipsum represents a long-held tradition for designers,
typographers and the like. Some people hate it and argue for
its demise, but others ignore the hate as they create awesome
tools to help create filler text for everyone from bacon lovers
to Charlie Sheen fans.
</p>
<p>
<a href="#" class="link-black text-sm mr-2"><i class="fas fa-share mr-1"></i> Share</a>
<a href="#" class="link-black text-sm"><i class="far fa-thumbs-up mr-1"></i> Like</a>
<span class="float-right">
<a href="#" class="link-black text-sm">
<i class="far fa-comments mr-1"></i> Comments (5)
</a>
</span>
</p>
<input class="form-control form-control-sm" type="text" placeholder="Type a comment">
</div>
<!-- /.post -->
<!-- Post -->
<div class="post clearfix">
<div class="user-block">
<img class="img-circle img-bordered-sm" src="../../dist/img/user7-128x128.jpg" alt="User Image">
<span class="username">
<a href="#">Sarah Ross</a>
<a href="#" class="float-right btn-tool"><i class="fas fa-times"></i></a>
</span>
<span class="description">Sent you a message - 3 days ago</span>
</div>
<!-- /.user-block -->
<p>
Lorem ipsum represents a long-held tradition for designers,
typographers and the like. Some people hate it and argue for
its demise, but others ignore the hate as they create awesome
tools to help create filler text for everyone from bacon lovers
to Charlie Sheen fans.
</p>
<form class="form-horizontal">
<div class="input-group input-group-sm mb-0">
<input class="form-control form-control-sm" placeholder="Response">
<div class="input-group-append">
<button type="submit" class="btn btn-danger">Send</button>
</div>
</div>
</form>
</div>
<!-- /.post -->
<!-- Post -->
<div class="post">
<div class="user-block">
<img class="img-circle img-bordered-sm" src="../../dist/img/user6-128x128.jpg" alt="User Image">
<span class="username">
<a href="#">Adam Jones</a>
<a href="#" class="float-right btn-tool"><i class="fas fa-times"></i></a>
</span>
<span class="description">Posted 5 photos - 5 days ago</span>
</div>
<!-- /.user-block -->
<div class="row mb-3">
<div class="col-sm-6">
<img class="img-fluid" src="../../dist/img/photo1.png" alt="Photo">
</div>
<!-- /.col -->
<div class="col-sm-6">
<div class="row">
<div class="col-sm-6">
<img class="img-fluid mb-3" src="../../dist/img/photo2.png" alt="Photo">
<img class="img-fluid" src="../../dist/img/photo3.jpg" alt="Photo">
</div>
<!-- /.col -->
<div class="col-sm-6">
<img class="img-fluid mb-3" src="../../dist/img/photo4.jpg" alt="Photo">
<img class="img-fluid" src="../../dist/img/photo1.png" alt="Photo">
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<p>
<a href="#" class="link-black text-sm mr-2"><i class="fas fa-share mr-1"></i> Share</a>
<a href="#" class="link-black text-sm"><i class="far fa-thumbs-up mr-1"></i> Like</a>
<span class="float-right">
<a href="#" class="link-black text-sm">
<i class="far fa-comments mr-1"></i> Comments (5)
</a>
</span>
</p>
<input class="form-control form-control-sm" type="text" placeholder="Type a comment">
</div>
<!-- /.post -->
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="timeline">
<!-- The timeline -->
<div class="timeline timeline-inverse">
<!-- timeline time label -->
<div class="time-label">
<span class="bg-danger">
10 Feb. 2014
</span>
</div>
<!-- /.timeline-label -->
<!-- timeline item -->
<div>
<i class="fas fa-envelope bg-primary"></i>
<div class="timeline-item">
<span class="time"><i class="far fa-clock"></i> 12:05</span>
<h3 class="timeline-header"><a href="#">Support Team</a> sent you an email</h3>
<div class="timeline-body">
Etsy doostang zoodles disqus groupon greplin oooj voxy zoodles,
weebly ning heekya handango imeem plugg dopplr jibjab, movity
jajah plickers sifteo edmodo ifttt zimbra. Babblely odeo kaboodle
quora plaxo ideeli hulu weebly balihoo...
</div>
<div class="timeline-footer">
<a href="#" class="btn btn-primary btn-sm">Read more</a>
<a href="#" class="btn btn-danger btn-sm">Delete</a>
</div>
</div>
</div>
<!-- END timeline item -->
<!-- timeline item -->
<div>
<i class="fas fa-user bg-info"></i>
<div class="timeline-item">
<span class="time"><i class="far fa-clock"></i> 5 mins ago</span>
<h3 class="timeline-header border-0"><a href="#">Sarah Young</a> accepted your friend request
</h3>
</div>
</div>
<!-- END timeline item -->
<!-- timeline item -->
<div>
<i class="fas fa-comments bg-warning"></i>
<div class="timeline-item">
<span class="time"><i class="far fa-clock"></i> 27 mins ago</span>
<h3 class="timeline-header"><a href="#">Jay White</a> commented on your post</h3>
<div class="timeline-body">
Take me to your leader!
Switzerland is small and neutral!
We are more like Germany, ambitious and misunderstood!
</div>
<div class="timeline-footer">
<a href="#" class="btn btn-warning btn-flat btn-sm">View comment</a>
</div>
</div>
</div>
<!-- END timeline item -->
<!-- timeline time label -->
<div class="time-label">
<span class="bg-success">
3 Jan. 2014
</span>
</div>
<!-- /.timeline-label -->
<!-- timeline item -->
<div>
<i class="fas fa-camera bg-purple"></i>
<div class="timeline-item">
<span class="time"><i class="far fa-clock"></i> 2 days ago</span>
<h3 class="timeline-header"><a href="#">Mina Lee</a> uploaded new photos</h3>
<div class="timeline-body">
<img src="http://placehold.it/150x100" alt="...">
<img src="http://placehold.it/150x100" alt="...">
<img src="http://placehold.it/150x100" alt="...">
<img src="http://placehold.it/150x100" alt="...">
</div>
</div>
</div>
<!-- END timeline item -->
<div>
<i class="far fa-clock bg-gray"></i>
</div>
</div>
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="settings">
<form class="form-horizontal">
<div class="form-group row">
<label for="inputName" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputName" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label for="inputEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail" placeholder="Email">
</div>
</div>
<div class="form-group row">
<label for="inputName2" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputName2" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label for="inputExperience" class="col-sm-2 col-form-label">Experience</label>
<div class="col-sm-10">
<textarea class="form-control" id="inputExperience" placeholder="Experience"></textarea>
</div>
</div>
<div class="form-group row">
<label for="inputSkills" class="col-sm-2 col-form-label">Skills</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputSkills" placeholder="Skills">
</div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> I agree to the <a href="#">terms and conditions</a>
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<button type="submit" class="btn btn-danger">Submit</button>
</div>
</div>
</form>
</div>
<!-- /.tab-pane -->
</div>
<!-- /.tab-content -->
</div><!-- /.card-body -->
</div>
<!-- /.nav-tabs-custom -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<footer class="main-footer">
<div class="float-right d-none d-sm-block">
<b>Version</b> 3.0.2
</div>
<strong>Copyright &copy; 2014-2019 <a href="http://adminlte.io">AdminLTE.io</a>.</strong> All rights
reserved.
</footer>
<!-- Control Sidebar -->
<aside class="control-sidebar control-sidebar-dark">
<!-- Control sidebar content goes here -->
</aside>
<!-- /.control-sidebar -->
</div>
<!-- ./wrapper -->
<!-- jQuery -->
<script src="../../plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="../../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="../../dist/js/adminlte.min.js"></script>
<!-- AdminLTE for demo purposes -->
<script src="../../dist/js/demo.js"></script>
</body>
</html>

View File

@ -16,6 +16,14 @@
<p class="login-box-msg">注册来获得属于自己的空间</p>
<form th:action="@{/register}" th:object="${registerForm}" method="post">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="昵称" th:field="*{nickName}">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="用户名" th:field="*{username}">
<div class="input-group-append">