SpringBoot整合Shiro实现用户登录认证和权限鉴定

版权声明:谁不是在与生活苦战。狼性的年级,就不要做个俗人 https://blog.csdn.net/Axela30W/article/details/86595693

最近两个月诸事不顺,有时候显得浮躁焦虑,也没处理好自己的情绪,小伙子,需要冷静。你还有3个月才二十四岁,你着什么急。只有你足够优秀,才能遇到更优秀的人。文字是让人静下来的好东西。2019年的第一篇博客就献给Shiro吧。


一、Shiro

Apache Shiro是一个Java安全框架,用来做身份验证(用户登录)、授权(权限控制)、密码和会话管理。常用的就是前两个模块。Shiro配置简单,使用起来无倾入性。比SpringSecurity更轻量级。
先对3个核心组件类有个印象,一定先理解这三个东西,对后面写代码和设计有帮助:

  • Subject:可以把它看成当前请求访问系统的用户。会存储当前用户的信息,用户名密码等等。
  • SecurityManager:管理所有用户的安全操作。它是Shiro框架的核心,Shiro通过SecurityManager来管理内部各个组件实例,并通过它来提供安全管理的各种服务。权限验证等等。
  • Realm: 当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm(自己定义这个Realm)验证用户及其权限信息。

二、设计方案

本文只是介绍Shiro的基本使用和一般系统的登录,权限模块设计思路。

  • system_user(id、username、password、role_id<system_role.id>):用户表
  • system_role(id、role_name):角色表
  • system_resources(id、resources_name、resources_url、permission_code):系统资源表,resources_url就是该资源的请求路径,譬如一个接口请求路径,permission_code是使用Shiro的权限控制注解是需要用到的code,后面会看到。此表有条数据【1,用户列表,/user/getList,permis[get]】后面会用到!!!!
  • role_resources(id、role_id<system_role.id>、resources_id<system_resources.id>):角色资源关联表
    大致表结构如上,很清晰易懂。
    系统每个用户对应一个角色(本文就设计为一个用户只有一个角色,当然,你也可以设计多个角色的情况)。
    传统的权限控制手段可能就是:用户登录–>查询数据库用户名和密码是否匹配–>匹配的话根据当前用户的role_id在角色资源关联表查询到该角色拥有的资源id集合–>关联系统资源表–>再去判断该用户是否有此资源的权限。Shiro可以包揽整个过程。

三、实现

一步一步看着来,不要着急。慢慢理解,我应该写的很详细了,不难懂。(这里只列核心代码)
pom.xml Shiro依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>

本文只做两个事,把用户登录的验证和用户是否拥有某个接口的请求权限交给Shiro来处理。

1、先解决用户登录问题

try{
	//省略了其他的常规代码,比如判断字段是否为空之类的
	//此Subject就是开头提到的  代表当前用户
    Subject subject = SecurityUtils.getSubject();
    //用请求的用户名和密码创建UsernamePasswordToken(此类来自shiro包下)
    UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
    //调用subject.login进行验证,验证不通过则会抛出AuthenticationException异常,然后自定义返回信息
    subject.login(usernamePasswordToken);
    //未抛异常 则验证通过
    //此Session也来自shiro包  是对传统的HttpSession的封装,可以看做是一样的
    Session session = subject.getSession();
    //下面的是自定义的代码,随你怎么写
    session.setAttribute(RequestConstans.USER_ID, checkUser.getId());
} catch (AuthenticationException e) {
	//这行也是自定义的代码,随你怎么写
    throw new BusinessException(BusinessErrorCode.USER_LOGIN_PWD_ERROR_FAIL);
}

这是截取的登录接口的一部分代码,这就是登录接口方法的处理了,很简单,通过subject.login(usernamePasswordToken);方法,就把登录验证交给Shiro来处理了。省略了原来自己去判断用户名跟密码是否匹配的过程。
扩展:hiro session和Spring session一样吗?

2、自定义的Realm,用来处理登录验证与鉴权

package com.web.config;

import com.mechat.backend.dao.CommercialMapper;
import com.mechat.backend.dao.SystemMenuMapper;
import com.mechat.backend.model.Commercial;
import com.mechat.backend.model.SystemMenu;
import com.mechat.backend.utils.UserConfig;
import com.mechat.common.auth.AESUtils;
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.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author: WangRui
 * @Date: 2019/1/22
 * Time: 22:43
 * Description:
 */
public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private SystemMenuMapper systemMenuMapper;

    @Autowired
    private UserMapper userMapper;

    /**
     * 用户登录认证方法
     * 上面不是调用了subject.login()方法嘛,就会进入到这个方法来进行具体登录验证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    	//authenticationToken.getPrincipal()是获得用户名
        if (authenticationToken.getPrincipal() == null) {
            throw new AuthenticationException("账号名为空,登录失败!");
        }
        //获取用户信息
        String name = authenticationToken.getPrincipal().toString();
        User user = new User();
        user.setUserName(name);
        //通过用户名在数据库查到该用户的信息
        user = userMapper.selectByPKey(user);
        if (user == null) {
            //这里返回后会报出对应异常
            throw new AuthenticationException("不存在的账号,登录失败!");
        } else {
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
            //getName()  是Shiro包下org.apache.shiro.realm.CachingRealm的方法,不是自定义的
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword, getName());
            return simpleAuthenticationInfo;
        }
    }

    /**
     * 用户鉴权
     * 用户请求有权限要求的接口时要经过此认证,
     * 譬如我在某个Controller的方法上加了注解@RequiresPermissions(value = "permis[get]"),
     * 那么该用户的角色拥有的资源必须要包含“permis[get]”权限才能访问此接口,
     * “permis[get]”对应文章开头说的表结构那里的系统资源表中的permission_code
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户名,此用户名是在登录接口里new UsernamePasswordToken()时设置的
        String account = (String) principalCollection.getPrimaryPrincipal();
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //根据用户账号查询拥有的所有资源权限  SystemMenu对应表system_resources,这是一条关联sql,
        //通过用户账号-拿到角色id-在role_resources查询到拥有的resources_id-然后关联system_resources查询到拥有的所有资源
        List<SystemMenu> systemMenuList = systemMenuMapper.findUserPermission(account);
        //添加角色   因为此次只关联到权限permission,故暂不添加角色,只通过permission来鉴权
        //添加角色对应的使用注解是 @RequiresRoles()
        //simpleAuthorizationInfo.save("admin");
        //添加权限  在这里就把该用户对应的角色拥有的所有的权限的permission_code添加到Shiro了,每次访问带有权限限制的接口时就会验证,拥有对应权限code的话就可以正常访问。
        simpleAuthorizationInfo.addStringPermissions(systemMenuList.stream().map(systemMenu -> systemMenu.getPermission()).collect(Collectors.toList()));
        return simpleAuthorizationInfo;
    }
}

注解使用图解:
在这里插入图片描述
注意@RequiresPermissions(value = “permis[get]”) value的值就是对应资源的permission_code,见开头表结构设计那里!!!换句话说,此用户必须要有此资源权限才能访问这个Controller方法。

3、securityManager安全配置

自定义的MyShiroRealm写好了,要配置他才能使用。下面就是Shiro的配置类了:
定义了一个Properties,需要忽略验证的请求路径

@Component
@ConfigurationProperties
public class IgnoreAuthUrlProperties {

    List<String> ignoreAuthUrl;

    public List<String> getIgnoreAuthUrl() {
        return ignoreAuthUrl;
    }

    public void setIgnoreAuthUrl(List<String> ignoreAuthUrl) {
        this.ignoreAuthUrl = ignoreAuthUrl;
    }
}

对应的配置:
在这里插入图片描述

package com.web.config;

import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.*;

/**
 * @Author: WangRui
 * @Date: 2019/1/22
 * Time: 22:44
 * Description:
 */
@Configuration
public class ShiroConfiguration {
    @Autowired
    private IgnoreAuthUrlProperties ignoreAuthUrlProperties;

    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }

    @Bean
    public CacheManager cacheManager() {
        return new EhCacheManager();
    }

    @Bean
    public SessionDAO sessionDAO() {
        return new EnterpriseCacheSessionDAO();
    }

    @Bean
    public SessionManager sessionManager(SessionDAO sessionDAO) {
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        manager.setSessionDAO(sessionDAO);
        //设置session过期时间
        manager.setGlobalSessionTimeout(3600000);
        manager.setSessionValidationInterval(3600000);
        return manager;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     */
    @Bean
    public SecurityManager securityManager(CacheManager cacheManager, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager);
        securityManager.setRealm(myShiroRealm());
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件  这是重点!!!
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Set<String> urlSet = new HashSet<>(ignoreAuthUrlProperties.getIgnoreAuthUrl());
        //必须采用LinkedHashMap有序存储过滤条件 
        Map<String, String> map = new LinkedHashMap<>();
        //anon表示所有用户都可以不鉴权匿名访问
        urlSet.stream().forEach(temp -> map.put(temp, "anon"));
        //此路径必须放在最后  这是为啥一定使用LinkedHashMap的原因
        map.put("/**", "authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 加入shiro注解的使用,不加入这个注解不生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

至次,Shiro的登录验证和鉴权算是开发完成了。只是简单实用,Shiro还有其他很强大的功能。
有啥疑问网上搜一搜应该都能解决。也可以留言。


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Axela30W/article/details/86595693