shiro是apache下的一个开源的权限认证框架。相对于spring security来说是一个轻量级的安全认证组件。今天用shiro讲原来的权限认证给替换掉。总结一下如下:
第一步肯定是要引入shiro的相关依赖的:
<!-- 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-spring-boot-starter</artifactId>
<version>1.4.0-RC2</version>
</dependency>
第二步就是編寫shiro相關的。从用户登录开始吧:
**
* 登陆操作
* @param request
* @return
*/
@ApiOperation(value="登陆操作", notes="描述")
@RequestMapping(value = "login.do", method = RequestMethod.POST)
@ResponseBody
public ResultMsg loginDo(HttpServletRequest request){
//验证码校验
if (!CaptchaUtils.checkVerifyCode(request)) {
return ResultMsg.fail("验证码有误!");
}
String name = request.getParameter("name");
String password = request.getParameter("password");
//用户名密码校验
UsernamePasswordToken token = new UsernamePasswordToken(name, MD5Util.MD5(password));
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
return ResultMsg.fail("用户名或密码错误!");
}
return ResultMsg.ok();
}
注意点:
UsernamePasswordToken token = new UsernamePasswordToken(name, MD5Util.MD5(password));
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
这三行代码是关键。见该用户名和密码封装shiro内置touken对象。获取主题。提交登录信息给shiro认证。看源码就会
发现最后调用的是:info = this.doGetAuthenticationInfo(token);方法
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
那么我们就集成这个抽象类public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {重写认证方法如下:
package com.nuanshui.mintleaf.common;
import com.nuanshui.mintleaf.modules.core.dao.CoreButtonDao;
import com.nuanshui.mintleaf.modules.core.dao.CoreMenuDao;
import com.nuanshui.mintleaf.modules.core.dao.CoreRoleDao;
import com.nuanshui.mintleaf.modules.core.entity.CoreButton;
import com.nuanshui.mintleaf.modules.core.entity.CoreMenu;
import com.nuanshui.mintleaf.modules.core.entity.CoreRole;
import com.nuanshui.mintleaf.modules.core.dao.CoreUserDao;
import com.nuanshui.mintleaf.modules.core.entity.CoreUser;
import lombok.extern.slf4j.Slf4j;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Slf4j
public class Realm extends AuthorizingRealm{
@Autowired
private CoreUserDao coreUserDao;
@Autowired
private CoreRoleDao coreRoleDao;
@Autowired
private CoreMenuDao coreMenuDao;
@Autowired
private CoreButtonDao coreButtonDao;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取用户名
String userName = (String)SecurityUtils.getSubject().getPrincipal();
//根据用户名查询用户对象
CoreUser coreUser = coreUserDao.getCoreUserByname(userName);
//根据用户获取用户的角色
List<CoreRole> userRoles = coreRoleDao.getUserRolesById(coreUser.getId());
userRoles.forEach(coreRole -> {
log.info("角色的名称:{}",coreRole.getName());
info.addRole(coreRole.getName());
//根据角色获取用户菜单
List<CoreMenu> menus = coreMenuDao.findMenuByRole(coreRole.getName());
//根据角色获取按钮信息
List<CoreButton> buttons = coreButtonDao.findButtonByRole(coreRole.getName());
menus.forEach(coreMenu -> {
log.info("菜单权限:{}",coreMenu.getPermission());
info.addStringPermission(coreMenu.getPermission());
});
buttons.forEach(coreButton -> {
log.info("按钮权限:{}",coreButton.getPermission());
info.addStringPermission(coreButton.getPermission());
});
});
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获得当前用户的用户名
String username = (String)authenticationToken.getPrincipal();
//从数据库中根据用户名查找用户
CoreUser coreUser = coreUserDao.getCoreUserByname(username);
if(coreUser==null){
new UnknownAccountException("账号不存在!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(coreUser.getName(), coreUser.getPsw(),getName());
return info;
}
}
doGetAuthenticationInfo方法中验证用户信息。实现登录成功。那么第一步登录认证就完成了
接下来就是授权。授权调用doGetAuthorizationInfo()方法根据用户信息后去用户的权限。然后交给shiro去处理。shiro值负责去验证是否有权限。而权限的赋值需要开发去实现。
/** * 进入新增页面 * @return */ @RequiresPermissions("auth:user:add") @ApiOperation(value="进入新增页面", notes="描述") @RequestMapping(value="add.html",method = {RequestMethod.GET}) public ModelAndView add(){ ModelAndView view =new ModelAndView("modules/core/coreuser/add.html"); return view; }
@RequiresPermissions("auth:user:add")这一行注解表示该方法需要授权认证是否有权限。去掉则不认证。不认真就不会去调用doGetAuthorizationInfo()方法。
然后就是配置shiroconfig.这是一个配置类。配置类的作用就是告诉shiro那些路径需要拦截认证授权。那些不需要拦截直接匿名访问。如下:
package org.mintleaf.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.mintleaf.common.MyExceptionResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.mintleaf.common.Realm;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author: MengchuZhang
* @Date: 2018/8/13 16:29
* @Version 1.0
*/
@Configuration
public class ShiroConfig {
//定义路径拦截的规则
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String, String> filterMap = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
filterMap.put("/css/**", "anon");
filterMap.put("/images/**", "anon");
filterMap.put("/script/**", "anon");
filterMap.put("/plugins/**", "anon");
filterMap.put("/login.do", "anon");
filterMap.put("/captcha/getCaptcha.jpg", "anon");
//文件上传
filterMap.put("/upload/**", "anon");
//视频播放
filterMap.put("/stshipinb/player.html", "anon");
filterMap.put("/stshipinb/ckplayer.html", "anon");
filterMap.put("/stshipinb/hlsplayer.html", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterMap.put("/loginOut.do", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
//出事化securityManager安全管理器并且注入realm.如果还有其他操作。注入ream要放到最后
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm());
return defaultWebSecurityManager;
}
//出事话ream对象
@Bean
public Realm myRealm() {
Realm myRealm = new Realm();
return myRealm;
}
//配置统一异常处理
@Bean
public HandlerExceptionResolver solver(){
HandlerExceptionResolver handlerExceptionResolver=new MyExceptionResolver();
return handlerExceptionResolver;
}
}
接下来就是统一异常处理。如果用户没有权限访问。那么我们就给它跳转到无权限访问页面。
package org.mintleaf.common;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* @Description: 统一异常处理
* @Param:
* @return:
* @Author: liyingying
* @Date:
*/
public class MyExceptionResolver implements HandlerExceptionResolver{
public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==============异常开始=============");
//如果是shiro无权操作,因为shiro 在操作auno等一部分不进行转发至无权限url
if(ex instanceof UnauthorizedException){
ModelAndView mv = new ModelAndView("/403.html");
return mv;
}
ex.printStackTrace();
ModelAndView mv = new ModelAndView("/403.html");
mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
return mv;
}
}
这样就实现了一套比较完整的shiro权限认证