Shiro + Spring Boot 项目实战(二):数据库的创建和登录功能

GitHub: 代码连接.

https://github.com/17639622607/springshiro

前言

本项目中,实现权限控制一共用到了5张表,user(用户表)、role(角色表)、menu(资源表)。由于用户和角色之间、角色和资源之间存在多对多的关系,所以把三个表之间的关联信息抽离到两个表里。user_role(用户角色表)、role_menu(角色资源表)

用户表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户表',
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '密码',
  `salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '盐值',
  `enabled` int(10) DEFAULT 0 COMMENT '是否启用',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '角色表',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '名称',
  `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '描述',
  `role_sort` int(10) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
资源表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `menu_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称',
  `parent_id` int(11) DEFAULT 0 COMMENT '父菜单ID',
  `order_num` int(4) DEFAULT 0 COMMENT '显示顺序',
  `url` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '#' COMMENT '请求地址',
  `menu_type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
  `visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '#' COMMENT '菜单图标',
  `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
用户角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '用户角色表',
  `user_id` int(10) DEFAULT NULL COMMENT '用户id',
  `role_id` int(10) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `user_id`(`user_id`, `role_id`) USING BTREE COMMENT '联合唯一键'
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
角色资源表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '角色资源表',
  `role_id` int(10) DEFAULT NULL COMMENT '角色id',
  `menu_id` int(10) DEFAULT NULL COMMENT '列表id',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `role_id`(`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 190 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

先讲下user表,单纯的只对我们的密码加密往往是不够安全的,一般的话我们会拼接一个盐值。在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”

在这里插入图片描述
接下来我们来开发登录界面

login.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <style>
        body{
            height: 100%;
            background: url("../img/loginbg.jpg") no-repeat center fixed;
            -webkit-background-size: cover;
            -moz-background-size: cover;
            -o-background-size: cover;
            background-size: cover;
            color: rgba(255, 255, 255, .95);

        }
        form{
            width: 240px;
            height: 240px;
            padding: 30px;
            margin: 10% 0 0 63% ;
            background: rgba(255,255,255,.2);
            border: 1px solid rgba(255,255,255,.3);
            border-radius: 3px;
        }
        input{
            display: inline-block;
            width: 225px;
            height: 30px;
            border-radius: 3px;
            margin: 15px 0;
            outline-style: none ;
            border: none ;
            text-indent: 10px;
        }
        #btnSubmit{
            background-color: #1c84c6;
            border-color: #1c84c6;
            color: #FFFFFF;
            cursor:pointer;
        }

    </style>
    <link rel="shortcut icon" href="../static/favicon.ico" th:href="@{favicon.ico}"/>
</head>
<body>
    <form>
        登录
        <input type="text" placeholder="用户名" name="username" id="username">
        <input type="password" placeholder="密码" name="password" id="password">
        <input type="button" value="登录" id="btnSubmit">
    </form>
</body>
<script th:src="@{/layui/layui.js}"></script>
<script>
    layui.use('layer', function(){
        var $ = layui.jquery, layer = layui.layer;
        $("#btnSubmit").click(function (){
            $.ajax({
                type: "post",
                url: "/login",
                data: {
                    username: $("#username").val(),
                    password: $("#password").val(),
                },
                dataType: "json",
                success: function (res) {
                    if(res.code==0){
                        location.href =   '/index';
                    }else {
                        layer.msg(res.msg);
                    }
                }
            });
        })
    })
</script>
</html>

在controller层加上映射

@GetMapping("/login")
    public String login(){
        return "login";
    }

此时我们就有登录界面了,但是我们还没用户信息,下面我们来添加一个用户,

后端用户相关的开发
(一)添加用户实体类SysUser,类名用数据库表名,将首字母和下划线下一个字母大写
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.*;
@Data
@ToString
@Entity
@Table
@JsonIgnoreProperties({"handler","hibernateLazyInitializer"})
public class SysUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    private String username;
    private String password;
    private String salt;
    private Integer enabled;
}

用JPA的话实体类简单的写法大概就这么写,是不是看着很复杂,不用每个注释都非常了解,大概知道干嘛的就行。这里的复杂为后面不用写sql语句稍微铺垫了一下

(二)UserDao层
import com.ysc.springshiro.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserDao extends JpaRepository<SysUser, Integer> {
    //按照名字查找user
    public SysUser findSysUserByUsername(String username);
}

继承JpaRepository类的话,里面已经包含了基本的增删改查sql语句,如果在不连表的情况下,我们可以直接写接口,这里大概讲一下吧
比如我们想按姓名查找SysUser对象的话,我们的方法名就叫可以写成findSysUserByUsername,参数写成字符串型,返回值是SysUser
如果我们想按Id查找的话,方法名则是findSysUserById,参数是整形,返回值是SysUser

(三)UserService层
import com.ysc.springshiro.dao.UserDao;
import com.ysc.springshiro.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    //添加或者编辑user
    public void insertUser(SysUser user){
        //save() 方法的作用是,当主键存在时更新数据,当主键不存在时插入数据。
        userDao.save(user);
    }
    //查询集合
    public List<SysUser> findAllUser(){
        return userDao.findAll();
    }
    //按照名字查找用户
    public SysUser findUserByUsername(String username){
        return userDao.findSysUserByUsername(username);
    }
}

这里的UserService调用UserDao里面的方法,刚才UserDao里怎么写我们想要的方法已经大概说了下,接下来我们说下UserDao里自带的一些方法
查询集合:findAllUser
删除:delete
添加和修改是一个方法:save

方法我们是写好了,下面我们的目的是给我们这个项目添加一个用户,我们用户表中有个salt字段不知道大家玩了没,现在他要排上用场了。首先呢,由于salt这个字段是为了加密用的,这里需要引入我们的shiro框架,用里面的一些加密方法

(四)引入shiro
1. pom.xml中新加
        <!--Shiro  -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.2</version>
        </dependency>
        <!-- shiro ehcache -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.2</version>
        </dependency>
        <!-- pagehelper 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
2.新建ShiroRealm和ShiroConfiguration

ShiroRealm

import com.ysc.springshiro.entity.SysUser;
import com.ysc.springshiro.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    /*** 授权*/
     @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //能进入到这里,表示账号已经通过验证了
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();

        return s;
    }
    /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取账号密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String username = token.getPrincipal().toString();
        SysUser user=userService.findUserByUsername(username);
        //获取数据库中的密码
        String passwordInDB = user.getPassword();
        //认证信息里存放账号密码,getName()是当前Reaml的继承方法,通常返回当前类名 :shirorealm
        String salt = user.getSalt();
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,passwordInDB, ByteSource.Util.bytes(salt),getName());
        return authenticationInfo;
    }
}

ShiroConfiguration

@Configuration
public class ShiroConfiguration {
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    //配置核心安全事务管理器
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getBootRealm());
        return securityManager;
    }
    //配置自定义的权限登录器
    @Bean
    public ShiroRealm getBootRealm() {
        ShiroRealm wjRealm = new ShiroRealm();
        wjRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return wjRealm;
    }
    //自定义密码加密规则
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }
    /**
     * 开启aop注解支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     *  开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager  manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登录的url和登录成功的url以及验证失败的url
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login/*", "anon");
        filterChainDefinitionMap.put("/static/*", "anon");
        //所有的路径都拦截,被UserFilter拦截,这里会判断用户有没有登陆
        filterChainDefinitionMap.put("/*", "user");

        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }
}
3.调用接口添加一个用户
@Controller
public class TestController {
    @Autowired
    UserService userService;

    @GetMapping("/register")
    @ResponseBody
    public String register(String username, String password){
        SysUser user=new SysUser();
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();// 生成盐,默认长度 16 位
        int times = 2;// 设置 hash 算法迭代次数
        user.setUsername(username);
        user.setSalt(salt);
        String encodedPassword = new SimpleHash("md5", password, salt, times).toString();// 得到 hash 后的密码
        user.setPassword(encodedPassword);
        try{
            userService.insertUser(user);
            return "成功";
        }catch (Exception e){
            return "失败";
        }
    }
}

我们在浏览器地址手动调这个接口,如下操作

在这里插入图片描述这时我们看数据库,数据已经添加上了

在这里插入图片描述

5.写登录逻辑

在写逻辑之前,小伙伴们先把一些公共的方法引进来,可以先把我git上的项目拉下来,这些公共的方法也都是以前封装的,这里就不做一一介绍了,都不是很难理解

在这里插入图片描述

登录逻辑

@Controller
public class LoginController extends BaseController {
    @Autowired
    UserService userService;
    
    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/login")
    @ResponseBody
    public AjaxResult ajaxLogin(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return success();
        }catch (AuthenticationException e){
            return error("用户或密码错误");
        }
    }
    @GetMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }
}

这里有的小伙伴可能就要问了,不是要和数据库的用户做对比吗,怎么没调数据库呢。这里我们调用subject.login(token)已经调用了ShiroRealm中我们自定义的登录方法doGetAuthenticationInfo,这样我们的登录功能就写好了

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值