SpringBoot整合Shiro实现完整的用户登录

一、简介

Apache Shiro是一个功能强大且易于使用的 Java 安全框架,可执行身份验证、授权、加密和会话管理。借助 Shiro 易于理解的 API,您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。

二、流程

用户输入用户名、密码–点击登录–> Shrio自封装成UsertnamePasswordToken----> 主体信息:Subject(可以是具体的人,也可以是其他实体…)---->Shiro的SecurityManager(用于管理所有的主题Subject)---->真正用于认证和授权的Realm
在这里插入图片描述

三、添加依赖

加入权限认证框架Shiro的相关依赖
在这里插入图片描述

<properties>
        <shiro.version>1.7.1</shiro.version>
</properties>
<dependencies>
<!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
</dependencies>

添加依赖的时候可以直接用Maven仓储库官网搜索到相关依赖的格式。

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.7.1</version>
</dependency>

四、实现

1、controller

package com.tony.pmp.server.controller;

import com.tony.pmp.common.response.BaseResponse;
import com.tony.pmp.common.response.StatusCode;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: SysLoginController
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/14:13
 * @Description:
 */
@Controller
public class SysLoginController {
    //日志
    protected Logger log = LoggerFactory.getLogger(getClass());
    
    /**
    * @Description: login 登录
    * @Param: [username 用户名, password 密码, captcha 验证码]
    * @return: com.tony.pmp.common.response.BaseResponse
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 14:17
    */
    @RequestMapping(value = "/sys/login",method = RequestMethod.POST)
    @ResponseBody
    public BaseResponse login(String username,String password,String captcha) {
        log.info("用户名:{} 密码: {} 验证码:{}",username,password,captcha);
        try {
            //region 提交登录
            Subject subject = SecurityUtils.getSubject(); //subject:提交的用户主体信息
            if (!subject.isAuthenticated()) {
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                subject.login(token);
            }
            //endregion
        } catch (Exception e) {
            return new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return new BaseResponse(StatusCode.Success);
    }
}

2、mapper

Shiro配置中获取到的用户数据是使用mybatisplus获取到的(select * from user_tbl where username = “XX”),你可以直接自己写或者直接继承BaseMapper

package com.tony.pmp.model.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tony.pmp.model.entity.SysUserEntity;

/**
 * Created with IntelliJ IDEA.
 * @Title: SysUserMapper
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/09/10:05
 * @Description: 系统用户
 */
public interface SysUserMapper extends BaseMapper<SysUserEntity> {
}

3、加密工具类

package com.tony.pmp.server.shiro;

import com.tony.pmp.common.exception.CommonException;
import com.tony.pmp.model.entity.SysUserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: ShiroUtil
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/20:52
 * @Description: 加密工具类
 */
public class ShiroUtil {
    //加密算法
    public final static String hashAlgorithmName = "SHA-256";

    //循环次数
    public final static int hashIterations = 16;

    public static String sha256(String password, String salt) {
        return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();
    }

    //获取Shiro Session
    public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }

    //获取Shiro Subject
    public static Subject getSubject() {
        return SecurityUtils.getSubject();
    }

    //获取Shiro中的真正主体
    public static SysUserEntity getUserEntity() {
        return (SysUserEntity)SecurityUtils.getSubject().getPrincipal();
    }

    public static Long getUserId() {
        return getUserEntity().getUserId();
    }

    public static void setSessionAttribute(Object key, Object value) {
        getSession().setAttribute(key, value);
    }

    public static Object getSessionAttribute(Object key) {
        return getSession().getAttribute(key);
    }

    public static boolean isLogin() {
        return SecurityUtils.getSubject().getPrincipal() != null;
    }

    public static void logout() {
        SecurityUtils.getSubject().logout();
    }

    /**
    * @Description: main 密码生成测试(明文+盐)
    * @Param: [args]
    * @return: void
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 21:00
    */
    public static void main(String[] args) {
        String password="debug";
        System.out.println(ShiroUtil.sha256(password, "YzcmCZNvbXocrsz9dm8e"));
	}
}

五、配置

1、实现自定义注入Shiro相关组件的配置

在这里插入图片描述

package com.tony.pmp.server.config;

import com.tony.pmp.server.shiro.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
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;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: ShiroConfig
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/16:22
 * @Description: Shiro的通用化配置
 */
@Configuration
public class ShiroConfig {

    /**
    * @Description: securityManager 安全器管理-管理所有的subject
    * @Param: [userRealm]
    * @return: org.apache.shiro.mgt.SecurityManager
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:33
    */
    @Bean
    public SecurityManager securityManager(UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

    /**
    * @Description: shiroFilter 过滤器配置
    * @Param: [securityManager]
    * @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:33
    */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter=new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        //设定用户没有登录认证时的跳转链接、没有授权时的跳转链接
        shiroFilter.setLoginUrl("/login.html");
        shiroFilter.setUnauthorizedUrl("/");

        //过滤器链配置
        Map<String, String> filterMap = new LinkedHashMap();
        //region 不过滤anon(一些静态资源或者登录页面等)
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/swagger-resources/**", "anon");

        filterMap.put("/statics/**", "anon");
        filterMap.put("/login.html", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/favicon.ico", "anon");
        //endregion

        //过滤authc
        filterMap.put("/**","authc");

        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    /**
    * @Description: lifecycleBeanPostProcessor 关于Shiro的Bean生命周期的管理
    * @Param: []
    * @return: org.apache.shiro.spring.LifecycleBeanPostProcessor
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:33
    */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

2、实现自定义的Realm

并重写其中的登录认证与密码匹配器逻辑。
在这里插入图片描述

①明文匹配

此种方式不安全

package com.tony.pmp.server.shiro;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tony.pmp.model.entity.SysUserEntity;
import com.tony.pmp.model.mapper.SysUserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: UserRealm
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/16:23
 * @Description: 自定义Realm
 */
@Component
public class UserRealm extends AuthorizingRealm {

    private static final Logger log= LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private SysUserMapper sysUserMapper;
    
	/**
    * @Description: doGetAuthorizationInfo 资源权限分配的授权-核心1(需要将分配给当前用户的权限列表塞给shiro的权限字段中去)
    * @Param: [principalCollection]
    * @return: org.apache.shiro.authz.AuthorizationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    
    /**
    * @Description: doGetAuthenticationInfo 用户认证(登录认证)-核心2
    * @Param: [authenticationToken]
    * @return: org.apache.shiro.authc.AuthenticationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        final String userName=token.getUsername();
        final String password=String.valueOf(token.getPassword());
        log.info("用户名: {} 密码:{}",userName,password);
		//数据库用户
        SysUserEntity entity=sysUserMapper.selectOne(new QueryWrapper<SysUserEntity>().eq("username",userName));
        //账户不存在
        if (entity==null){
            throw new UnknownAccountException("账户不存在!");
        }
        //账户被禁用
        if (0 == entity.getStatus()){
            throw new DisabledAccountException("账户已被禁用,请联系管理员!");
        }

        //第一种 : 明文匹配
        //账户名密码不匹配
        if (!entity.getPassword().equals(password)){
            throw new IncorrectCredentialsException("账户密码不匹配!");
        }
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(entity,password,getName());
        return info;
    }
}

②加密匹配

使用SHA-256加密算法进行加密(明文+盐)

package com.tony.pmp.server.shiro;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tony.pmp.model.entity.SysUserEntity;
import com.tony.pmp.model.mapper.SysUserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: UserRealm
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/16:23
 * @Description: 自定义Realm
 */
@Component
public class UserRealm extends AuthorizingRealm {

    private static final Logger log= LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
    * @Description: doGetAuthorizationInfo 资源权限分配的授权-核心1(需要将分配给当前用户的权限列表塞给shiro的权限字段中去)
    * @Param: [principalCollection]
    * @return: org.apache.shiro.authz.AuthorizationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
    * @Description: doGetAuthenticationInfo 用户认证(登录认证)-核心2
    * @Param: [authenticationToken]
    * @return: org.apache.shiro.authc.AuthenticationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        final String userName=token.getUsername();
        final String password=String.valueOf(token.getPassword());

        log.info("用户名: {} 密码:{}",userName,password);

        SysUserEntity entity=sysUserMapper.selectOne(new QueryWrapper<SysUserEntity>().eq("username",userName));
        //账户不存在
        if (entity==null){
            throw new UnknownAccountException("账户不存在!");
        }
        //账户被禁用
        if (0 == entity.getStatus()){
            throw new DisabledAccountException("账户已被禁用,请联系管理员!");
        }

        //region 第二种:交给shiro的密钥匹配器去实现
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(entity, entity.getPassword(), ByteSource.Util.bytes(entity.getSalt()), getName());
        return info;
        //endregion
    }

    /**
    * @Description: setCredentialsMatcher 第二种:shiro自带密码验证器
    * @Param: [credentialsMatcher]
    * @return: void
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 21:09
    */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        shaCredentialsMatcher.setHashAlgorithmName(ShiroUtil.hashAlgorithmName);
        shaCredentialsMatcher.setHashIterations(ShiroUtil.hashIterations);
        super.setCredentialsMatcher(shaCredentialsMatcher);
    }
}

③明文加密匹配

package com.tony.pmp.server.shiro;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.tony.pmp.model.entity.SysUserEntity;
import com.tony.pmp.model.mapper.SysUserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created with IntelliJ IDEA.
 *
 * @Title: UserRealm
 * @Auther: 皮蛋布丁
 * @Date: 2021/07/11/16:23
 * @Description: 自定义Realm
 */
@Component
public class UserRealm extends AuthorizingRealm {

    private static final Logger log= LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
    * @Description: doGetAuthorizationInfo 资源权限分配的授权-核心1
    * @Param: [principalCollection]
    * @return: org.apache.shiro.authz.AuthorizationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
    * @Description: doGetAuthenticationInfo 用户认证(登录认证)-核心2
    * @Param: [authenticationToken]
    * @return: org.apache.shiro.authc.AuthenticationInfo
    * @Author: 皮蛋布丁
    * @Date: 2021/7/11 16:35
    */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        final String userName=token.getUsername();
        final String password=String.valueOf(token.getPassword());

        log.info("用户名: {} 密码:{}",userName,password);

        SysUserEntity entity=sysUserMapper.selectOne(new QueryWrapper<SysUserEntity>().eq("username",userName));
        //账户不存在
        if (entity==null){
            throw new UnknownAccountException("账户不存在!");
        }
        //账户被禁用
        if (0 == entity.getStatus()){
            throw new DisabledAccountException("账户已被禁用,请联系管理员!");
        }
        
        //region 第三种:前端传递的明文+数据库的盐加密后匹配(比较常用)
        String realPassword=ShiroUtil.sha256(password,entity.getSalt());
        if (StringUtils.isBlank(realPassword) || !realPassword.equals(entity.getPassword())){
            throw new IncorrectCredentialsException("账户密码不匹配!");
        }
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(entity,password,getName());
        return info;
        //endregion
    }
}

3、数据表

主要注意的是密码存储的是密文

CREATE TABLE `sys_user` (
  `user_id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '姓名',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) DEFAULT NULL COMMENT '密码',
  `salt` varchar(20) DEFAULT NULL COMMENT '盐',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `mobile` varchar(100) DEFAULT NULL COMMENT '手机号',
  `status` tinyint DEFAULT '1' COMMENT '状态  0:禁用   1:正常',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb3 COMMENT='系统用户';

注:能力有限,还请谅解,争取早日能够写出有质量的文章!

我是皮蛋布丁,一位爱吃皮蛋的热爱运动的废铁程序猿。

在这里插入图片描述

感谢各位大佬光临寒舍~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值