天天看点

【Springboot+Vue】做一个权限管理后台(五):引入shiro

文章目录

    • 前言
    • 代码部分
    • Shiro框架的引入
    • 下章预告
    • 结语

前言

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

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

    加盐,是提高 hash 算法的安全性的一个常用手段。其意义在于利用你的账户生成“盐”,通过这个盐生成密码并用hash加密。

    为什么更安全?

    • 黑客就算拿到数据库中的密码也无法使用。
    • 即使用户使用相同的密码,由于各个用户的盐不同,所以也无法破解用户的密码。

代码部分

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

【Springboot+Vue】做一个权限管理后台(五):引入shiro

在SpringBoot的Pojo文件夹下的User同样增加Salt字段以及get,set方法。

  • 引入Service层

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

    @Service
    public class UserService {
        
        @Autowired
        private UserMapper userMapper;
        
        public boolean checkUser(User user){
            User u = userMapper.selectUser(user);
            if(u == null){
                return false;//如果账号密码输入不正确,返回false
            }
            return true;
        }
        
        public boolean isExist(String username){
            User user = userMapper.isExist(username);
            if(user != null){
                return true;//从数据库可以查找出用户 
            }
            return false;
        }
    }
               
  • 用户注册

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

    <select id="isExist" resultType="com.demo.demo.Pojo.User">
       select * from user where username = #{username}
    </select>
               
    @PostMapping("/register")
    public String register(@RequestBody User user){
        if(userService.isExist(user.getUsername())){
            return "用户名已存在";
        }
        // 根据用户名生成盐
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        // 根据生成的盐和用户输入的密码迭代2次生成新的密码
        String newPassword = new SimpleHash("md5", user.getPassword(), salt, 2).toString();
        // 存储用户信息,包括 salt 与 hash 后的密码
        user.setSalt(salt);
        user.setPassword(newPassword);
        userService.add(user);
        return "注册成功";
    }
               
  • 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.4.0</version>
            </dependency>
               
  • 前端提供一个注册的按钮写注册函数
    register(){
            var params = {
              username:this.username,
              password:this.password
            }
            axios.post('http://localhost:9527/user/register',params).then(res => {
              consoel.log(res)
            })
          }
               
  • 验证

    当输入已存在用户名:

    【Springboot+Vue】做一个权限管理后台(五):引入shiro
    当输入不同用户名:
    【Springboot+Vue】做一个权限管理后台(五):引入shiro
    可以看到,数据库中存储了用户的密码和盐值都是加密方式的,大大提高了用户信息的安全性。
    【Springboot+Vue】做一个权限管理后台(五):引入shiro
    以后用户登录的过程:
    • 前端明文提交账号密码
    • 后端根据用户名取出数据库中对应的盐值
    • 根据取出的盐值进行同样hash加密两次
    • 比如加密后的结果与数据库中的是否一致

Shiro框架的引入

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

  1. Shiro的三个概念

    Subject:当前与Shiro交互的东西,比如前端发送登录请求到后端,那么Subject就是提交的用户类。

    SecurityManager :管理全部的Subject。

    Realms:将用户信息进行一系列处理传递给SecurityManage。

  2. Realm的配置

    我们说过,Realm是输入的用户信息与SecurityManaga之间的桥梁,那么我们需要在Realm中做什么呢?比如对用户输入的用户名变成盐,对输入的密码进行加密等,操作完后才提交给SecurityManage。

    我们创建Realm文件夹下创建UserRealm.java文件,在其中对用户信息进行处理。

    public class UserRealm extends AuthorizingRealm {//所有自定义的Realm必须继承AuthorizingRealm
    
        @Autowired
        private UserService userService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;//对登录用户的授权,现在我们先不写
        }
    
        //此部分处理登录
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String username = token.getPrincipal().toString();//token由控制层传入,获取token中存储的用户名
            User user = userService.getUserByName(username);//根据用户名去数据库查询是否存在该用户
            if(user == null){
                throw new UnknownAccountException();//用户不存在抛出不存在异常交给控制层处理
            }
            String password = user.getPassword();
            String salt = user.getSalt();
            //再次把salt转成byte将整个认证交给SecurityManage
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),getName());
            return authenticationInfo;
        }
    }
               
  3. Shiro配置

    在Config文件夹下创建ShiroConfig.java文件进行Shiro配置

    package com.demo.demo.Confg;
    
    import com.demo.demo.Realm.UserRealm;
    import org.apache.shiro.authc.AbstractAuthenticator;
    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;
    
    @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;
        }
    }
    
               
  4. 登录控制层重写
    @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 "账号或密码错误";
            }
        }
               
  5. 登录验证一下
    【Springboot+Vue】做一个权限管理后台(五):引入shiro
    发现登录成功,说明我们的Shiro配置完成,已实现基本的登录。

下章预告

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

结语

有问题的可以在评论区留言哦,也可以提出意见或者建议。