前言

由于将用户的账号密码明文存储在数据库中具有不安全性,比价容易泄露,用户觉得不靠谱等,所以我们需要将用户的密码加密存储在数据库中。

  • Hash 加密

hash算法(散列算法、摘要算法)即把任意长度的输入映射为固定长度的输出,比如密码Evanniubi变成五位的输出kchpl,这种算法不可逆,且存在信息损失,虽然随着时间推移,出现了字典法、彩虹表法等优化手段,但本质上想要破解还是靠穷举与瞎蒙,而且对于复杂密码来说,破解成本极高。

  • 加盐加密
    加盐,是提高hash算法的安全性的一个常用手段。其意义在于利用你的账户生成‘盐’,通过这个盐生成密码并用hash加密。
    为什么更安全?
  • 黑客就算拿到数据库中的密码也无法使用。
  • 即使用户使用相同的密码,由于各个用户的盐不同,所以也无法破解用户的密码。

代码部分

我们首先要在user表中添加salt字段,存储每个用户生成的“盐”。

在这里插入图片描述
在SpringBoot的pojo文件夹下的User同样增加salt字段以及get,set方法。

  • 引入service 层
    大型的项目中,一般不由控制层直接调用mapper层,所以需要提供一个service层,让它担当controller层与mapper层的媒介,接收controller层的请求,处理mapper层的数据。所以我们在项目文件下创建service文件夹,创建UserService.java文件,注解@Service,代表Service 层,并在其中对UserMapper进行@Autowired。

  • 用户注册
    前面说过,每个用户生成唯一的盐,所以我们的保证用户是唯一的,这样就需要我们用户注册的时候验证数据库中是否存在该账号。

UserMapper.java

    User isExist(String username);

UserMapper.xml

    <select id="isExist" resultType="com.jkd.springbootproject.pojo.User">
        select * from user where username = #{username}
    </select>
    // 用户注册
    @PostMapping("register")
    public String register(@RequestBody User user){
        System.out.println(user.getUsername());
        if (userService.isExist(user.getUsername())){
            return "用户名已存在";
        }
        // 根据用户名生成盐
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        // 根据生成的盐和用户输入的密码迭代2次生成新的密码
        String newPasswoed = new SimpleHash("md5",user.getPassword(),salt,2).toString();
        // 存储用户信息,包括salt与hash后的密码
        user.setSalt(salt);
        user.setPassword(newPasswoed);
        userService.add(user);
        return "注册成功";
    }
  • Shiro的Jar包引入
        <!--shiro的jar包引入-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.1</version>
        </dependency>
    </dependencies>
  • 验证
    当输入已存在用户名:
    在这里插入图片描述
    当输入不同用户名:
    在这里插入图片描述
    可以看到,数据库中存储了用户的密码和盐值都是加密方式的,大大提高了用户信息的安全性。
    在这里插入图片描述
    以后用户登录的过程:
    • 前端明文提交账号密码
    • 后端根据用户名取出数据库中对应的盐值
    • 根据取出的盐值进行同样hash加密两次
    • 比如加密后的结果与数据库中的是否一致

Shiro框架的引入

Shiro提供了一系列安全的加密验证,用户授权,用户会话等方法,我们的一切验证授权手段都是在调用Shiro的API,与之相类似的有Spring Security。

  1. List item
    Shiro的三个概念
    Subject: 当前与Shiro交互的东西,比如前端发送登录请求到后端,那么Subject就是提交的用户类。
    SecurityManager: 管理全部的Subject。
    Realms: 将用户信息进行一系列处理 传递给SecurityManage。
  2. Realm的配置
    我们说过,Realm是输入的用户信息与SecurityManage之间的桥梁,那么我们需要在Realm中做什么呢?比如对用户输入的用户名变成盐,对输入的密码进行加密等,操作完成后提交给SecurityManage。
    我们创建Realm文件夹下创建UserRealm.java文件,在其中对用户信息进行处理。
/**
 * @projectName: springbootproject
 * @package: com.jkd.springbootproject.Realm
 * @className: UserRealm
 * @author: JKD
 * @description: 用户信息处理
 *               所有自定义的Realm必须继承AuthorizingRealm
 * @version: 1.0
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;



    /**
     * 对登录用户的授权,现在我们先不写
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 此部分处理登录
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // token由控制层传入,获取token中存储的用户名
        String username = authenticationToken.getPrincipal().toString();
        // 根据用户名去数据库查询是否存在该用户
        User user = userService.getUserByName(username);
        if (user == null){
            // 用户不存在抛出不存在异常交给控制层处理
            throw new UnknownAccountException();
        }
        String password = user.getPassword();
        String salt = user.getSalt();
        // 再次把salt转成byte将整个认证交给SecurityManage
        return new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), getName());
    }
}

  1. Shiro配置
    在Config文件夹下创建ShiroConfig.java文件进行Shiro配置
package com.jkd.springbootproject.config;

import com.jkd.springbootproject.Realm.UserRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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 org.apache.shiro.mgt.SecurityManager;

/**
 * @projectName: springbootproject
 * @package: com.jkd.springbootproject.config
 * @className: ShiroConfig
 * @author: JKD
 * @description: Shiro配置
 */

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

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getUserRealm());
        return securityManager;
    }

    @Bean
    public UserRealm getUserRealm() {
        UserRealm realm = new UserRealm();
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    @Bean
    public CredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}
  1. 登录控制层重写
    @PostMapping("/login")
    public String Login(@RequestBody User user){
        Subject subject = SecurityUtils.getSubject();
        // Shiro帮我们写好了usernamePasswordToken,只要提交账号密码,后面的交给Realm,Realm交给SecurityManage
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
        // 只要一行代码就能实现登录
        try {
            subject.login(token);
            return "登录成功";
        }catch (UnknownAccountException e){ // 处理我们在Realm中抛出的异常
            return "用户不存在";
        } catch (AuthenticationException e) { // 当Shiro发现用户的账号密码不匹配时自动抛出这个异常
            return "账号或密码错误";
        }
    }
  1. 登录验证一下
    在这里插入图片描述
    发现登录成功,说明我们的Shiro配置完成,已实现基本的登录。

下章预告

  • 对登录的用户给予授权凭证
  • 登出功能

结语

参考文章:https://blog.csdn.net/qq_42241722/article/details/104269094


版权声明:本文为WuKongHeBaJie原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/WuKongHeBaJie/article/details/105121779