Spring Boot - 个人博客 - 登录

271 阅读2分钟

文章目录


在这里插入图片描述


1. 需求分析

管理后台的登录的唯一目的就是去验证用户的合法性,即根据输入的用户名和密码能否在数据库中找到相应的用户。因此,登录页需要做的最重要的工作就是验证。验证可以分为前端和后端两个方面进行:

  • 前端:不允许不输入用户名或密码来直接登录,如果用户在这两项中有一项没有输入相应的信息,前端页面应该要给出相应的提示;如果根据输入的用户名和密码无法在数据库中找到合法的用户,那么也应该在前端页面给出相应的提示,告诉用户用户名或密码错误
  • 后端:如果用户正常的输入了用户名和密码,那么接下来就需要根据用户名和密码到数据库中进行验证,那么就要求在数据持久层应该有相应的方法。如果根据用户名和密码能找到合法的用户,那么将跳转到欢迎页,否则在前端给出错误提示

在分析了前端和后端对于用户登录的需求后,我们就可以分别对其进行处理,从而实现用户登录验证管理功能。

2. 前端验证

登录页较为简单,本质上就是一个form表单

 <div class="m-container-small m-padded-tb-massive" style="max-width: 30em !important;">
   <div class="ur container">
     <div class="ui middle aligned center aligned grid">
       <div class="column">
         <h2 class="ui teal image header">
           <div class="content">
             管理后台登录
           </div>
         </h2>
         <form class="ui large form" method="post" th:action="@{/admin/login}">
           <div class="ui  segment">
             <!--用户名输入框-->
             <div class="field">
               <div class="ui left icon input">
                 <i class="user icon"></i>
                 <input type="text" name="username" placeholder="用户名">
               </div>
             </div>
             <!--用户名输入框-->
             <div class="field">
               <div class="ui left icon input">
                 <i class="lock icon"></i>
                 <input type="password" name="password" placeholder="密码">
               </div>
             </div>
             <button class="ui fluid large teal submit button">登   录</button>
           </div>
           <div class="ui error mini message"></div>
           <div class="ui mini negative message" th:unless="${#strings.isEmpty(message)}" th:text="${message}">用户名或密码错误</div>
         </form>
       </div>
     </div>
   </div>
 </div>

对于前端来说,主要工作是进行进行表单的验证,不允许用户输入空的信息,针对的部分就是<div class="ui segment">所定义的form表单。因此,可以通过JS对表单进行验证,如下所示:

<script>
  $('.ui.form').form({
    fields : {
      username : {
        identifier: 'username',
        rules: [{
          type : 'empty',
          prompt: '请输入用户名'
        }]
      },
      password : {
        identifier: 'password',
        rules: [{
          type : 'empty',
          prompt: '请输入密码'
        }]
      }
    }
  });
</script>

首先通过类'.ui.form'找到form表单,然后对于其中的两个field区域,即用户名输入和密码输入定义规则:

  • 如果用户名为空,则前端提示请输入用户名
  • 如果密码为空,则前端提示请输入密码

整体上最后是这样的一个效果,当我不输入任何信息时,页面就会弹出信息,另外对应的框会变红,提示这是一个不合法的登录请求。
在这里插入图片描述
如果用户名或密码错误,同样会给出提示信息。
在这里插入图片描述

3. 后端验证

首先在表现层应该有一个Controller来处理登录请求,然后使用localhost:8080/admin就可以返回登录页,如下所示:

@Controller
@RequestMapping("/admin")
public class LoginController {

    @Autowired
    private UserService userService;

    @GetMapping
    public String loginPage(){
        return "admin/login";
    }
}

程序会在resource目录及其子目录下找名为login.html的页面进行加载。

而在form表单中使用thymleaf定义的动作是th:action="@{/admin/login},因此需要一个方法处理/admin/login为链接的请求,进行用户验证,如下所示

@Controller
@RequestMapping("/admin")
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        HttpSession session, RedirectAttributes attributes){
		...
    }
}

首先使用@PostMapping表示该方法处理的一个post请求,然后使用@RequestParam从request域中拿到前端输入的用户名(username)和密码(password)。同时使用HttpSession在session中记录登录的用户,使用RedirectAttributes实现重定向,当用户名或密码输入错误时,仍然重定向回登录页。

那么拿到用户名和密码后,在业务层需要使用它们到数据库中找有没有相应的用户。因此,业务层需要定义checkUser()来进行验证。

public interface UserService {
    
    User checkUser(String username, String password);
}

接口的实现类为:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User checkUser(String username, String password) {
        // 控制台显示信息
        System.out.println(username + " " + password);
        // 验证
        User user = userRepository.findByUsernameAndPassword(username, password);

        return user;
    }
}

那么在持久层就需要相应的操作方法,根据Jpa的命名规则findByxxxAndxxx定义相应的方法findByUsernameAndPassword(),只需要定义相应的方法即可,并不需要自己写处理逻辑,Jpa会自动的帮我们处理:

public interface UserRepository extends JpaRepository<User,Long> {

    User findByUsernameAndPassword(String username, String password);
}

不熟悉Jpa可以查阅我之前的一篇博文:一文详尽 Spring Data JPA 的日常使用

再次回到业务层,当调用UserRepository中的方法查询用户后会得到一个User对象。如果可以找到,那么user不为空,否则user就等于null,表示该用户不存在。因此,最终表现层通过调用业务层的方法就能拿到一个user,其中处理post请求的方法处理逻辑如下所示:

@PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        HttpSession session, RedirectAttributes attributes){
		// 拿到User对象
        User user = userService.checkUser(username, password);
        // 如果当前user合法
        if (user != null){
            // 那么在session中保存user,同时为了安全将password置空
            user.setPassword(null);
            session.setAttribute("user", user);
            // 跳转到欢迎页
            return "admin/index";
        } else {
            // 如果当前的user为空,说明用户名或密码错误
            // 使用attributes向前端传递提示message
            attributes.addFlashAttribute("message", "用户名或密码错误");
            // 重定向回登录页
            return "redirect:/admin";
        }
    }

这样后端的验证工作就处理结束了,主要就是验证用户名和密码在数据库中是否存在。另外,通常为了密码的安全,尝试用MD5进行加密,这样数据库中保存的密码其实是加密后的结果。

public class MD5Utils {
    public static String code(String str){
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[]byteDigest = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < byteDigest.length; offset++) {
                i = byteDigest[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            //32位加密
            return buf.toString();
            // 16位的加密
            //return buf.toString().substring(8, 24);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }

    }
}

使用MD5加密后,checkUser()就变成了如下的形式:

@Override
public User checkUser(String username, String password) {
    System.out.println(username + " " + password);
    User user = userRepository.findByUsernameAndPassword(username, MD5Utils.code(password));
    
    return user;
}

即传递到数据库中的也是加密后的结果。如果一切顺利通过,那么最终会正常跳转到后台的欢迎页。
在这里插入图片描述

另外,还需要一个注销功能,当用户点击右上角的注销时会退出登录,重新回到登录页。因此,导航栏的注销按钮部分就需要使用th:href添加一个跳转

<i class="dropdown icon"></i>
<div class="menu">
    <a href="#" th:href="@{/admin/logout}" class="item">注销</a>
</div>

然后表现层需要有方法来处理,即将之前保存在session域中的user清除掉,最后重定向会登录页。

@GetMapping("/logout")
public String logout(HttpSession session){
    session.removeAttribute("user");
    return "redirect:/admin";
}

4. 登录欢迎页

登录欢迎页只是简单的展示一个页面,并不会处理其他的请求,页面设计如下:

<div class="m-container-small m-padded-tb-big">
    <div class="ui container">
        <div class="ui success large  message">
            <h3 th:text="'Hi, ' + ${session.user.nickname}+' ,欢迎回来!'">Hi, Forlogen,欢迎登录!</h3>
        </div>
        <img src="../static/images/manba.jpg" th:src="@{/images/manba.jpg}" alt=""
             class="ui rounded bordered fluid image">
    </div>
</div

这里需要从session域中拿到user对象的nickname字段,然后使用th:text进行动态的替换;而显示的图片使用th:src来指定图片存放的路径。