本篇博客将进行详细介绍Shiro+Spring+SpringMVC+Mybatis+数据库整合并进行登陆认证和授权详细配置。
SSM的整合可以参考:https://blog.csdn.net/a745233700/article/details/81049763
下面主要介绍Shiro与SSM的整合和使用:
1、导入Shiro需要的maven依赖:
<!-- shiro -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
或者一次性导入shiro的所有依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
2、在web.xml文件中配置shiro的filter拦截器:
在与Spring整合中,shiro也通过Filter进行拦截,但是拦截后的操作权交给spring中配置的filterChainDefinitions(过滤链)处理。
<!-- shiro的filter -->
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、配置Shiro框架的相关配置:(applicationContext-shiro.xml文件中)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- id属性值要对应 web.xml中shiro的filter对应的bean -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action"></property>
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功会默认跳转到上一个请求路径 -->
<!-- <property name="successUrl" value="/first.action"></property> -->
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面,这个位置会拦截不到,下面有给出解决方法 -->
<!-- <property name="unauthorizedUrl" value="/refuse.jsp"></property> -->
<!-- 过滤器定义,从上到下执行,一般将/**放在最下面 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon
/validatecode.jsp =anon
<!-- 请求logout.action地址,shiro去清除session -->
/logout.action = logout
<!-- /**=authc 所有的url都必须通过认证才可以访问 -->
/** = authc
<!-- /**=anon 所有的url都可以匿名访问,不能配置在最后一排,不然所有的请求都不会拦截 -->
</value>
</property>
</bean>
<!-- 解决shiro配置的没有权限访问时,unauthorizedUrl不跳转到指定路径的问题 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
</props>
</property>
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm"></property>
</bean>
<!-- 配置自定义Realm -->
<bean id="customRealm" class="com.zwp.shiro.CustomRealm">
<!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密算法 -->
<property name="hashAlgorithmName" value="md5"></property>
<!-- 迭代次数 -->
<property name="hashIterations" value="1"></property>
</bean>
</beans>
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<SysPermission> 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。
<!-- 解决shiro配置的没有权限访问时,unauthorizedUrl不跳转到指定路径的问题 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
</props>
</property>
</bean>
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<SysPermission> permissionList = null;
try{
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
}catch (Exception e) {
e.printStackTrace();
}
//单独定一个集合对象
List<String> permissions = new ArrayList<String>();
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文件中配置:
<!-- 使用注解驱动:自动配置处理器映射器与处理器适配器 -->
<mvc:annotation-driven />
<!-- 开启aop,对类代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 开启shiro注解支持 -->
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
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" %>
<html>
<body>
<shiro:authenticated>
这是登陆之后 才可以看到的内容
</shiro:authenticated><br/>
<shiro:notAuthenticated>
这是未登陆的时候 才可以看到的内容
</shiro:notAuthenticated><br/>
<shiro:hasRole name="manager">
这是拥有商品管理员角色才可以看到的内容
</shiro:hasRole><br/>
<shiro:lacksRole name="manager">
这是没有商品管理员角色才可以看到的内容
</shiro:lacksRole><br/>
<shiro:hasPermission name="item:update">
这是拥有item:update资源权限才可以看到的内容
</shiro:hasPermission><br/>
<shiro:lacksPermission name="item:update">
这是没有item:update资源权限才可以看到的内容
</shiro:lacksPermission><br/>
<shiro:lacksPermission name="user:query">
这是没有user:query资源权限才可以看到的内容
</shiro:lacksPermission><br/>
</body>
</html>
至此,当用户成功进入到授权成功页面时,只能看到符合自己所属权限和角色的内容。
(3)常见的shiro授权标签:
标签名称 |
标签条件(均是显示标签内容) |
<shiro:authenticated> |
登录之后 |
<shiro:notAuthenticated> |
不在登录状态时 |
<shiro:guest> |
用户在没有RememberMe时 |
<shiro:user> |
用户在RememberMe时 |
<shiro:hasAnyRoles name=”abc,123″ > |
在有abc或者123角色时 |
<shiro:hasRole name=”abc”> |
拥有角色abc |
<shiro:lacksRole name=”abc”> |
没有角色abc |
<shiro:hasPermission name=”abc”> |
拥有权限资源abc |
<shiro:lacksPermission name=”abc”> |
没有abc权限资源 |
<shiro:principal> |
显示用户身份名称 |
<shiro:principal property=”username”/> |
显示用户身份中的属性值 |
15、授权测试:
(1)当调用controller的一个方法,由于该 方法加了@RequiresPermissions(“item:query”) ,shiro调用realm获取数据库中的权限信息,看”item:query”是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。
(2)当展示一个jsp页面时,页面中如果遇到<shiro:hasPermission name=”item:update”>,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。
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
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/114709.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...