Apache Shiro 是java的一个安全框架,Shiro可以非常容易的开发出足够好的应用,其不仅可以用在javaSE环境,也可以用在javaEE环境。Shiro可以帮助我们完成:认证,授权,加密,会话,管理,与Web集合,缓存等。
Java领域中spring security也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单,灵活,所以现在越来越多的用户选择shiro。
身份认证/登录,验证用户是不是拥有相应的身份
授权,即权限验证,验证某个已认证的用户是否拥有某个权限,判断用户是否能做事情,常见的如:验证某个用是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中,会话可以是普通javaSE环境的,也可以是如Web环境的。
加密,保护数据的安全性,如密码加密储存到数据库,而不是明文存储。
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。 通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证, 通过Authorizer进行授权,通过SessionManager进行会话管理等。 SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类, 通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
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配置文件共有[main],[users],[roles],[urls]共4部分组成 1. [main] 用于定义全局变量 2. [users] 用于定义用户名及密码 3. [roles] 用于定义角色 4. [urls] 用于定义访问url及拦截验证方式
#提供了对根对象 securityManager 及其依赖的配置 securityManager=org.apache.shiro.mgt.DefaultSecurityManager jdbcRealm = xxxxx securityManager.realms=$jdbcRealm
#提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2 username=password,role1,role2 #定义用户信息 [users] #用户名=密码 admin=123456 test=123
#提供了角色及权限之间关系的配置,角色=权限 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
#用于 web,提供了对 web url 拦截相关的配置,url=拦截器[参数],拦截器
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource ,即安全数据源。如我们之前的ini配置方式 将使用org.apache.shiro.realm.text.IniRealm。 Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息, **所以需要自定义realm**
<!-- shiro核心依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.2</version> </dependency>
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
mysql
#加载驱动 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
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(); } }
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; } }