這篇文章主要實現用戶登錄功能:用戶輸入自己的賬號、密碼以及驗證碼後,會進行身份驗證,認證通過的系統會自動判斷該用戶的身份,跳轉到不同的管理界面。
要實現的功能
- 用戶登錄判斷
- 根據用戶角色自動跳轉不同的頁面
- 加入驗證碼進行驗證
開發前的準備
需要查看另一篇文章
由於想減少文章的篇幅,這部分使用的Shiro
框架部分寫在另一篇文章中:
SpringBoot使用Shiro認證進行登錄和權限管理
設置和連接數據庫
建立數據庫
在Navicat中建立一個數據庫,並建立一張用戶表(用來存儲要登錄的用戶)
用戶登錄主要用到下面這張user表,這裏事先輸入了用戶名和密碼(用於測試,到後面可以自己添加、修改等)。
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主鍵,自增',
`nickname` varchar(10) NOT NULL COMMENT '暱稱',
`username` varchar(10) NOT NULL COMMENT '用戶名',
`password` varchar(15) NOT NULL COMMENT '密碼',
`permission` varchar(10) NOT NULL COMMENT '權限',
`role` varchar(5) NOT NULL COMMENT '角色',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8;
設置application.properties文件
# 數據庫的連接驅動
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 連接數據庫的地址,注意端口和要連接的數據庫名,後面設置時區,不然可能會報錯
spring.datasource.url=jdbc:mysql://localhost:3306/database-manager?serverTimezone=UTC
# 連接的賬號和密碼,根據自己情況
spring.datasource.username=root
spring.datasource.password=123456
# 下面關於mybatis的要設置,不然會報錯,讀取不了數據庫數據。
# mapper xml文件路徑
mybatis.mapper-locations=classpath:mapper/*.xml
# 實體類別名
mybatis.type-aliases-package=pinksmile.database.domin
# 開啓駝峯命名
mybatis.configuration.map-underscore-to-camel-case=true
# 將日誌輸出到控制檯
mybatis.configuration.logImpl=org.apache.ibatis.logging.stdout.StdOutImpl
# 這個開發配置爲false,避免改了模板還要重啓服務器
spring.thymeleaf.cache=false
用IDEA連接數據庫
這一步不是必須的,這樣做在之後寫MyBatis操作數據庫時會很方便
具體步驟
1. 使用Mybatis操作數據庫
配置數據庫數據操作UserMapper.xml
文件:注意與dao層和domain層關聯。
需要注意的地方下面已經標出,介紹一下一些地方參數的意義:
namespace
:這裏要和dao
層的UserDao
也就是User
表操作接口文件關聯resultMap
中的設置實際上就是把數據庫中整個表用resutMap
表示。這裏就用UserMap
來代表數據庫中表的數據。下面的result
是數據庫中user
表中字段映射到這裏,最終整張表可以用UserMap
代替。注意數據庫和java映射的類型不要寫錯,後面有注意的地方。- 其他的內容就是數據庫操作語句,裏面的
id
和dao層的接口名字一樣;返回值的類型根據需要選擇。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--author PinkSmile 用戶表-->
<mapper namespace="pinksmile.database.dao.UserDao">
<resultMap id="UserMap" type="pinksmile.database.domain.User">
<result column="id" javaType="Integer" jdbcType="INTEGER" property="id"/>
<result column="nickname" javaType="String" jdbcType="VARCHAR" property="nickname"/>
<result column="username" javaType="String" jdbcType="VARCHAR" property="username"/>
<result column="password" javaType="String" jdbcType="VARCHAR" property="password"/>
<result column="permission" javaType="String" jdbcType="VARCHAR" property="permission"/>
<result column="role" javaType="String" jdbcType="VARCHAR" property="role"/>
</resultMap>
<!--通過用戶名查詢用戶, 返回是單個用戶-->
<select id="queryByName" resultMap="UserMap">
select id, nickname, username, password, permission, role
from user
where username = #{username}
</select>
<!--通過用戶id獲得用戶角色-->
<select id="getUserRoleByID" resultType="String">
select role from user where id = #{userId}
</select>
</mapper>
2. 實體類的實現
在domin文件夾下創建User.java
文件,該文件映射數據庫的的user
表,以後數據庫中的每張表都會有這個文件。
- 注意代碼中的註釋
- 使用了lombok,這個需要在IDEA中下載插件
package pinksmile.database.domain;
import lombok.Data;
/**
* 登錄的用戶表
* @author PinkSmile
*/
@Data
public class User {
/**
* 數據庫用戶標的字段
* 保證字段和數據類型一致
*/
private Integer id;
private String nickname;
private String username;
private String password;
private String permission;
private String role;
}
3. Dao層接口類實現
在Dao層下創建UserDao.java
的接口類,內容如下:
package pinksmile.database.dao;
import org.springframework.stereotype.Repository;
import pinksmile.database.domain.User;
import java.util.List;
@Repository // 這是mybatis操作的數據庫
public interface UserDao {
// 根據用戶 id 獲取用戶角色
String getUserRoleByID(Integer userId);
// 根據用戶名查詢用戶(單個)
User queryByName(String username);
}
4. 服務層類的實現
在service文件夾下創建UserService.java
文件,用來調用dao層中的接口。
package pinksmile.database.service;
import pinksmile.database.dao.UserDao;
import pinksmile.database.domain.User;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) { this.userDao = userDao; }
// 通過用戶ID得到用戶角色
public String getUserRoleByID(Integer userId) {return userDao.getUserRoleByID(userId);}
// 根據用戶名查詢用戶(單個)
public User queryByName(String username) {return userDao.queryByName(username);}
}
5. 控制層的實現
首先是controller層的Login.java
文件
實現的頁面跳轉功能:
- 跳轉到登錄頁面
- 驗證賬號和密碼
- 根據用戶角色跳轉不同頁面
其中,verification函數實現邏輯:
- 先校驗驗證碼是否爲空或者是否輸入錯誤
- shiro框架會自動將輸入的用戶名和密碼封裝成一個對象
- 用subject對象進行登錄,已經登錄信息的獲取
package pinksmile.database.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import pinksmile.database.domain.User;
import pinksmile.database.service.UserService;
@Controller
public class Login {
final UserService userService;
public Login(UserService userService) {
this.userService = userService;
}
/**
* 用於返回登錄頁面
* @return 返回登錄頁面模板
*/
@GetMapping("/login") // 登錄頁面網址
public String login() { return "login"; }
/**
* 用於驗證賬號和密碼是否正確
* @param model ..
* @return 登錄成功跳轉管理界面,失敗返回錯誤信息到登錄頁面
*/
@PostMapping("/login") // 失敗錯誤信息頁面網址
public String verification(String username, String password, String captcha, Model model) {
//校驗驗證碼
//session中的驗證碼
String sessionCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
if (null == captcha || !captcha.equalsIgnoreCase(sessionCaptcha)) {
model.addAttribute("msg","驗證碼錯誤!");
return "login";
}
// shiro框架將用戶名和密碼封裝爲對象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
//login認證通過後,便可拿到shiro保存的用戶對象
User user1 = (User) subject.getPrincipal();
subject.getSession().setAttribute("user", user1); // 保存用戶信息
return "redirect:/manage";
} catch(Exception e) {
if (e instanceof UnknownAccountException) {
model.addAttribute("msg", "用戶名錯誤!");
}
if (e instanceof IncorrectCredentialsException) {
model.addAttribute("msg", "密碼錯誤!");
}
//返回登錄頁面
return "login";
}
}
}
BackManage.java
文件
用於用戶登錄後根據角色進行界面跳轉
package pinksmile.database.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import pinksmile.database.annotation.Log;
import pinksmile.database.domain.User;
import pinksmile.database.service.UserService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Controller
public class BackManage {
final UserService userService;
public BackManage(UserService userService) {
this.userService = userService;
}
/**
* 後臺管理頁面數據準備接口
* @param request 用於獲取Session來判斷是否登錄
* @return 返回模板頁面
*/
@GetMapping("manage") // 管理頁面網址
public String manage(HttpServletRequest request){
HttpSession session=request.getSession(); // 獲取登錄信息
Object obj = session.getAttribute("user");
// 沒有登錄,返回登錄頁面
if(obj == null){ // 登錄信息爲 null,表示沒有登錄
return "login";
}
User loginUser = (User) obj; // 強制轉換成 User
Integer userId = loginUser.getId(); // 獲得登錄用戶的 id
String role = userService.getUserRoleByID(userId); // 通過登錄用戶的 id 得到用戶的角色
// 如果是用戶登錄,返回用戶界面
if (role.equals("user")){
return "usermanage";
}
// 如果是管理員登錄就返回管理頁面
return "adminmanage";
}
@GetMapping("welcome")
public String toWelcome() {return "welcome";}
@Log(value = "返回錯誤頁面")
@GetMapping("error")
public String toError(){return "error";}
}
6. 驗證碼的實現
在config文件夾下創建KaptchaConfig.java
文件:
package pinksmile.database.config;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.servlet.KaptchaServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class KaptchaConfig {
@Bean
public ServletRegistrationBean<KaptchaServlet> kaptchaServlet() {
ServletRegistrationBean<KaptchaServlet> registrationBean = new ServletRegistrationBean<>(new KaptchaServlet(), "/captcha/kaptcha.jpg");
registrationBean.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY, Constants.KAPTCHA_SESSION_KEY);
//寬度
registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH,"125");
//高度
registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT,"60");
//字體大小
registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE,"50");
//無邊框
registrationBean.addInitParameter(Constants.KAPTCHA_BORDER,"no");
//樣式引擎
registrationBean.addInitParameter(Constants.KAPTCHA_OBSCURIFICATOR_IMPL,"com.google.code.kaptcha.impl.ShadowGimpy");
//長度
registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
//字符間距
registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "4");
//文本樣式
registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋體,楷體,微軟雅黑");
//可以設置很多屬性,具體看com.google.code.kaptcha.Constants
// kaptcha.border 是否有邊框 默認爲true 我們可以自己設置yes,no
// kaptcha.border.color 邊框顏色 默認爲Color.BLACK
// kaptcha.border.thickness 邊框粗細度 默認爲1
// kaptcha.producer.impl 驗證碼生成器 默認爲DefaultKaptcha
// kaptcha.textproducer.impl 驗證碼文本生成器 默認爲DefaultTextCreator
// kaptcha.textproducer.char.string 驗證碼文本字符內容範圍 默認爲abcde2345678gfynmnpwx
// kaptcha.textproducer.char.length 驗證碼文本字符長度 默認爲5
// kaptcha.textproducer.font.names 驗證碼文本字體樣式 默認爲new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
// kaptcha.textproducer.font.size 驗證碼文本字符大小 默認爲40
// kaptcha.textproducer.font.color 驗證碼文本字符顏色 默認爲Color.BLACK
// kaptcha.textproducer.char.space 驗證碼文本字符間距 默認爲2
// kaptcha.noise.impl 驗證碼噪點生成對象 默認爲DefaultNoise
// kaptcha.noise.color 驗證碼噪點顏色 默認爲Color.BLACK
// kaptcha.obscurificator.impl 驗證碼樣式引擎 默認爲WaterRipple
// kaptcha.word.impl 驗證碼文本字符渲染 默認爲DefaultWordRenderer
// kaptcha.background.impl 驗證碼背景生成器 默認爲DefaultBackground
// kaptcha.background.clear.from 驗證碼背景顏色漸進 默認爲Color.LIGHT_GRAY
// kaptcha.background.clear.to 驗證碼背景顏色漸進 默認爲Color.WHITE
// kaptcha.image.width 驗證碼圖片寬度 默認爲200
// kaptcha.image.height 驗證碼圖片高度 默認爲50
return registrationBean;
}
}
7.login.html
前端頁面
爲了文章篇幅,這裏就不貼出管理員管理頁面adminmanage.html
和用戶管理頁面usermanage.html
頁面代碼。可以自己隨便創建兩個不同的頁面,來測試登錄跳轉功能。
<!doctype html>
<html class="x-admin-sm" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>企業庫存管理系統登錄</title>
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link rel="stylesheet" th:href="${urls.getForLookupPath('/css/font.css')}">
<link rel="stylesheet" th:href="${urls.getForLookupPath('/css/login.css')}">
<link rel="stylesheet" th:href="${urls.getForLookupPath('/css/xadmin.css')}">
<script type="text/javascript" th:src="${urls.getForLookupPath('/js/jquery.min.js')}"></script>
<script th:src="${urls.getForLookupPath('/lib/layui/layui.js')}" charset="utf-8"></script>
</head>
<body class="login-bg">
<div class="login layui-anim layui-anim-up">
<div class="message">企業庫存管理系統登錄</div>
<div id="darkbannerwrap"></div>
<form method="post" class="layui-form" th:action="@{/login}">
<input th:value="${username}" name="username" placeholder="請輸入用戶名" type="text" lay-verify="username" autocomplete="off" pattern="^[a-zA-Z0-9_-]{1,16}$" class="layui-input" >
<hr class="hr15">
<input th:value="${password}" name="password" lay-verify="password" placeholder="請輸入密碼" type="password" class="layui-input">
<hr class="hr15">
<div class="layui-form-item">
<div class="layui-input-inline" style="width: 150px;vertical-align:bottom;">
<input type="text" name="captcha" lay-verify="captcha" autocomplete="off" placeholder="請輸入驗證碼" class="layui-input">
</div>
<img th:src="@{../captcha/kaptcha.jpg}" title="看不清,點擊換一張" id="codeImage" onclick="chageCode()" style="width: 180px;height: 50px;vertical-align: middle;">
</div>
<p style="color: #f13517e0; text-align:center" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<hr class="hr15">
<input value="登錄" lay-submit lay-filter="login" style="width:100%;" type="submit">
<hr class="hr20" >
</form>
</div>
<script>
$(function () {
layui.use(['form', 'layer'], function(){
var form = layui.form;
//自定義驗證規則
form.verify({
username:function (value) {
if(value.length===0){
return '請輸入用戶名!';
}
},
password: function(value) {
if (value.length===0) {
return '請輸入密碼!';
}
},
captcha:function (value) {
if (value.length===0) {
return '請輸入驗證碼!';
}
}
});
});
});
function chageCode(){
document.getElementById("codeImage").src="../captcha/kaptcha.jpg?"+Math.random();
}
</script>
</body>
</html>