当前位置: 首页 > news >正文

从零搭建基于SpringBoot的秒杀系统(五):基于Shiro的人员登陆认证

前面实现了抢购的功能,但是人员目前是写死的,关于登陆有许多实现方式,这里采用Shiro来实现人员的登陆认证。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等,在这里我们用shiro进行登陆认证。

1.登陆注册页面编写

两个页面的内容很简单,一个用户名输入框,一个密码输入框,一个登陆或者注册按钮。

首先是登陆页面:

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<meta name="referrer" content="no-referrer" />
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>登陆界面</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <form class="form-signin" action="/login" method="post">
        <h2 class="form-signin-heading">Please sign in</h2>
        <label for="username" class="sr-only">Username</label>
        <input type="username" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
        <span><a href="/register" class="register">重新注册一个用户</a></span>
        <h2 th:text="${errormsg}"></h2>
    </form>
</div> <!-- /container -->
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

注册页面:

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<meta name="referrer" content="no-referrer" />
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>注册界面</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <form class="form-signin" action="/register" method="post">
        <h2 class="form-signin-heading">Please Register</h2>
        <label for="username" class="sr-only">Username</label>
        <input type="username" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
        <h2 th:text="${errormsg}"></h2>
    </form>
</div> <!-- /container -->
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

2.shiro文件配置

shiro中有个比较重要的内容叫做Realm(域),Realm是应用与安全数据之间的连接器,当用于执行登陆认证时,shiro会从Realm中查找用户的信息及权限,你也可以把Realm看成一个安全的数据源,用户数据都是从这里获取。

在Service文件下新建一个CustomRealm ,CustomRealm 需要继承AuthorizingRealm ,并实现其中的doGetAuthorizationInfo方法和doGetAuthenticationInfo方法,认证登录我们用到doGetAuthenticationInfo方法。具体的代码逻辑放在注释里。

package com.sdxb.secondkill.service;

import com.google.common.base.Objects;
import com.sdxb.secondkill.entity.User;
import com.sdxb.secondkill.mapper.UserMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

public class CustomRealm extends AuthorizingRealm {
    @Autowired
    public UserMapper userMapper;
    /**
     * 认证-授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    /**
     * 认证-登陆
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取UsernamePasswordToken对象,从这个对象中可以获取用户名和密码
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        String password = String.valueOf(token.getPassword());
        System.out.println(username+password);
        //根据用户名查找用户信息
        User user = userMapper.SelectByName(username);
        //如果用户为空,向上抛出UnknownAccountException异常
        if (user==null){
            throw new UnknownAccountException("用户名不存在");
        }
        //如果isActive不等于1,向上抛出DisabledAccountException异常
        if (!Objects.equal(1,user.getIsActive().intValue())){
            throw new DisabledAccountException("当前用户已被禁用");
        }
        //如果密码错误,抛出IncorrectCredentialsException异常
        if(!user.getPassword().equals(password)){
            throw new IncorrectCredentialsException("用户密码错误");
        }
        //创建SimpleAuthenticationInfo对象进行校验
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user.getId(),password,getName());
        //将key和对应的value装入shiro的session中,最终交给httpSession进行管理
        setSession("uid",user.getId());
        return info;
    }

    /**
     * 将key和对应的value装入shiro的session中,最终交给httpSession进行管理
     * @param key
     * @param value
     */
    private void setSession(String key, Object value) {
        Session session = SecurityUtils.getSubject().getSession();
        if (session!=null){
            session.setAttribute(key,value);
            session.setTimeout(300000L);
        }
    }
}

简单来讲,就是通过Realm获取用户名和密码进行校验,并装入shiro的session中,接下来就可以写Shiro的通用配置了

package com.sdxb.secondkill.config;
import com.sdxb.secondkill.service.CustomRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    //身份认证的realm
    @Bean
    public CustomRealm customRealm(){
        return new CustomRealm();
    }
    //安全管理器,并设置realm
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(customRealm());
        return manager;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        //创建一个ShiroFilter工厂
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(securityManager());
        //设置登陆url
        bean.setLoginUrl("/login");
        //设置为认证的url
        bean.setUnauthorizedUrl("/unauth");
        //创建过滤器,在这里可以自定义需要过滤的url
        Map<String,String> filterChainDefinitionMap = new HashMap<String, String>();
        //anon: 无需认证即可访问
        //authc: 需要认证才可访问
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/register","anon");
        filterChainDefinitionMap.put("/item/*","authc");
        filterChainDefinitionMap.put("/kill/execute/*","authc");
        filterChainDefinitionMap.put("/**","authc");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }
}

Shiro的通用配置中主要配置了realm以及过滤器,设置相关的过滤条件,anon:表示无需认证即可访问,authc: 表示需要认证才可访问。配置当路径为/login或/register,不需要认证,为其他路径时需要认证。setloginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面,setUnauthorizedUrl:没有权限默认跳转的页面,登录的用户访问了没有被授权的资源自动跳转到的页面。

3.后端登陆注册业务逻辑

接下来编写业务逻辑,首先在controller下新建UserController,编写登陆和注册的controller

import com.sdxb.secondkill.entity.User;
import com.sdxb.secondkill.mapper.UserMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping(value = {"/login","unauth"},method = RequestMethod.GET)
    public String toLogin(){
        return "login";
    }

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public String login(@RequestParam("username")String username, @RequestParam("password")String password, Model model){
        System.out.println(username+password);
        //用于记录错误信息
        String msg="";
        try {
            //如果未认证,生成一个UsernamePasswordToken对象,通过login登陆
            if (!SecurityUtils.getSubject().isAuthenticated()){
                UsernamePasswordToken token=new UsernamePasswordToken(username,password);
                SecurityUtils.getSubject().login(token);
            }
        }catch (UnknownAccountException e){
            msg=e.getMessage();
        }catch (DisabledAccountException e){
            msg=e.getMessage();
        }catch (IncorrectCredentialsException e){
            msg=e.getMessage();
        }catch (Exception e){
            msg="用户登陆异常";
            e.printStackTrace();
        }
        //如果msg为空表示没有错误,跳转到首页
        if (StringUtils.isBlank(msg)){
            return "redirect:/item";
        }else {
            model.addAttribute("errormsg",msg);
            return "login";
        }
    }

    @RequestMapping(value = "/register",method = RequestMethod.GET)
    public String toRegister(){
        return "register";
    }

    @RequestMapping(value = "/register",method = RequestMethod.POST)
    public String checkRegister(@RequestParam("username")String username,@RequestParam("password")String password,Model model){
        String msg="";
        User user = userMapper.SelectByName(username);
        if (user!=null){
            msg="当前用户已存在";
            model.addAttribute("errormsg",msg);
            return "register";
        }
        else {
            int res=userMapper.insertuser(username,password);
            if (res>0){
                return "login";
            }else {
                msg="用户创建失败,请联系管理员处理";
                model.addAttribute("errormsg",msg);
                return "register";
            }
        }
    }
    @RequestMapping(value = "/logout",method = RequestMethod.GET)
    public String Logout(){
        SecurityUtils.getSubject().logout();
        return "login";
    }
}

对数据库的操作放在mapper中,在mapper文件下新建Usermapper

@Mapper
public interface UserMapper {
    @Select("select * from user where user_name=#{username}")
    User SelectByName(@Param("username")String username);

    @Insert("insert into user(user_name,password) values(#{username},#{password})")
    int insertuser(@Param("username") String username, @Param("password") String password);
}

当输入的url不是/login或/register时,shiro会进行校验,如果发现未通过人员认证,则会跳转到登陆页面进行登陆认证。输入http://localhost:8080/logout则退出登陆。

4.修改抢购业务

在前一篇博客中,我们把KillController中的userid写死成10,现在有了人员登陆后需要修改一下之前的代码:

//修改前
//Integer userid=10;
//添加登陆功能后使用这段代码
Object uid=httpSession.getAttribute("uid");
if (uid==null){
    return new BaseResponse(StatusCode.UserNotLog);
}
Integer userid= (Integer) uid;

完成这些步骤后登陆注册功能就算彻底完成了。

5.效果展示

输入网址:http://localhost:8080/item,自动跳转到登陆页面

如果输入错误用户名,显示用户名不存在

输入错误密码,显示密码错误

输入正确账号密码后,进入首页。点击左下角的重新注册一个用户,进入用户注册页面:

点击注册后即可注册。

到目前为止的代码均放在https://github.com/OliverLiy/SecondKill/tree/version4.0

我搭建了一个微信公众号《Java鱼仔》,分享大量java知识点与学习经历,如果你对本项目有任何疑问,欢迎在公众号中联系我,我会尽自己所能为大家解答。

相关文章:

  • 从零搭建基于SpringBoot的秒杀系统(六):使用RabbitMQ让订单指定时间后失效
  • 从零搭建基于SpringBoot的秒杀系统(七):高并发导致超卖问题分析处理
  • 从零搭建基于SpringBoot的秒杀系统(八):通过分布式锁解决多线程导致的问题
  • 读《世界是数字的》有感
  • 面试官问我:什么是静态代理?什么是动态代理?注解、反射你会吗?
  • redis入门到精通系列(十):springboot集成redis及redis工具类的编写
  • css3延时动画
  • redis入门到精通系列(十一):redis的缓存穿透、缓存击穿以及缓存雪崩详解
  • 子数组最大值设计02
  • redis入门到精通系列(十二):看完这一篇文章别再说不懂布隆过滤器
  • 如何用SpringBoot(2.3.3版本)快速搭建一个项目?文末有小彩蛋
  • Linux上find命令详解
  • 一步步带你看SpringBoot(2.3.3版本)自动装配原理
  • CCF系列之I’m stuck!(201312-5)
  • SpringBoot配置文件及自动配置原理详解,这应该是SpringBoot最大的优势了吧
  • 230. Kth Smallest Element in a BST
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • Computed property XXX was assigned to but it has no setter
  • Material Design
  • mysql常用命令汇总
  • React的组件模式
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 多线程事务回滚
  • 给github项目添加CI badge
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 技术发展面试
  • 来,膜拜下android roadmap,强大的执行力
  • 前端_面试
  • 如何使用 JavaScript 解析 URL
  • 写代码的正确姿势
  • 一些css基础学习笔记
  • 栈实现走出迷宫(C++)
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • Spring Batch JSON 支持
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (02)vite环境变量配置
  • (11)MATLAB PCA+SVM 人脸识别
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (算法设计与分析)第一章算法概述-习题
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • @Async注解的坑,小心
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @ModelAttribute使用详解