SpringBoot+Shiro+kaptcha验证码实现用户登录和根据角色跳转页面

这篇文章主要实现用户登录功能:用户输入自己的账号、密码以及验证码后,会进行身份验证,认证通过的系统会自动判断该用户的身份,跳转到不同的管理界面。

要实现的功能

  • 用户登录判断
  • 根据用户角色自动跳转不同的页面
  • 加入验证码进行验证

开发前的准备

需要查看另一篇文章

由于想减少文章的篇幅,这部分使用的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映射的类型不要写错,后面有注意的地方。
  • 其他的内容就是数据库操作语句,里面的iddao层的接口名字一样;返回值的类型根据需要选择。
<?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函数实现逻辑:

  1. 先校验验证码是否为空或者是否输入错误
  2. shiro框架会自动将输入的用户名和密码封装成一个对象
  3. 用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>

效果图

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章