这篇文章主要实现用户登录功能:用户输入自己的账号、密码以及验证码后,会进行身份验证,认证通过的系统会自动判断该用户的身份,跳转到不同的管理界面。
要实现的功能
- 用户登录判断
- 根据用户角色自动跳转不同的页面
- 加入验证码进行验证
开发前的准备
需要查看另一篇文章
由于想减少文章的篇幅,这部分使用的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>