本篇博客将进行详细介绍Shiro+Spring+SpringMVC+Mybatis+数据库整合并进行登陆认证和授权详细配置。
SSM的整合可以参考:https://blog.csdn.net/a745233700/article/details/81049763
1、导入Shiro需要的maven依赖:
org.apache.shiro
shiro-core
1.2.3
org.apache.shiro
shiro-ehcache
1.2.3
org.apache.shiro
shiro-web
1.2.3
org.apache.shiro
shiro-spring
1.2.3
或者一次性导入shiro的所有依赖:
org.apache.shiro
shiro-all
1.2.3
2、在web.xml文件中配置shiro的filter拦截器:
在与Spring整合中,shiro也通过Filter进行拦截,但是拦截后的操作权交给spring中配置的filterChainDefinitions(过滤链)处理。
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
targetBeanName
shiroFilter
shiroFilter
/*
3、配置Shiro框架的相关配置:(applicationContext-shiro.xml文件中)
/images/** = anon
/js/** = anon
/styles/** = anon
/validatecode.jsp =anon
/logout.action = logout
/** = authc
/refuse
4、配置自定义的Realm,重写认证的方法:
//自定义的Realm
public class CustomRealm extends AuthorizingRealm{
//注入service
@Autowired
private SysService sysService;
// 设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
//认证的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询用户信息
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
e1.printStackTrace();
}
// 如果查询不到返回null
if(sysUser==null){
return null;
}
// 从数据库查询到密码
String password = sysUser.getPassword();
//盐
String salt = sysUser.getSalt();
// 如果查询到,返回认证信息AuthenticationInfo
//activeUser就是用户身份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..
//根据用户id取出菜单
List menus = null;
try {
//通过service取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
e.printStackTrace();
}
//将用户菜单,设置到activeUser
activeUser.setMenus(menus);
//将activeUser设置simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
}
5、登陆:
(1)原理:使用FormAuthenticationFilter的过滤器实现。
①在用户没有认证时,请求loginUrl进行认证,用户身份和用户密码提交到loginUrl;
②FormAuthenticationFilter拦截住,并取出request中的username和password(两个参数名称可以配置)
③FormAuthenticationFilter调用realm传入一个token(即username和password);
④realm认证是根据username查询用户信息,(并在ActiveUser中存储,包括userid、usercode、username、menus等),如果查询不到,realm返回null,FormAuthenticationFilter向requset域中填充一个参数(记录了异常信息)。
(2)登陆页面:
由于FormAuthenticationFilter的用户身份和密码的input的默认值(username和password),修改页面的账号和密码 的input的名称为username和password。
(3)登陆代码实现:
@Controller
public class LoginController {
//loginUrl指定的认证提交地址
@RequestMapping("/login.action")
public String login(HttpServletRequest request) throws Exception{
//如果登陆失败,则从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName=request.getParameter("shiroLoginFailure");
//根据shiro返回的异常路径判断,抛出指定异常信息
if(exceptionClassName!=null){
if(UnknownAccountException.class.getName().equals(exceptionClassName)){
throw new CustomException("账户不存在");
}else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
throw new CustomException("用户名/密码错误");
}else{
throw new Exception();
}
}
//此方法不处理登陆成功(认证成功),如果shiro认证成功会自动跳转到上一个请求路径。
//登陆失败回到login页面:
return "login";
}
}
(4)认证拦截器:/** = authu
6、退出登陆:
在shiro中,不需要我们去实现退出登陆接口,只要去访问一个退出的url(该url是可以不存在),由LogoutFilter拦截住,清除session。
7、认证信息在页面显示:
在controller层取出用户信息,并设置在Attribute中,first.action会跳转到首页页面。
@RequestMapping("/first.action")
public String first(Model model)throws Exception{
//从shiro的session中取activeUser
Subject subject = SecurityUtils.getSubject();
//取身份信息
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
//通过model传到页面
model.addAttribute("activeUser", activeUser);
return "/first";
}
、至此,Shiro+Spring+SpringMVC+Mybatis整合实现登陆认证和退出登陆的功能就完成了。启动工程进行测试,在你没有登陆认证成功之前,访问项目中的任何路径,都会被强制跳转到loginUrl指定的路径进行登陆提交。只有登陆认证成功,才可以访问项目中的内容。
8、配置Shiro授权过滤器:使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所对应的权限。
测试流程:
(1)在applicationContext-shiro.xml中配置filter规则
/permissionTest = perms[item:query] //即访问“/permissionTest“” 路径需要“item:query”权限
(2)用户在认证通过后,请求“/permissionTest”,被PermissionsAuthorizationFilter拦截,发现需要“item:query”权限;
(3)PermissionsAuthorizationFilter 调用 doGetAuthorizationInfo 获取数据库中的正确权限并返回;
(4)PermissionsAuthorizationFilter对“item:query”和从realm中获取的权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。
9、创建授权失败页面refuse.jsp,并配置自动跳转:(记录一个在此处遇到的小问题)
如果授权失败,跳转到refuse.jsp,需要在spring容器中配置。
(1)授权失败,页面没有自动跳转:
在一开始,使用上面这种方法进行配置,但是配置完之后,发现授权失败之后,页面并没有自动跳转,而是直接抛出异常。
(2)原因:通过查看Shiro的源码:
private void applyUnauthorizedUrlIfNecessary(Filter filter) {
String unauthorizedUrl = getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
//only apply the unauthorizedUrl if they haven't explicitly configured one already:
String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
if (existingUnauthorizedUrl == null) {
authzFilter.setUnauthorizedUrl(unauthorizedUrl);
}
}
}
发现是因为shiro源代码中判断了filter是否为AuthorizationFilter,只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,所以unauthorizedUrl设置后不起作用。
(3)解决方法:异常全路径做key,错误页面做value。
/refuse
10、shiro常见的默认过滤器:
过滤器简称 |
对应的java类 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
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 |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查。
11、授权:重写自定义realm的doGetAuthorizationInfo方法,从数据库查询权限信息。
通常使用注解式授权方法和Jsp标签授权方法。
//自定义的Realm
public class CustomRealm extends AuthorizingRealm{
//注入service
@Autowired
private SysService sysService;
// 设置realm的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
//授权的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从principals获取主身份信息
//将getPrimaryPrincipal方法返回值转为真实身份类型(在上边doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中的身份类型)
ActiveUser activeUser=(ActiveUser)principals.getPrimaryPrincipal();
//根据身份信息获取权限信息:从数据库获取到权限数据
List permissionList = null;
try{
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
}catch (Exception e) {
e.printStackTrace();
}
//单独定一个集合对象
List permissions = new ArrayList();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
//将数据库中权限标签符放入集合
permissions.add(sysPermission.getPercode());
}
}
//查到权限数据,返回授权信息(要包括上边的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo =new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissions);//这里添加用户有的权限列表
simpleAuthorizationInfo.addRole("manager");//这里添加用户所拥有的角色
return simpleAuthorizationInfo;
}
}
12、授权:开启controller类的aop支持:
对系统中类的方法给用户授权,建议在controller层进行方法授权。
在springmvc.xml文件中配置:
13、授权:在controller方法中添加注解:
//权限测试方法
@RequestMapping("/permissionTest")
@RequiresPermissions("item:update")//执行此方法需要"item:update"权限
public String permissionTest(Model model){
return "/permissionTest";
}
至此,项目启动后,当访问“/permissionTest”时,如果用户没有“item:update”权限,将会自动跳转到refuse.jsp页面;如果如果用户拥有该权限,就会跳转到permissionTest.jsp页面。
14、授权:Jsp标签授权:(permissionTest.jsp)
(1)jsp页面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
(2)jsp标签授权:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
这是登陆之后 才可以看到的内容
这是未登陆的时候 才可以看到的内容
这是拥有商品管理员角色才可以看到的内容
这是没有商品管理员角色才可以看到的内容
这是拥有item:update资源权限才可以看到的内容
这是没有item:update资源权限才可以看到的内容
这是没有user:query资源权限才可以看到的内容
至此,当用户成功进入到授权成功页面时,只能看到符合自己所属权限和角色的内容。
(3)常见的shiro授权标签:
标签名称 |
标签条件(均是显示标签内容) |
|
登录之后 |
|
不在登录状态时 |
|
用户在没有RememberMe时 |
|
用户在RememberMe时 |
|
在有abc或者123角色时 |
|
拥有角色abc |
|
没有角色abc |
|
拥有权限资源abc |
|
没有abc权限资源 |
|
显示用户身份名称 |
|
显示用户身份中的属性值 |
15、授权测试:
(1)当调用controller的一个方法,由于该 方法加了@RequiresPermissions("item:query") ,shiro调用realm获取数据库中的权限信息,看"item:query"是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。
(2)当展示一个jsp页面时,页面中如果遇到
16、完成:
至此,使用Shiro整合Spring+SpringMVC+Mybatis进行登陆认证和授权就完成了,但是在这里,授权的时候存在一个问题,只要遇到注解或jsp标签的授权,都会调用realm方法查询数据库,因此需要使用缓存解决此问题。
最后,推荐几篇有关Shiro的文章:
https://www.cnblogs.com/learnhow/p/5694876.html
https://www.sojson.com/shiro#so358852059
https://www.xttblog.com/?p=1272