学习任务目标
用户必须要登陆之后才能访问定义链接,否则跳转到登录页面。
对链接进行权限控制,只有当当前登录用户有这个链接访问权限才可以访问,否则跳转到指定页面。
输入错误密码用户名或则用户被设置为静止登录,返回相应json串信息。
个人博客:http://z77z.oschina.io/
此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus
导入shiro依赖包到pom.xml
<!-- shiro权限控制框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
采用RBAC模式建立数据库
/*表结构插入*/
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层代码的编写
配置shiro
ShiroConfig.java
/**
* @author 作者 z77z
* @date 创建时间:2017年2月10日 下午1:16:38
*
*/
@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;
}
}
登录认证实现
doGetAuthenticationInfo的重写
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
* @param token
* @return
* @throws AuthenticationException
*/
@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());
}
链接权限的实现
/**
* 授权
*/
@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;
}
编写web层的代码
登录页面:
controller
//跳转到登录表单页面
@RequestMapping(value="login")
public String login() {
return "login";
}
/**
* ajax登录请求
* @param username
* @param password
* @return
*/
@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;
}
jsp
<%@ 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>
主页页面
controller
//跳转到主页
@RequestMapping(value="index")
public String index() {
return "index";
}
/**
* 退出
* @return
*/
@RequestMapping(value="logout",method =RequestMethod.GET)
@ResponseBody
public 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;
}
jsp
<%@ 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>
添加操作页面
controller
@RequestMapping(value="add")
public String add() {
return "add";
}
jsp
<%@ 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>
测试
任务一
任务二
任务三
总结
当然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 登录后跳转到最后一个访问的页面
在线显示,在线用户管理(踢出登录)。
登录注册密码加密传输。
集成验证码。
记住我的功能。关闭浏览器后还是登录状态。
还有没有想到的后面再说,欢迎大家提出一些建议。