shiro看了有一段时间了。但是由于之前对这部分理解不了所以在这上面学习的进展一直不多。但是有了解权限管理在日常开发中很重要,所以硬着头皮也要啃下来。实现功能:
项目完整github地址 欢迎star
springboot一些学习整合完整地址
shiro的四大组件:
Shiro内置过滤器,可以实现权限相关的拦截器
常用的过滤器:
anon: 无需认证(登录)可以访问
authc: 必须认证才可以访问
user: 如果使用rememberMe的功能可以直接访问
perm: 该资源必须得到资源权限才可以访问
role: 该资源必须得到角色权限才可以访问
这里面只用到了身份认证和授权,权限认证只用到了一点点,shiro的原理是封装的过滤器,他能够在访问浏览器前能过自动完成一些内容。
shiro配置主要两部分—— shiroconfig和自定义的Realm(继承AuthorizingRealm)
。其中,shiroconfig是shiro的主要配置文件,而自定义的Realm主要是重写 AuthorizingRealm
的两个方法,分别是身份认证和授权认证调用数据库查询比对。而如果需要role访问则需要重写一个filter。
项目结构:
环境:
新建表:
对应的bean:
package com.shiro.bean; public class student { private String username; private String password; private String role; private String perm; //省略get set 复制代码
mybatis简单查询:
package com.shiro.mapper; import com.shiro.bean.student; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface studentMapper { @Select("select * from student where username=#{name}") student findByName(String name); } 复制代码
省略html和sql,详细可以到GitHub下载
页面目录,:
UserRealm.java
package com.shiro.config; import com.shiro.bean.student; import com.shiro.mapper.studentMapper; import org.apache.shiro.SecurityUtils; 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.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * 自定义Realm * @author bigsai * */ public class UserRealm extends AuthorizingRealm{ @Autowired(required = false) private studentMapper studentMapper; private final Logger logger= LoggerFactory.getLogger(UserRealm.class); /** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { logger.info("执行逻辑授权"); //给资源进行授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加资源的授权字符串 //到数据库查询当前登录用户的授权字符串 //获取当前登录用户 Subject subject = SecurityUtils.getSubject(); student user = (student) subject.getPrincipal(); student dbUser = studentMapper.findByName(user.getUsername()); info.addRole(user.getRole());//添加role 和perms role代表角色 perms代表操作,或者动作等。用于颗粒化权限管理 info.addStringPermission(dbUser.getPerm()); System.out.println("user:"+dbUser.getPerm()); return info; } /** * 执行认证逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("执行认证逻辑"); //编写shiro判断逻辑,判断用户名和密码 //1.判断用户名 UsernamePasswordToken token = (UsernamePasswordToken)arg0; student user = studentMapper.findByName(token.getUsername()); if(user==null){ //用户名不存在 return null;//shiro底层会抛出UnKnowAccountException } //2.判断密码 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } } 复制代码
rolesFilter
package com.shiro.config; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; // AuthorizationFilter抽象类事项了javax.servlet.Filter接口,它是个过滤器。 public class rolesFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception { Subject subject = getSubject(req, resp); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问 return true; } for (int i = 0; i < rolesArray.length; i++) { if (subject.hasRole(rolesArray[i])) { //若当前用户是rolesArray中的任何一个,则有权限访问 return true; } } return false; } } 复制代码
shiroConfig:shiro的主要配置
package com.shiro.config; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro的配置类 * * @author bigsai */ @Configuration public class ShiroConfig { /** * 创建ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filtersMap = new LinkedHashMap<>(); filtersMap.put("rolesFilter",new rolesFilter()); shiroFilterFactoryBean.setFilters(filtersMap);//使用自定义fitter //添加Shiro内置过滤器 /** * Shiro内置过滤器,可以实现权限相关的拦截器 * 常用的过滤器: * anon: 无需认证(登录)可以访问 * authc: 必须认证才可以访问 * user: 如果使用rememberMe的功能可以直接访问 * perm: 该资源必须得到资源权限才可以访问 * role: 该资源必须得到角色权限才可以访问 */ Map<String, String> filterMap = new LinkedHashMap<String, String>(); filterMap.put("/login", "anon");//要将登陆的接口放出来,不然没权限访问登陆的接口 filterMap.put("/getcontroller", "anon"); // //授权过滤器 //注意:当前授权拦截后,shiro会自动跳转到未授权页面 filterMap.put("/add", "perms[add]"); filterMap.put("/update", "perms[update]"); // filterMap.put("/test1.html","rolesFilter[admin,user]"); filterMap.put("/*", "authc");//authc即为认证登陆后即可访问 //修改调整的登录页面 shiroFilterFactoryBean.setLoginUrl("/index"); //设置未授权提示页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } /** * 创建DefaultWebSecurityManager */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联realm securityManager.setRealm(userRealm); return securityManager; } /** * 创建Realm */ @Bean(name = "userRealm") public UserRealm getRealm() { return new UserRealm(); } } 复制代码
身份认证,就是登录校检。这是第一层过滤,并且当用户没有登录的时候,回退到没登陆的界面。在controller中,login的核心为:
@RequestMapping("/login") public String login(String name, String password, Model model, HttpServletRequest request) { model.addAttribute("nama", "给个star"); /** * 使用Shiro编写认证操作 */ //1.获取Subject Subject subject = SecurityUtils.getSubject(); //2.封装用户数据 UsernamePasswordToken token = new UsernamePasswordToken(name, password); //3.执行登录方法 try { subject.login(token); //登录成功 //跳转 return "redirect:/index2"; } catch (UnknownAccountException e) { //e.printStackTrace(); //登录失败:用户名不存在 model.addAttribute("msg", "用户名不存在"); return "login"; } catch (IncorrectCredentialsException e) { //e.printStackTrace(); //登录失败:密码错误 model.addAttribute("msg", "密码错误"); return "login"; } } 复制代码
releam中
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("执行认证逻辑"); //编写shiro判断逻辑,判断用户名和密码 //1.判断用户名 UsernamePasswordToken token = (UsernamePasswordToken)arg0; student user = studentMapper.findByName(token.getUsername()); if(user==null){ //用户名不存在 return null;//shiro底层会抛出UnKnowAccountException } //2.判断密码 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } 复制代码
而这只是表象的处理过程,而在releam(继承 AuthorizingRealm
)中需要充血 doGetAuthenticationInfo()
方法.
大致流程为: 登录
——> 拿账号密码检验
———> 用着token的账号通过你的sql查询对象
——> 比对数据是否一致
——> 通过还是抛各种异常
而在shiroConfig中,基于url过滤时 authc
即可访问
可能会遇到如下情况:教师端,学生端来自两张表,两个登录接口,我该如何使用shiro身份认证。对于这种问题,你可以配置多个releam,但是我觉得如果简单你可以在不同的登录接口下传递一个参数过来,这个参数就用session传递。因为, shiro的session和网页httprequest获得的session是同一个session
。
所以当你在login传递一个属性到releam中,可用 if else判断然后不同登录接口执行不同的查询方法即可。
接上流程 是否登录
——> 是/否
——(是)—> 查询role/perm添加到subject
——> 过滤器校验该url需要权限
——> 可以访问/权限不足
shiro主要url可以根据角色(role)和资源(perm)的管理。对于role,可以是管理员,教师等,而perm,可能是一个动作,一个操作,等等。==并且可能一个角色拥有多个role和perm==。 同理,授权就是查询数据库的role或者perm字段添加到角色中。当然具体api不做介绍。 主要方法为上述:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { logger.info("执行逻辑授权"); //给资源进行授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加资源的授权字符串 //到数据库查询当前登录用户的授权字符串 //获取当前登录用户 Subject subject = SecurityUtils.getSubject(); student user = (student) subject.getPrincipal(); student dbUser = studentMapper.findByName(user.getUsername()); info.addRole(user.getRole());//添加role 和perms role代表角色 perms代表操作,或者动作等。用于颗粒化权限管理 info.addStringPermission(dbUser.getPerm()); System.out.println("user:"+dbUser.getPerm()); return info; } 复制代码
而url中也是
filterMap.put("/add", "perms[add]"); filterMap.put("/update", "roles[admin]"); 复制代码
常常遇到这种情况:一个接口/页面,有两个或者以上角色可以访问。然后再后台的过滤器配置总。shiro默认的配置是and而不是or。这就需要我们自己定义filter继承 AuthorizationFilter
从写对应方法。
以多角色访问为例子。从写上述就是文件rolesFilter。在使用的时候也要首先声明filter才能使用。
有个小注意点:如果mybatis2.0版本回和spring-start-web有冲突。我用1.3.2版本没问题。