【Shiro】Apache Shiro架构之身份认证(Authentication) 
【Shiro】Apache Shiro架构之权限认证(Authorization) 
【Shiro】Apache Shiro架构之集成web 
【Shiro】Apache Shiro架构之实际运用(整合到Spring中)

 之前写的博客里都是使用.ini文件来获取信息的,包括用户信息,角色信息,权限信息等。进入系统时,都是从.ini文件这读取进入的。实际中除非这个系统特别特别简单,否则一般都不是这样干的,这些信息都是需要在数据库中进行维护的,所以就需要用到自定义realm了。

写在前面:这篇博文是基于上一篇Shiro集成web基础之上修改的。

1. 数据库建表

  首先在数据库中新建三个表:t_user,t_role和t_permission,分别存储用户信息,角色信息和权限信息,建表语句如下:

CREATE TABLE `t_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`rolename` varchar(20) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(20) NOT NULL COMMENT '密码',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role表',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`permissionname` varchar(50) NOT NULL COMMENT '权限名',
`role_id` int(11) DEFAULT NULL COMMENT '外键关联role',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

 每个表中我添加了一些测试数据,如下: 

2. 自定义realm

  自定义realm中需要操作数据库,所有首先得先写一个dao,使用的是原始的jdbc,主要是下面的自定义realm。

public class UserDao {

    //根据用户名查找用户
public User getByUsername(Connection conn, String username) throws Exception { User resultUser = null;
String sql = "select * from t_user where username=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
resultUser = new User();
resultUser.setId(rs.getInt("id"));
resultUser.setUsername(rs.getString("username"));
resultUser.setPassword(rs.getString("password"));
}
return resultUser;
} //根据用户名查找改用户所拥有的角色
public Set<String> getRoles(Connection conn, String username) throws Exception {
Set<String> roles = new HashSet<String>();
String sql = "select * from t_user u, t_role r where u.role_id=r.id and u.username=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
roles.add(rs.getString("rolename"));
}
return roles;
} //根据用户名查找该用户角色所拥有的权限
public Set<String> getPerms(Connection conn, String username) throws Exception {
Set<String> perms = new HashSet<String>();
String sql = "select * from t_user u, t_role r, t_permission p where u.role_id=r.id and p.role_id=r.id and u.username=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
perms.add(rs.getString("permissionname"));
}
return perms;
}
}

 有了dao了,接下来就可以写自定义的realm了,自定义realm需要继承AuthorizingRealm类,因为该类封装了很多方法,它也是一步步继承自Realm类的,继承了AuthorizingRealm类后,需要重写两个方法:

doGetAuthenticationInfo()方法:用来验证当前登录的用户,获取认证信息 
doGetAuthorizationInfo()方法:用来为当前登陆成功的用户授予权限和角色(已经登陆成功了)

  下面来看一下具体的实现:

public class MyRealm extends AuthorizingRealm {

    private UserDao userDao = new UserDao();

    // 为当前登陆成功的用户授予权限和角色,已经登陆成功了
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal(); //获取用户名
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Connection conn = null;
try {
conn = DbUtil.getConnection();
authorizationInfo.setRoles(userDao.getRoles(conn, username)); //设置角色
authorizationInfo.setStringPermissions(userDao.getPerms(conn, username)); //设置权限
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DbUtil.closeConnection(conn);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return authorizationInfo;
} // 验证当前登录的用户,获取认证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal(); // 获取用户名
Connection conn = null;
try {
conn = DbUtil.getConnection();
User user = userDao.getByUsername(conn, username); // 仅仅是根据用户名查出的用户信息,不涉及到密码
if (user != null) {
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(
user.getUsername(), user.getPassword(), "myrealm");
return authcInfo;
} else {
return null;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DbUtil.closeConnection(conn);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
}

 从上面两个方法中可以看出:验证身份的时候是根据用户输入的用户名先从数据库中查出该用户名对应的用户,这时候并没有涉及到密码,也就是说到这一步的时候,即使用户输入的密码不对,也是可以查出来该用户的,然后将该用户的正确信息封装到authcInfo 中返回给Shiro,接下来就是Shiro的事了,它会根据这里面的真实信息与用户前台输入的用户名和密码进行校验, 这个时候也要校验密码了,如果校验通过就让用户登录,否则跳转到指定页面。同理,权限验证的时候也是先根据用户名获取与该用户名有关的角色和权限,然后封装到authorizationInfo中返回给Shiro。

3. 修改ini文件

  在该配置文件中,[users]和[roles]的信息就可以删掉了,因为这些信息都是从数据库中维护的,另外还要在文件中指定我们自定义的realm的完全限定名,并且指定securityManager的realm使用我们自定义的realm,如下:

[main]
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=/unauthorized.jsp #定义自己的realm
myRealm=demo.shiro.realm.MyRealm
securityManager.realms=$myRealm #定义请求的地址需要做什么验证
[urls]
/login=anon
/admin=authc
/student=roles[teacher]
/teacher=perms["user:create"]

dbutil.java

package demo.shiro.servlet;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties; public class DbUtil { private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
//static语句块初始化字段信息 static{
try{
//读取db.properties文件中的数据库连接信息 InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties"); Properties prop = new Properties(); prop.load(in); //获取数据库连接驱动
driver = prop.getProperty("driver");
//获取数据库连接URL地址
url = prop.getProperty("url");
//获取数据库连接用户名
username = prop.getProperty("username");
//获取数据库连接密码
password = prop.getProperty("password");
//加载数据库驱动
Class.forName(driver); }catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
} /**
* @Method: getConnection
* @Description: 获取数据库连接对象
* @Anthor:
*
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username,password);
} /**
* @Method: release
* @Description: 释放资源,
* 要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
} if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
try {
System.out.println(new DbUtil().getConnection().getClass().getName());
} catch (SQLException e) { e.printStackTrace();
}
} }

user.java

public class User {

    private Integer id;
private String username;
private String password;
//省略get set }

这样我们自定义的realm就搞定了,根据配置文件,当我们请求…/admin的时候会进行身份认证,所以会进入LoginServlet中,当调用currentUser.login(token);的时候,就会进入我们自定义的realm中的doGetAuthenticationInfo方法进行身份初始化,然后交给Shiro去验证。当我们请求…./student的时候,也会先进行身份验证,就是上面的过程,然后验证通过,当我们再次请求…/student的时候,就会进入我们自定义的realm中的doGetAuthorizationInfo方法进行权限的初始化,然后交给Shiro去验证。 
  下一篇博文我将总结一下如何将Shiro运用到实际项目中,即如何将Shiro整合到spring中。

【Shiro】Apache Shiro架构之自定义realm的更多相关文章

  1. Shiro第二篇【介绍Shiro、认证流程、自定义realm、自定义realm支持md5】

    什么是Shiro shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. spring中有spring security (原名Acegi),是一个权限框架,它和sp ...

  2. Apache Shiro 使用手册(一)Shiro架构介绍 - kdboy - ITeye技术网站

    转载 原文地址 http://kdboy.iteye.com/blog/1154644 一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理 ...

  3. Apache Shiro 使用手册(一)Shiro架构介绍

    一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能:  认证 - 用户身份识别,常被称为用户"登录": 授权 - ...

  4. 【Shiro】Apache Shiro架构之集成web

    Shiro系列文章: [Shiro]Apache Shiro架构之身份认证(Authentication) [Shiro]Apache Shiro架构之权限认证(Authorization) [Shi ...

  5. 【Shiro】Apache Shiro架构之权限认证(Authorization)

    Shiro系列文章: [Shiro]Apache Shiro架构之身份认证(Authentication) [Shiro]Apache Shiro架构之集成web [Shiro]Apache Shir ...

  6. 【Shiro】Apache Shiro架构之身份认证(Authentication)

    Shiro系列文章: [Shiro]Apache Shiro架构之权限认证(Authorization) [Shiro]Apache Shiro架构之集成web [Shiro]Apache Shiro ...

  7. Apache Shiro 使用手册(一)Shiro架构介绍(转发:http://kdboy.iteye.com/blog/1154644#bc2399255)

    一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”: 授权 - 访问控制: 密码加密 ...

  8. Apache Shiro 手册

    (一)Shiro架构介绍 一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户"登录 ...

  9. Apache Shiro 使用手册---转载

    原文地址:http://www.360doc.com/content/12/0104/13/834950_177177202.shtml (一)Shiro架构介绍 一.什么是Shiro Apache ...

随机推荐

  1. JDK源码学习笔记——LinkedList

    一.类定义 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E& ...

  2. Java高级架构师(一)第38节:Nginx的负载均衡模块

    负载均衡: 1.热备:如果你有2台服务器,当一台服务器发生事故时,才启用第二台服务器给提供服务.服务器处理请求的顺序:AAAAAA突然A挂啦,BBBBBBBBBBBBBB..... upstream ...

  3. 8VC Venture Cup 2016 - Final Round (Div. 2 Edition) C. XOR Equation 数学

    C. XOR Equation 题目连接: http://www.codeforces.com/contest/635/problem/C Description Two positive integ ...

  4. sql server 博客:不胜人生一场醉

    http://blog.csdn.net/baoqiangwang/article/category/604358

  5. 给XC2440开发板烧写程序的N种方式

    转:http://blog.chinaunix.net/uid-22030783-id-3420080.html 给XC2440开发板烧写程序非常灵活,总结起来有这么几种方式:   空片烧写(flas ...

  6. java通过UUID生成16位唯一订单号

    import java.util.UUID; public class T { public static String getOrderIdByUUId() { int machineId = 1; ...

  7. Git 对比 SVN

    转自:http://www.aqee.net/5-fundamental-differences-between-git-svn/ 我是一开始就用Mercurial, Git这类的系统.(现在已经百分 ...

  8. [转]SQL Server表分区

    本文转自:http://www.cnblogs.com/knowledgesea/p/3696912.html 什么是表分区 一般情况下,我们建立数据库表时,表数据都存放在一个文件里. 但是如果是分区 ...

  9. go语言基础之Printf和Println的区别

    1.示例 package main //必须有一个main包 import "fmt" func main() { a := 10 //一段一段处理,自动加换行 fmt.Print ...

  10. 关于onclick的执行原理

    js 或者 jQuery 为文档某一节点添加onclick事件的时候,添加的onclick事件会添加在原节点的onclick事件之后,不会覆盖之前的onclick事件  如果不想让原onclick事件 ...