SpringBoot+Shiro学习之密码加密和登录失败次数限制
2018-02-07 22:38
791 查看
学习任务目标
用户必须要登陆之后才能访问定义链接,否则跳转到登录页面。对链接进行权限控制,只有当当前登录用户有这个链接访问权限才可以访问,否则跳转到指定页面。
输入错误密码用户名或则用户被设置为静止登录,返回相应json串信息。
我是用的是之前搭建的一个springboot+mybatisplus+jsp的一个基础框架。在这之上进行shiro的整合。需要的同学可以去我的码云下载。个人博客:http://z77z.oschina.io/此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus
导入shiro依赖包到pom.xml
123456 | <!-- shiro权限控制框架 --><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version></dependency> |
采用RBAC模式建立数据库
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 | /*表结构插入*/DROP TABLE IF EXISTS `u_permission`;CREATE TABLE `u_permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(256) DEFAULT NULL COMMENT 'url地址', `name` varchar(64) DEFAULT NULL COMMENT 'url描述', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;/*Table structure for table `u_role` */DROP TABLE IF EXISTS `u_role`;CREATE TABLE `u_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL COMMENT '角色名称', `type` varchar(10) DEFAULT NULL COMMENT '角色类型', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;/*Table structure for table `u_role_permission` */DROP TABLE IF EXISTS `u_role_permission`;CREATE TABLE `u_role_permission` ( `rid` bigint(20) DEFAULT NULL COMMENT '角色ID', `pid` bigint(20) DEFAULT NULL COMMENT '权限ID') ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Table structure for table `u_user` */DROP TABLE IF EXISTS `u_user`;CREATE TABLE `u_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称', `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号', `pswd` varchar(32) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;/*Table structure for table `u_user_role` */DROP TABLE IF EXISTS `u_user_role`;CREATE TABLE `u_user_role` ( `uid` bigint(20) DEFAULT NULL COMMENT '用户ID', `rid` bigint(20) DEFAULT NULL COMMENT '角色ID') ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Dao层代码的编写
Dao层的entity,service,mapper等我是采用mybatisplus的代码自动生成工具生成的,具备了单表的增删改查功能和分页功能,比较方便,这里我就不贴代码了。配置shiro
ShiroConfig.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 | /** * @author 作者 z77z * @date 创建时间:2017年2月10日 下午1:16:38 * */@Configurationpublic 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)方法。该方法主要执行以下操作:1、检查提交的进行认证的令牌信息2、根据令牌信息从数据源(通常为数据库)中获取用户信息3、对用户信息进行匹配验证。4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。5、验证失败则抛出AuthenticationException异常信息。而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo
(),重写获取用户信息的方法。doGetAuthenticationInfo的重写
123456789101112131415161718192021222324252627282930313233343536 | /*** 认证信息.(身份验证) : Authentication 是用来验证用户身份 * * @param token * @return * @throws AuthenticationException */@Overrideprotected 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”这个角色才可以访问。
123456789101112131415161718192021222324252627282930313233 | /*** 授权 */@Overrideprotected 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;} |
编写web层的代码
登录页面:controller1234567891011121314151617181920212223242526272829 | //跳转到登录表单页面@RequestMapping(value="login")public String login() { return "login";}/** * ajax登录请求 * @param username * @param password * @return */@RequestMapping(value="ajaxLogin",method=RequestMethod.POST)@ResponseBodypublic 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;} |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 | <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><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> |
主页页面
controller12345678910111213141516171819202122 | //跳转到主页@RequestMapping(value="index")public String index() { return "index";}/*** 退出 * @return */@RequestMapping(value="logout",method =RequestMethod.GET)@ResponseBodypublic Map<String,Object> logout(){ Map<String, Object> resultMap = new LinkedHashMap<String, Object>(); try { //退出 SecurityUtils.getSubject().logout(); } catch (Exception e) { System.err.println(e.getMessage()); } return resultMap;} |
1234567891011121314151617181920212223242526 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;%><!DOCTYPE html><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>Insert title here</title></head><body> helloJsp <input type="button" id="logout" value="退出登录" /></body><script type="text/javascript"> $("#logout").click(function(){ location.href="/logout"; });</script></html> |
1234 | @RequestMapping(value="add")public String add() { return "add";} |
1234567891011121314151617181920 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;%><!DOCTYPE html><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>Insert title here</title></head><body>具有添加权限</body></html> |
测试
任务一编写好后就可以启动程序,访问index页面,由于没有登录就会跳转到login页面。登录之后就会跳转到index页面,点击退出登录后,有直接在浏览器中输入index页面访问,又会跳转到login页面上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()这个方法,也就是登录认证的方法。任务二登录之后访问add页面成功访问,在shiro配置文件中改变add的访问权限为filterChainDefinitionMap.put(“/add”,”perms[权限删除]”);再重新启动程序,登录后访问,会重定向到/403页面,由于没有编写403页面,报404错误。上面这些操作,会触发权限认证方法:MyShiroRealm.doGetAuthorizationInfo(),每访问一次就会触发一次。任务三输入错误的用户名或则密码,返回“帐号或密码不正确!”的错误信息,在数据库中把一个用户的状态改为被禁用,再登陆,提示“帐号已经禁止登录!”的错误信息上面的操作,是在MyShiroRealm.doGetAuthenticationInfo()登录认证的方法中实现的,通过查询数据库判断当前登录用户是否被禁用,具体可以去看源码。总结
当然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 登录后跳转到最后一个访问的页面
在线显示,在线用户管理(踢出登录)。
登录注册密码加密传输。
集成验证码。
记住我的功能。关闭浏览器后还是登录状态。
相关文章推荐
- SpringBoot+Shiro学习之密码加密和登录失败次数限制示例
- SpringBoot+Shiro学习之密码加密和登录失败次数限制
- SpringBoot+Shiro学习之密码加密和登录失败次数限制
- springboot整合shiro登录失败次数限制功能的实现代码
- SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)
- Shiro+SpringMVC 实现更安全的登录(加密匹配&登录失败超次数锁定帐号)
- Spring Boot +Shiro 验证码Filter和限制密码错误次数
- SpringBoot+shiro整合学习之登录认证和权限控制
- SpringBoot+shiro整合学习之登录认证和权限控制
- Spring Boot Security 学习笔记-根据登陆人动态配置权限-密码加密验证
- springmvc+mybatis+shiro MD5加密匹配&登录失败超次数锁定帐号
- [置顶] 【三】Springboot+Redis实现密码次数限制
- SpringBoot+shiro整合学习之登录认证和权限控制
- 【Java】SpringMVC项目利用Shiro设置固定时间内密码登录重试次数限制
- SpringBoot+shiro整合学习之登录认证和权限控制
- SpringBoot+shiro整合学习之登录认证和权限控制
- 限定登录失败次数,超过指定次数就限制登录一段时间
- SpringBoot整合shiro之盐值加密详解
- 在SSM中使用shiro实现登录验证(附密码加密)
- springboot学习笔记2(拦截器,redis,授权登录,读取yml配置文件)