Java基础、中级、高级、架构面试资料

Spring 整合 Shiro 实现登录认证和权限控制

JAVA herman 10896浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

前面有一篇文章写到了 Shiro 认证的相关数据库设计《开源权限框架 Shiro 整合 web 项目的数据库设计》。今天接着完成相关后台代码,分享给大家!

功能逻辑

  • 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面。
  • 对链接进行权限控制,只有当当前登录用户有这个链接访问权限才可以访问,否则跳转到指定页面。
  • 输入错误密码用户名或则用户被设置为禁止登录,返回相应json串信息。

配置 shiro 依赖包到pom.xml

<!-- shiro权限控制框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

Dao层的entity,service,mapper等我是采用mybatisplus的代码自动生成工具生成的,具备了单表的增删改查功能和分页功能,比较方便,这里我就不贴代码了。

配置 Shiro

@Configuration
public class ShiroConfig {
	/**
	 * ShiroFilterFactoryBean 处理拦截资源文件问题。
	 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
	 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
	 *
	 * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
	 * 3、部分过滤器可指定参数,如perms,roles
	 *
	 */
	@Bean
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		// 拦截器.
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		// 配置不会被拦截的链接 顺序判断
		filterChainDefinitionMap.put("/static/**", "anon");
		filterChainDefinitionMap.put("/ajaxLogin", "anon");
		// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
		filterChainDefinitionMap.put("/logout", "logout");
		filterChainDefinitionMap.put("/add", "perms[权限添加]");
		// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
		// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
		filterChainDefinitionMap.put("/**", "authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		System.out.println("Shiro拦截器工厂类注入成功");
		return shiroFilterFactoryBean;
	}
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置realm.
		securityManager.setRealm(myShiroRealm());
		return securityManager;
	}
	/**
	 * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
	 * 
	 * @return
	 */
	@Bean
	public MyShiroRealm myShiroRealm() {
		MyShiroRealm myShiroRealm = new MyShiroRealm();
		return myShiroRealm;
	}
}

登录认证实现

在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:

  • 检查提交的进行认证的令牌信息
  • 根据令牌信息从数据源(通常为数据库)中获取用户信息
  • 对用户信息进行匹配验证。
  • 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
  • 验证失败则抛出AuthenticationException异常信息。而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。

doGetAuthenticationInfo的重写

//认证信息.(身份验证) : Authentication 是用来验证用户身份
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
		AuthenticationToken authcToken) throws AuthenticationException {
	System.out.println("身份认证方法:MyShiroRealm.doGetAuthenticationInfo()");
	ShiroToken token = (ShiroToken) authcToken;
	Map<String, Object> map = new HashMap<String, Object>();
	map.put("nickname", token.getUsername());
	map.put("pswd", token.getPswd());
	SysUser user = null;
	// 从数据库获取对应用户名密码的用户
	List<SysUser> userList = sysUserService.selectByMap(map);
	if(userList.size()!=0){
		user = userList.get(0);
	}
	if (null == user) {
		throw new AccountException("帐号或密码不正确!");
	}else if(user.getStatus()==0){
		/**
		 * 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code>
		 */
		throw new DisabledAccountException("帐号已经禁止登录!");
	}else{
		//更新登录时间 last login time
		user.setLastLoginTime(new Date());
		sysUserService.updateById(user);
	}
	return new SimpleAuthenticationInfo(user, user.getPswd(), getName());
}

链接权限的实现

shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
在这个方法中主要是使用类:SimpleAuthorizationInfo
进行角色的添加和权限的添加。
authorizationInfo.addRole(role.getRole());
authorizationInfo.addStringPermission(p.getPermission());
当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(stringPermissions);
就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);
就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,
如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”);
就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
		PrincipalCollection principals) {
	System.out.println("权限认证方法:MyShiroRealm.doGetAuthenticationInfo()");
	SysUser token = (SysUser)SecurityUtils.getSubject().getPrincipal();
	String userId = token.getId();
	SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
	//根据用户ID查询角色(role),放入到Authorization里。
	/*Map<String, Object> map = new HashMap<String, Object>();
	map.put("user_id", userId);
	List<SysRole> roleList = sysRoleService.selectByMap(map);
	Set<String> roleSet = new HashSet<String>();
	for(SysRole role : roleList){
		roleSet.add(role.getType());
	}*/
	//实际开发,当前登录用户的角色和权限信息是从数据库来获取的,我这里写死是为了方便测试
	Set<String> roleSet = new HashSet<String>();
	roleSet.add("100002");
	info.setRoles(roleSet);
	//根据用户ID查询权限(permission),放入到Authorization里。
	/*List<SysPermission> permissionList = sysPermissionService.selectByMap(map);
	Set<String> permissionSet = new HashSet<String>();
	for(SysPermission Permission : permissionList){
		permissionSet.add(Permission.getName());
	}*/
	Set<String> permissionSet = new HashSet<String>();
	permissionSet.add("权限添加");
	info.setStringPermissions(permissionSet);
       return info;
}

登录 Controller 代码

//跳转到登录表单页面
@RequestMapping(value="login")
public String login() {
	return "login";
}
//ajax登录请求
@RequestMapping(value="ajaxLogin",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> submitLogin(String username, String password,Model model) {
	Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
	try {
		
		ShiroToken token = new ShiroToken(username, password);
		SecurityUtils.getSubject().login(token);
		resultMap.put("status", 200);
		resultMap.put("message", "登录成功");
	} catch (Exception e) {
		resultMap.put("status", 500);
		resultMap.put("message", e.getMessage());
	}
	return resultMap;
}

登录页面

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript"
	src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
<title>登录</title>
</head>
<body>
	错误信息:
	<h4 id="erro"></h4>
	<form>
		<p>
			账号:<input type="text" name="username" id="username" value="admin" />
		</p>
		<p>
			密码:<input type="text" name="password" id="password" value="123" />
		</p>
		<p>
			<input type="button" id="ajaxLogin" value="登录" />
		</p>
	</form>
</body>
<script>
	var username = $("#username").val();
	var password = $("#password").val();
	$("#ajaxLogin").click(function() {
		$.post("/ajaxLogin", {
			"username" : username,
			"password" : password
		}, function(result) {
			if (result.status == 200) {
				location.href = "/index";
			} else {
				$("#erro").html(result.message);
			}
		});
	});
</script>
</html>

Shiro 总结

这里由于篇幅原因,这里只是截取了部分代码,还有几个页面和动态权限控制分配的代码未整理出来,大家可以到这里链接:http://pan.baidu.com/s/1dFKJKrf 密码:4cqj下载源代码。

当然shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,接下来我会继续学习和分享,说说接下来的学习路线吧:

  • shiro+redis集成,避免每次访问有权限的链接都会去执行MyShiroRealm.doGetAuthenticationInfo()方法来查询当前用户的权限,因为实际情况中权限是不会经常变得,这样就可以使用redis进行权限的缓存。
  • 实现shiro链接权限的动态加载,之前要添加一个链接的权限,要在shiro的配置文件中添加filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”),这样很不方便管理,一种方法是将
  • 接的权限使用数据库进行加载,另一种是通过init配置文件的方式读取。
  • Shiro 自定义权限校验Filter定义,及功能实现。
  • Shiro Ajax请求权限不满足,拦截后解决方案。这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。
  • Shiro JSP标签使用。
  • Shiro 登录后跳转到最后一个访问的页面
  • 在线显示,在线用户管理(踢出登录)。
  • 登录注册密码加密传输。
  • 集成验证码。
  • 记住我的功能。关闭浏览器后还是登录状态。
  • 还有没有想到的后面再说,欢迎大家提出一些建议。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » Spring 整合 Shiro 实现登录认证和权限控制