前面,已经将几个配置全列出来了,jdbc.properties文件内容如下:
url=jdbc:mysql://127.0.0.1:3306/test
driver=com.mysql.jdbc.Driver
uid=mysql
password=mysql
Shiro需要使用数据库进行权限验证,因此,对用户权限信息表,也需要设计一下,简单的把建表脚本内贴出来
/* mysql 建表脚本
tbuser 用户信息表
tbrole 角色信息表
tbpermission 权限资源表
tbuser_role 用户与角色关联表
tbrole_permission 角色与权限资源关联表
定义五张表后,方便应用使用Shiro通过JDBCRealm进行权限管理
表设计的有些简单,可以在此基础上进行扩充
*/
/* 用户定义 */
create table tbuser(
userid char(8) primary key not null,
username varchar(20) not null,
usertype char(1) not null,
email varchar(50),
mobile varchar(50),
tel varchar(50),
hometel varchar(50),
password varbinary(128),
status char(1),
lastModify timestamp default now()
);
/** 角色定义 **/
create table tbrole(
roleid int unsigned AUTO_INCREMENT not null,
rolename varchar(64) not null,
rolestr varchar(20) not null,
create_time datetime DEFAULT NULL,
update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP,
primary key(roleid)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
create unique index tbroleipx on tbrole (rolestr);
/** 定义角色权限表 **/
CREATE TABLE tbpermission (
permissionid int unsigned NOT NULL AUTO_INCREMENT,
permissionstr varchar(64) not null,
permissionname varchar(64) DEFAULT NULL,
create_time datetime DEFAULT NULL,
update_time timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (permissionid)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
create unique index tbpermissionipx on tbpermission (permissionstr);
/* 定义用户与角色关系表 */
CREATE TABLE tbuser_role (
id int unsigned NOT NULL AUTO_INCREMENT,
userid char(8) not null,
roleid int unsigned DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/* 定义色色与权限关系 */
CREATE TABLE tbrole_permission (
id int unsigned NOT NULL AUTO_INCREMENT,
roleid int unsigned not null,
permissionid int unsigned not null,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/* 增加 操作员 */
insert into tbuser (userid,username,usertype) values('111','username1', '1');
select * from tbuser;
/* 增加角色 */
insert into tbrole (rolename,rolestr,create_time,update_time) values('管理员','0011', now(),now() );
select * from tbrole;
/* 定义资源权限 */
insert into tbpermission (permissionname,permissionstr,create_time,update_time) values('增加操作员','user:add', now(),now() );
select * from tbpermission;
/* 增加用户与角色的关联 */
insert into tbuser_role (userid,roleid) values( '111',1);
/* 增加角色与权限的关联 */
insert into tbrole_permission(roleid,permissionid) values(1,1);
/* 查询用户口令 */
select password from tbuser where userid='111';
/* 查询用户角色列表 */
select b.rolestr from tbuser_role a join tbrole b on a.roleid=b.roleid where a.userid='111';
/* 查询用户有哪些资源权限列表 */
select e.permissionstr from tbuser_role a join tbrole c on a.roleid=c.roleid join tbrole_permission d on c.roleid=d.roleid join tbpermission e on d.permissionid=e.permissionid where a.userid='111';
那么,数据库方面的各表的操作代码,可以通过MybatisGenerator自动生成,另外还需要手工写两个获取操作员角色和获取操作员权限的SQL DAO。 在 TbuserMapper.java文件中增加这两个接口方法
List<String> findRoles(String userid);
List<String> findPermissions(String userid);
在 TbuserMapper.xml文件中,增加两个SQL查询方法,好了。这样就完成了如何获取用户角色列表和用户权限列表的功能了,非常方便吧,如何使用等后面再讲。
<select id="findRoles" parameterType="java.lang.String" resultType="java.lang.String">
select b.rolestr from tbuser_role a
join tbrole b on a.roleid=b.roleid
where a.userid = #{userid,jdbcType=CHAR}
</select>
<select id="findPermissions" parameterType="java.lang.String" resultType="java.lang.String">
select e.permissionstr from tbuser_role a
join tbrole c on a.roleid=c.roleid
join tbrole_permission d on c.roleid=d.roleid
join tbpermission e on d.permissionid=e.permissionid
where a.userid = #{userid,jdbcType=CHAR}
</select>
使用Freemarker作视图展示,而不使用JSP的好处可以网上找,但使用的人不太多。 在springmvc 的配置中,使用了自定义的 FreemarkerView 类,这里也贴下代码
public class FreemarkerView extends org.springframework.web.servlet.view.freemarker.FreeMarkerView{
@Override
protected void exposeHelpers(Map<String, Object> model, HttpServletRequest request) throws Exception {
super.exposeHelpers(model, request);
//就是在Spring返回的Model中,增加一个basedir
model.put("basedir", request.getContextPath());
}
}
在 WEB-INF/ftl 目录下新建一个 index.ftl文件
<html>
<head>
<!-- 这里省略一些 meta 等内容 -->
<! -- 这里就使用到了 FreemarkerView 中添加的 basedir -->
<link type="text/css" rel="stylesheet" href="${basedir}/resource/bootstrap/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
${currentUser!}
这是${pagename!}页面
</div>
</body>
</html>
页面显示做好了。现在做一个 Controller ,可以显示这个 ftl ,很简单的类,使用了 Shiro注解和SpringMVC的注解
@Controller
public class hello {
@RequiresPermissions("user:add")
@RequestMapping("/user/info")
public ModelAndView userinfo(){
ModelAndView mv=new ModelAndView("index");
mv.addObject("pagename","/user/info页面 调用显示 index.ftl ");
return mv;
}
}
最后,要把Shiro实现的两个类实现了,就大功告成了。 MyRealM 扩展 AuthorizingRealm ,实现其中的关键的2个函数 doGetAuthorizationInfo 的功能是通过参数principals用户信息,获取数据库中的用户的Role 角色 和 permission 权限。
doGetAuthenticationInfo 的功能是通过用户登录时提供的 AuthenticationToken 获取 数据库中的用户名和口令,但是它不做验证,验证工作由另一个类实现完成。
public class MyRealM extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(MyRealM.class);
@Autowired
private TbuserMapper usermapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(new HashSet<>());
authorizationInfo.setStringPermissions(new HashSet<>());
//就是这里,使用了Mybatis的 TbuserMapper的代码,手工添加的方法
authorizationInfo.addRoles(usermapper.findRoles(username));
authorizationInfo.addStringPermissions(usermapper.findPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authtoken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authtoken;
//就是这里,使用了Mybatis的 TbuserMapper的代码
Tbuser user = usermapper.selectByPrimaryKey(token.getUsername());
if(user == null )
throw new UnknownAccountException();
AuthenticationInfo auth = new SimpleAuthenticationInfo(token.getPrincipal(), user.getPassword() , getName());
//返回成功的登录结果
return auth;
}
private void setSession(Object key, Object value) {
Subject currentUser = SecurityUtils.getSubject();
if (null != currentUser) {
Session session = currentUser.getSession();
if (null != session) {
session.setAttribute(key, value);
}
}
}
}
最后一个用户口令密码验证的类,没有做验证代码,直接返回了true,但是log.info 信息已经完成说明了你该如何去实现验证
public class MyCredentialsMatcher extends HashedCredentialsMatcher {
private static final Logger log=LoggerFactory.getLogger("matcher");
public MyCredentialsMatcher() {
}
/**
* 一个用户信息校验的方法,将登录信息 与 RealM提供的后台信息进行核对,核对成功就返回True,否则就抛出异常
* @param token 登录信息,包括用户身份信息(username) 和证明信息 (password)
* @param uinfo RealM从 数据库 或 LDAP 等后台服务器处获取的信息
* @return return true if matched, else false.
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo uinfo) {
UsernamePasswordToken utoken=(UsernamePasswordToken)token;
SimpleAuthenticationInfo info=(SimpleAuthenticationInfo)uinfo;
log.info("登录用户的信息:{},{}",utoken.getUsername(),new String(utoken.getPassword()));
log.info("验证的用户名:{}",(String)info.getPrincipals().getPrimaryPrincipal() );
log.info("验证的口令:{}", info.getCredentials() );
return true;
}
}
最后,一个实现用户登录 的Cotroller
@RequestMapping(value = "/dologin")
public String doLogin(HttpServletRequest request, Model model) {
String msg = "";
String userName = request.getParameter("userName");
String password = request.getParameter("password");
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
token.setRememberMe(true);
Subject subject = SecurityUtils.getSubject();
try {
/* 此处调用 RealM 的 doGetAuthenticationInfo 方法 */
subject.login(token);
if (subject.isAuthenticated()) {
//登录成功后执行
subject.getSession().setAttribute("currentUser", subject.getPrincipal().toString());
return "redirect:/index.do";
} else {
//未登录或登录失败
return "login";
}
} catch (IncorrectCredentialsException e) {
msg = "登录密码错误. Password for account " + token.getPrincipal() + " was incorrect.";
model.addAttribute("message", msg);
System.out.println(msg);
} catch (ExcessiveAttemptsException e) {
msg = "登录失败次数过多";
model.addAttribute("message", msg);
System.out.println(msg);
} catch (LockedAccountException e) {
msg = "帐号已被锁定. The account for username " + token.getPrincipal() + " was locked.";
model.addAttribute("message", msg);
System.out.println(msg);
} catch (DisabledAccountException e) {
msg = "帐号已被禁用. The account for username " + token.getPrincipal() + " was disabled.";
model.addAttribute("message", msg);
System.out.println(msg);
} catch (ExpiredCredentialsException e) {
msg = "帐号已过期. the account for username " + token.getPrincipal() + " was expired.";
model.addAttribute("message", msg);
System.out.println(msg);
} catch (UnknownAccountException e) {
msg = "帐号不存在. There is no user with username of " + token.getPrincipal();
model.addAttribute("message", msg);
System.out.println(msg);
} catch (UnauthorizedException e) {
msg = "您没有得到相应的授权!" + e.getMessage();
model.addAttribute("message", msg);
System.out.println(msg);
}
return "login";
}