在之前我们用的都是直接读取ini文件的数据,显然这种方式不现实,仅针对数据量较小的时候的测试,这一篇来介绍利用自定义的Realm读取数据库的数据。
第一步,根据用户拥有角色和权限建立数据库:
我们按照上一篇的用户关系来建数据库,一个角色可以对应多个用户,一个角色也可以对应多个权限。所以我们要建3张表:
t_user(id,userName,password,roleId)
t_roles(id,roleName)
t_permission(id,permission,roleId)
每个用户对应一个roleId外键,关联t_role表,这样就实现了一个角色对应多个用户,每一个权限对应一个roleId外键,关联t_role表,这样实现了一个角色对应多个权限。
按照之前的数据填好表。
第二步:自定义Realm
新建MyRealm类,继承AuthorizingRealm,实现里面的doGetAuthenticationInfo,doGetAuthorizationInfo方法,
首先实现doGetAuthenticationInfo方法:获取token里面的用户名,然后去数据库找看是否有这一个用户名,如果没有直接抛出异常,如果有,会new 一个SimpleAuthenticationInfo对象将从数据库找到的用户名和密码传进去,看是否和token里面的密码一样,如果不一样,也会抛出异常,如果一样则身份验证通过,
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName= (String)token.getPrincipal();//获取身份信息
//根据用户名查找
Connection con = null;
try {
con = dbUtil.getCon();
User user = userDao.getByUserName(con, userName);
if(user!=null) {
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),"xx");
return authenticationInfo;
}
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
然后调用doGetAuthorizationInfo方法去把拥有的权限和角色获取到,通过用户名从数据库里找到角色和权限分别存储在set集合里,new 一个authorizationInfo对象,调用setRoles和setStringPermissions方法将从数据库里获得的set集合放入,返回authorizationInfo即可。
/**
* 对已经身份认证过的用户授予权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Connection con = null;
try {
con = dbUtil.getCon();
authorizationInfo.setRoles(userDao.getRole(con,userName));
authorizationInfo.setStringPermissions(userDao.getPermissions(con,userName));
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return authorizationInfo;
}
数据库的操作就不一一说明了,很简单。
第三步:配置ini文件:
[main]
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=unpermitted.jsp
MyRealm=com.java.realm.MyRealm
securityManager.realm=$MyRealm
[urls]
/login=anon
/admin=authc
/student=roles[teacher]
/delete=perms["user:delete"]
把用户,角色,权限信息去掉,然后换成自定义的realm。
第四步:测试。
没有问题。
分析一遍执行过程:
首先,从登录界面开始说,用户输入用户名和密码,点击登录,请求/login请求,从web.xml中找到对应映射的servlet类,找到了LoginServlet,执行里面的dopost方法,这个方法将用户输入的用户名和密码获取,并利用shiro得到当前用户subject,并将用户名密码封装成一个token,当subject当前用户执行.login方法时,把token带着就执行刚才写的doGetAuthenticationInfo方法,进行身份验证,具体验证过程上面已经介绍了,当身份验证通过后,有调用doGetAuthorizationInfo方法进行角色和权限的授权,并进行角色和权限的验证,执行完login ,接着执行我们自己写的转发到sueecss界面。当登录后,用户再请求时,shiro会再进行一遍角色和权限的授权,并再进行一次角色和权限的验证,因此说shiro安全性很高。
loginServlet的dopost方法:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login dopost");
String userName = req.getParameter("userName");
String password = req.getParameter("password");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
subject.login(token);
resp.sendRedirect("success.jsp");
}catch(Exception e) {
e.printStackTrace();
req.setAttribute("errorMsg", "用户名或密码错误");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}