天天看點

Springboot前後端分離-Shiro+Md5加密與認證登入------學習記錄Springboot前後端分離-整合Shiro-Md5加密與認證登入——學習記錄

Springboot前后端分离-整合Shiro-Md5加密与认证登录——学习记录

参考借鉴这里:https://blog.csdn.net/bbxylqf126com/article/details/110501155
				https://blog.csdn.net/weixin_42375707/article/details/111145907
				https://blog.csdn.net/qq_34845394/article/details/94858168
           

本人,迷茫大聪明-张童学。

目前在学shiro,之前学过springsecurity。网上说前者比后者容易(我咋觉得是不是,,,难道是我,,,)

文章目录

  • Springboot前后端分离-整合Shiro-Md5加密与认证登录——学习记录
    • 依赖
    • 直接上代码(我认为关键)
    • 接下来是“四兄弟”
    • 写在后面

依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- SpringBootText注解依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Junit依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--        <dependency>-->
        <!--            <groupId>org.slf4j</groupId>-->
        <!--            <artifactId>slf4j-log4j12</artifactId>-->
        <!--            <version>1.7.21</version>-->
        <!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>org.slf4j</groupId>-->
        <!--            <artifactId>jcl-over-slf4j</artifactId>-->
        <!--            <version>1.7.21</version>-->
        <!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>commons-logging</groupId>-->
        <!--            <artifactId>commons-logging</artifactId>-->
        <!--            <version>1.1.3</version>-->
        <!--        </dependency>-->
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>
           

直接上代码(我认为关键)

shiro核心配置

package com.ztxue.mybatis_plus.shiro;

import com.ztxue.mybatis_plus.utils.ShiroConstant;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
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.LinkedHashMap;
import java.util.Map;

/**
 * Shiro的核心配置类,用来整合shiro框架
 */
@Configuration
public class ShiroConfiguration {
    //1.引入自定义realm-CustomerRealm()
    @Bean
    public Realm getRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        // 设置密码匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式
        credentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ALGORITHM_NAME.MD5);
        // 设置散列次数
        credentialsMatcher.setHashIterations(ShiroConstant.HASH_ITERATORS);
        shiroRealm.setCredentialsMatcher(credentialsMatcher);
        return shiroRealm;
    }
    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm)  {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联realm
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }
    //3.创建过滤工厂-负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //过滤器链映射
        Map<String, String> filter = new LinkedHashMap<>();
        /*
         * 常用过滤器如下
         * anon:无需认证访问
         * authc:必须认证了才能访问
         * user:记住我开启才可以访问
         * perms:拥有对某个资源的权限才能访问
         * */
        filter.put("/user/login","anon");
        filter.put("/user/regis","anon");
        // 配置不会被拦截的链接 顺序判断,必须配置到每个静态目录
        filter.put("/swagger-ui.html/**", "anon");
        filter.put("/webjars/**","anon");
        // 所有url拦截
//        filter.put("/**", "authc");
        // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面
        filter.put("/user/logout", "logout");
        // 修改shiro默认登录地址,登录成功之后返回用户基本信息及token给前端
        bean.setLoginUrl("/user/login");
        // 设置成功之后要跳转的链接
        bean.setSuccessUrl("/user/regis");
        // 拦截未授权路径
        bean.setUnauthorizedUrl("/user/unauthorized");
        //过滤器链传值
        bean.setFilterChainDefinitionMap(filter);
        return bean;
    }
}
           

自定义的realm

package com.ztxue.mybatis_plus.shiro;

import com.ztxue.mybatis_plus.fv_sys.service.user.FvUserService;
import org.apache.commons.lang3.ObjectUtils;
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;

import java.util.List;

/**
 * 自定义realm--放弃使用.ini文件,使用数据库查询
 */
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    FvUserService userService;
    @Autowired
    FvRoleService roleService;
    @Autowired
    FvPermissionService permissionService;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    }
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户的输入的账号
        String userName = (String) token.getPrincipal();
        System.out.println("userName----------------------------->>>" + userName);
        //通过username从数据库中查找 User对象.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        FvUser user = userService.findByName(userName);
        System.out.println("user.getUPassword()----------------------------->>>" + user.getUPassword());
        System.out.println("userName----------------------------->>>" + userName);

        if (!ObjectUtils.isEmpty(user)) {
            return new SimpleAuthenticationInfo(
                    // 也可以写用户名
                    user,
                    // 传入的是从数据库中获取到的password,然后再与token中的password进行对比匹配
                    user.getUPassword(),
                    // salt–用于加密密码对比。若不需要,则可以设置为空 “ ”
                    ByteSource.Util.bytes(user.getUSalt()),
                    // 当前realm的名字
                    getName()
            );
        }
        return null;
    }
}
           

注意这个地方

new SimpleAuthenticationInfo(
                    // 也可以写用户名
                    user,
                    // 传入的是从数据库中获取到的password,然后再与token中的password进行对比匹配
                    user.getUPassword(),
                    // salt–用于加密密码对比。若不需要,则可以设置为空 “ ”
                    ByteSource.Util.bytes(user.getUSalt()),
                    // 当前realm的名字
                    getName()
            )
           

SimpleAuthenticationInfo里面三个或四个参数,第三个–

ByteSource.Util.bytes(user.getUSalt()),

获取的盐值

随机盐生成工具类(可收藏学习)

package com.ztxue.mybatis_plus.utils;

import java.util.Random;

/**
 * 用户随机盐生成工具类
 */
public class SaltUtil {
    /**
     * 生成salt的静态方法
     * @param n
     * @return
     */
    public static String getSalt(int n){
        char[] chars = "A[email protected]#$%^&*()".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
}
           

MD5加密说明类(定义成常量而已,我感觉duck不必,可能是我水平不够)

package com.ztxue.mybatis_plus.utils;

public class ShiroConstant {
    /** 随机盐的位数 **/
    public static final int SALT_LENGTH = 8;
    /** hash的散列次数 **/
    public static final int HASH_ITERATORS = 1024;
    /** 加密方式 **/
    public interface HASH_ALGORITHM_NAME {
        String MD5 = "MD5";
    }
}
           

接下来是“四兄弟”

controller关键代码

package com.ztxue.mybatis_plus.fv_sys.controller.user;
import com.ztxue.mybatis_plus.config.exception.LoginException;
import com.ztxue.mybatis_plus.fv_sys.entity.user.FvUser;
import com.ztxue.mybatis_plus.fv_sys.mapper.user.UserMapper;
import com.ztxue.mybatis_plus.fv_sys.service.user.FvUserService;
import com.ztxue.mybatis_plus.result.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author 张童学
 * @since 2021-07-17
 */
@RestController
@RequestMapping("/user")
@Api(description = "用户页")
public class FvUserController {

    @Autowired
    FvUserService fvUserService;
    @Autowired
    UserMapper userMapper;


    @ApiOperation("注册")
    @PostMapping("/regis")
    public AjaxResult register(FvUser user) {
        try {
            fvUserService.register(user);
            return AjaxResult.success("注册成功!",user);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("注册失败!大侠请从头再来!");
        }
    }

    @ApiOperation("登录")
    @RequestMapping("/login")
    public AjaxResult login(String userName, String password) {

        // 获取Subject实例对象,用户实例
        Subject currentUser = SecurityUtils.getSubject();
        // 将用户名和密码封装到UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);

        System.out.println("token============>" + token);
        try {
            // 传到 MyShiroRealm 类中的方法进行认证
            currentUser.login(token);
            return AjaxResult.success(token);
        } catch (UnknownAccountException e) {
            throw new LoginException("账号不存在!", e);
        } catch (IncorrectCredentialsException e) {
            throw new LoginException("密码不正确!", e);
        } catch (AuthenticationException e) {
            throw new LoginException("用户验证失败!", e);
        }
        // 登录成功返回用户信息
    }
}
           

这里用上了封装的结果集

package com.ztxue.mybatis_plus.result;


import com.ztxue.mybatis_plus.utils.HttpStatus;
import com.ztxue.mybatis_plus.utils.StringUtils;

import java.util.HashMap;

/**
 * 操作消息提醒
 * */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, null);
    }
}
           

serviceImpl 部分

@Autowired
    UserMapper userMapper;


    @Override
    public FvUser findByName(String name) {
        return userMapper.findByName(name);
    }
    // 注册
    @Override
    public void register(FvUser user) {
        // 生成随机盐
        String salt = SaltUtil.getSalt(ShiroConstant.SALT_LENGTH);
        // 保存随机盐
        user.setUSalt(salt);
        // 生成密码
        Md5Hash password = new Md5Hash(user.getUPassword(), salt, ShiroConstant.HASH_ITERATORS);
        // 保存密码
        user.setUPassword(password.toHex());
        userMapper.insert(user);
        System.out.println("生成的盐------------------>"+salt);
        System.out.println("MD5加密的密码------------------>"+password);
    }
           

serivce 部分

FvUser findByName(String name);
    // 注册
    void register(FvUser user);
           

mapper

package com.ztxue.mybatis_plus.fv_sys.mapper.user;

import com.ztxue.mybatis_plus.fv_sys.entity.user.FvUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author 张童学
 * @since 2021-07-17
 */
@Mapper
public interface UserMapper extends BaseMapper<FvUser> {

    @Select("select * from fv_user where u_name = #{uName}")
    FvUser findByName(String name);
    @Insert("INSERT INTO fv_user ( u_name, u_password, is_deleted, gmt_create, gmt_modified, u_salt ) VALUES (#{uName},#{uPassword},#{isDeleted},#{gmtCreate},#{gmtModified},#{uSalt})")
    int add(FvUser user);
}

           

其实mapper 我用了mybatis_plus ,继承了它的基本mapper,很多方法可以不用写,但我还不太熟。

然后这是我的实体类和数据库

package com.ztxue.mybatis_plus.fv_sys.entity.user;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

import java.io.Serializable;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 *
 * </p>
 *
 * @author 张童学
 * @since 2021-07-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class FvUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 用户id
     */
    @TableId(value = "u_id", type = IdType.AUTO)
    private Integer uId;
    /**
     * 用户名
     */
    private String uName;
    /**
     * 用户手机
     */
    private String uPhone;
    /**
     * 邮箱
     */
    public String uEmail;
    /**
     * 密码
     */
    public String uPassword;
    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer isDeleted;
    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;
    /**
     * 修改时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
    /**
     * 密码盐. 重新对盐重新进行了定义,用户名+salt,这样就不容易被破解,可以采用多种方式定义加盐
     */
    @TableField(value = "u_salt")
    private String uSalt;
}

           
CREATE TABLE `fv_user` (
  `u_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `u_name` varchar(255) DEFAULT NULL COMMENT '用户名',
  `u_phone` varchar(255) DEFAULT NULL COMMENT '用户手机',
  `u_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱',
  `u_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',
  `u_salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '盐',
  `u_state` int NOT NULL DEFAULT '1' COMMENT '状态',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
           

忽略部分前缀和部分无用字段

写在后面

博主目前在学shiro,因为在暑假实习,然后实习企业用的是shiro不是springSecurity,而且企业前后端分离,我刚好对前后端分离不熟悉,就趁着一块学了,但是碰上许多困难。

前后端分离需要写API,测试API我用swagger(现学现用就很nice)框架和APIPost这个软件(Postman我实在用不惯)。

shiro目前学基本的认证+MD5加密就搞得我头昏脑涨,因为博主本人“大聪明”,还总是好高骛远。

MD5加密逻辑不复杂,就“用户注册”时加密存入数据库,“用户认证”时从数据库取出来的加密密码,shiro能解析,不就酱紫?

后面也会有shiro的学习记录,这是vx:handsomeztx。希望有小伙伴一块学习交流、大佬批评指教。