SSM整合Shiro进行登陆认证和授权详细配置

SSM整合Shiro进行登陆认证和授权详细配置

本篇博客将进行详细介绍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 

SSM整合Shiro进行登陆认证和授权详细配置

6、退出登陆:

在shiro中,不需要我们去实现退出登陆接口,只要去访问一个退出的url(该url是可以不存在),由LogoutFilter拦截住,清除session。

SSM整合Shiro进行登陆认证和授权详细配置

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)授权失败,页面没有自动跳转:

SSM整合Shiro进行登陆认证和授权详细配置

 在一开始,使用上面这种方法进行配置,但是配置完之后,发现授权失败之后,页面并没有自动跳转,而是直接抛出异常。

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账号...

(0)


相关推荐

  • centos7和8区别_centos7进不去图形界面

    centos7和8区别_centos7进不去图形界面目前国内各大云服务器的默认centos系统版本还是7,vultr,centos只有8了官网,下载,默认也是8,作为本地主机玩的服务器,还是试一下centos8国外下载之前版本,下载链接:官网默

  • Java面对对象编程(超详细)

    Java面对对象编程(超详细)1、成员变量和成员方法成员变量(又叫属性,字段)成员方法2、类和对象的内存分配机制Java内存的结构分析栈:一般存放基本数据类型(局部变量)堆:存放对象(Catcat,数组等)

  • 彩色图和深度图转点云

    彩色图和深度图转点云环境:windows10、VS2013、opencv2.49、openNi、PCL1.8opencv环境搭建参考https://www.cnblogs.com/cuteshongshong/p/4057193.htmlhttps://blog.csdn.net/u013105549/article/details/50493069PCL1.8+openNi搭建参考https://blog.cs…

  • flume之退避算法backoff algorithm[通俗易懂]

    flume之退避算法backoff algorithm[通俗易懂]flume之退避算法backoffalgorithm什么是退避算法:Inasinglechannelcontentionbasedmediumaccesscontrol(MAC)protocols,whenevermorethanonestationornodetriestoaccessthemediumatthesameinstantof…

  • 不装了、摊牌了,我们要搞事情

    不装了、摊牌了,我们要搞事情一起在技术的海洋里狗刨~

  • nginx的负载均衡算法_redis负载均衡

    nginx的负载均衡算法_redis负载均衡1、轮询     就是按照时间顺序分配下一个请求的服务器。2、权值轮询     给每一个服务器加上权值,权值和分配的请求成正比,比较适应于服务器配置不均的情况。3、ip哈希     根据ip的哈希值分配,这样每个ip每次访问的服务器都相同,这样session的处理会容易些。4、响应时间动态分配   根据请求的响应时间来分配,时间越短,说明处理能力较强,这样会…

    2022年10月12日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号