shiro

概念

Apache Shiro 是java的一个安全框架,Shiro能够很是容易的开发出足够好的应用,其不只能够用在javaSE环境,也能够用在javaEE环境。Shiro能够帮助咱们完成:认证,受权,加密,会话,管理,与Web集合,缓存等。
Java领域中spring security也是一个开源的权限管理框架,可是spring security依赖spring运行,而shiro就相对独立,最主要是由于shiro使用简单,灵活,因此如今愈来愈多的用户选择shiro。html

基本功能

image-20191203112031777.png

Authentication

身份认证/登陆,验证用户是否是拥有相应的身份

Authorization

受权,即权限验证,验证某个已认证的用户是否拥有某个权限,判断用户是否能作事情,常见的如:验证某个用是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具备某个权限

Session Manager

会话管理,即用户登陆后就是一次会话,在没有退出以前,它的全部信息都在会话中,会话能够是普通javaSE环境的,也能够是如Web环境的。

Cryptography

加密,保护数据的安全性,如密码加密储存到数据库,而不是明文存储。
  • Web Support:Web 支持,能够很是容易的集成到web环境。
  • Caching:缓存,好比用户登陆后,其用户信息,用户的角色/权限没必要每次去查,这样能够提升效率。
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另外一个线程,能把权限自动传播过去。
  • Testing:提供测试支持。
  • Run As:容许一个用户伪装为另外一个用户的身份进行访问。
  • Remember Me:记住我,这个是很是常见的功能,即一次登陆后,下次再来的话不用登陆了。
  • Shiro:不会去维护用户,维护权限,这些须要咱们本身去设计/提供而后经过相应的接口注入给Shiro便可。

架构

image-20191204153257793.png

subject

Subject即主体,外部应用与subject进行交互,subject记录了当前操做用户,将用户的概念理解为当前操做的主体,多是一个经过浏览器请求的用户,也多是一个运行的程序。  Subject在shiro中是一个接口,接口中定义了不少认证授相关的方法,外部程序经过subject进行认证授,而subject是经过SecurityManager安全管理器进行认证受权

SecurityManager

SecurityManager即安全管理器,对所有的subject进行安全管理,它是shiro的核心,负责对全部的subject进行安全管理。
经过SecurityManager能够完成subject的认证、受权等,实质上SecurityManager是经过Authenticator进行认证,
经过Authorizer进行受权,经过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,
经过ModularRealmAuthenticator基本上能够知足大多数需求,也能够自定义认证器。

Authorizer

Authorizer即受权器,用户经过认证器认证经过,在访问功能时须要经过受权器判断用户是否有此功能的操做权限。

realm

Realm即领域,至关于datasource数据源,securityManager进行安全认证须要经过Realm获取用户权限数据,好比:若是用户身份数据在数据库那么realm就须要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证受权校验的相关的代码

sessionManager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,因此shiro可使用在非web应用上,也能够将分布式应用的会话集中在一点管理,此特性可以使它实现单点登陆。

SessionDAO

SessionDAO即会话dao,是对session会话操做的一套接口,好比要将session存储到数据库,能够经过jdbc将会话存储到数据库。

CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样能够提升性能。

Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。好比提供经常使用的散列、加/解密等功能。

默认过滤器

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

shiro.ini

shiro.ini配置文件共有[main],[users],[roles],[urls]共4部分组成

1.  [main] 用于定义全局变量

2.  [users] 用于定义用户名及密码

3.  [roles] 用于定义角色

4.  [urls] 用于定义访问url及拦截验证方式

[main]

#提供了对根对象 securityManager 及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
jdbcRealm = xxxxx
securityManager.realms=$jdbcRealm

[users]

#提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2 username=password,role1,role2
#定义用户信息
[users]
#用户名=密码
admin=123456
test=123

[roles]

#提供了角色及权限之间关系的配置,角色=权限 1,权限 2 role1=permission1,permission2
[roles]
role1=admin:query,admin:add,admin:update,admin:delete,admin:export
role2=user:query,user:add
role3=test:query,test:export

[urls]

#用于 web,提供了对 web url 拦截相关的配置,url=拦截器[参数],拦截器

Realm域

Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它须要从 Realm 获取相应的用户进行比较以肯定用户身份是否合法;也须要从 Realm 获得用户相应的角色/权限进行验证用户是否能进行操做;能够把 Realm 当作 DataSource ,即安全数据源。如咱们以前的ini配置方式 将使用org.apache.shiro.realm.text.IniRealm。
Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分状况下须要从系统的数据库中读取用户信息,
**因此须要自定义realm**

jar包

<!-- shiro核心依赖 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.2</version>
</dependency>

Realm

package com.bdqn.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;

public class JDBCRealmTest {

     public static void main(String[] args) {
        //1.读取配置文件,初始化工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.获取SecurityManager实例
        SecurityManager manager = factory.getInstance();
        //3.将SecurityManager绑定到工具类
        SecurityUtils.setSecurityManager(manager);
        //4.经过SecurityUtils获得当前登陆的用户
        Subject currentUser = SecurityUtils.getSubject();
        //5.窗口登陆令牌
        UsernamePasswordToken token = new UsernamePasswordToken("test","123");
        try {
            //6.登陆并传入令牌
            currentUser.login(token);
            System.out.println("身份信息验证成功!");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("身份信息验证失败!");
        }
        //7.退出
        currentUser.logout();
    }
}

加到项目中的验样子

Spring Boot框架
shiro
mysqljava


  • 配置 application.properties
#加载驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库链接路径
spring.datasource.url=jdbc:mysql://localhost:3306/erp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#数据库用户名
spring.datasource.username=root
#数据源类型(阿里巴巴)
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#thymeleaf
spring.thymeleaf.cache=false
#日期格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#mybatis-plus
#mybatis-plus.mapper-locations=classpath:mapper/*Mapper.xml,classpath:mapper/*/*Mapper.xml
#日志
logging.level.com.bdqn=debug
  • ShiroConfiguration 类
package com.bdqn.sys.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.bdqn.sys.realm.UserRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {
    private static final String SHIRO_DIALECT = "shiroDialect";
    private static final String SHIRO_FILTER = "shiroFilter";
    private String hashAlgorithmName = "md5";// 加密方式
    private int hashIterations = 2;// 散列次数


    /**
     * 声明凭证匹配器
     */
    @Bean("credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        credentialsMatcher.setHashIterations(hashIterations);
        return credentialsMatcher;
    }

    /**
     * 注入自定义的UserRealm
     * @return
     */
    @Bean
    public UserRealm getUserRealm(CredentialsMatcher credentialsMatcher){
        UserRealm userRealm = new UserRealm();
        //注入凭证匹配器
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }


    /**
     * 建立DefaultWebSecurityManager对象,关联自定义的UserRealm对象
     * @param userRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
        //建立DefaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联自定义realm
        defaultWebSecurityManager.setRealm(userRealm);
        //返回DefaultWebSecurityManager对象
        return defaultWebSecurityManager;
    }

    /**
     * 建立ShiroFilterFactoryBean对象,设置安全管理器
     * @param securityManager
     * @return
     */
    @Bean(SHIRO_FILTER)
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        //建立ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        factoryBean.setSecurityManager(securityManager);
        //设置过滤器链
        Map<String,String> filterChainDefinitionsMap = new LinkedHashMap<String,String>();
        //放行路径(匿名访问)
        filterChainDefinitionsMap.put("/resources/**","anon");//静态资源
        filterChainDefinitionsMap.put("/sys/user/login","anon");//登陆请求
        filterChainDefinitionsMap.put("/sys/login","anon");//去到登陆页面
        filterChainDefinitionsMap.put("/","anon");//去到登陆页面
        filterChainDefinitionsMap.put("/login.html","anon");//去到登陆页面
        filterChainDefinitionsMap.put("/favicon.ico","anon");//小图标
        //退出
        filterChainDefinitionsMap.put("/logout","logout");
        //拦截请求
        filterChainDefinitionsMap.put("/**","authc");
        //将过滤器链设置到shiroFilterFactoryBean对象中
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionsMap);
        //身份验证失败要去到登陆页面
        //若是不设置loginUrl,则默认找login.jsp页面
        factoryBean.setLoginUrl("/sys/login");
        return factoryBean;
    }

    /**
     * 注册shiro的委托过滤器,至关于以前在web.xml里面配置的
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName(SHIRO_FILTER);
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }
    /* 加入注解的使用,不加入这个注解不生效--开始 */
    /**
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /* 加入注解的使用,不加入这个注解不生效--结束 */

    /**
     * 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,否则会报错
     *
     * @return
     */
    @Bean(name = SHIRO_DIALECT)
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}
  • Realm 类
package com.bdqn.sys.realm;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bdqn.sys.entity.Permission;
import com.bdqn.sys.entity.User;
import com.bdqn.sys.service.PermissionService;
import com.bdqn.sys.service.RoleService;
import com.bdqn.sys.service.UserService;
import com.bdqn.sys.utils.SystemConstant;
import com.bdqn.sys.vo.LoginUserVo;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {


    @Resource
    private UserService userService;

    @Resource
    private RoleService roleService;

    @Resource
    private PermissionService permissionService;



    /**
     * 受权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //建立受权对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //1.获取当前登陆主体
        LoginUserVo loginUserVo = (LoginUserVo) principalCollection.getPrimaryPrincipal();
        //2.获取当前用户拥有的权限列表
        Set<String> permissions = loginUserVo.getPermissions();
        //3.判断当前登陆用户是不是超级管理员
        if(loginUserVo.getUser().getType()== SystemConstant.SUPERUSER){
            //超级管理员拥有全部操做权限
            simpleAuthorizationInfo.addStringPermission("*:*");
        }else{//普通用户
            //判断权限集合是否有数据
            if(permissions!=null && permissions.size()>0){
                //非超级管理员须要根据本身拥有的角色进行受权
                simpleAuthorizationInfo.addStringPermissions(permissions);
            }
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 身份验证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取当前登陆主体
        String userName = (String) authenticationToken.getPrincipal();
        try {
            //根据用户名查询用户信息的方法
            User user = userService.findUserByName(userName);
            //对象不为空
            if(user!=null){
                //建立当前登陆用户对象
                //建立登陆用户对象,传入用户信息,角色列表,权限列表
                LoginUserVo loginUserVo = new LoginUserVo(user,null,null);
                /***************************关联权限代码开始***************************************/
                //建立条件构造器对象
                QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>();
                queryWrapper.eq("type",SystemConstant.TYPE_PERMISSION);//只查权限不查菜单

                //根据当前登陆用户ID查询该用户拥有的角色列表
                Set<Integer> currentUserRoleIds = userService.findUserRoleByUserId(user.getId());
                //建立集合保存每一个角色下拥有的权限菜单ID
                Set<Integer> pids = new HashSet<Integer>();
                //循环遍历当前用户拥有的角色列表
                for (Integer roleId : currentUserRoleIds) {
                    //4.根据角色ID查询每一个角色下拥有的权限菜单
                    Set<Integer> permissionIds = roleService.findRolePermissionByRoleId(roleId);
                    //5.将查询出来的权限id放到集合中
                    pids.addAll(permissionIds);
                }
                //建立集合保存权限
                List<Permission> list = new ArrayList<Permission>();
                //判断pids集合是否有值
                if(pids!=null && pids.size()>0){
                    queryWrapper.in("id",pids);
                    //执行查询
                    list = permissionService.list(queryWrapper);
                }
                //查询权限编码
                Set<String> perCodes = new HashSet<String>();
                //循环每个菜单
                for (Permission permission : list) {
                    perCodes.add(permission.getPercode());
                }
                //给权限集合赋值
                loginUserVo.setPermissions(perCodes);

                /***************************关联权限代码结束***************************************/
                //建立盐值(以用户名做为盐值)
                ByteSource salt = ByteSource.Util.bytes(user.getSalt());
                //建立身份验证对象
                //参数1:当前登陆对象  参数2:密码  参数3:盐值 参数4:域名
                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(loginUserVo,user.getLoginpwd(),salt,"");
                return info;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //验证失败
        return null;
    }


}
相关文章
相关标签/搜索