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>

效果圖

在這裏插入圖片描述

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