本文旨在用Vert.x,shiro JdbcRealm开发一个对restfu api进行鉴权的demo
Vert.x:参看 http://vertx.io
shiro:参看 http://shiro.apache.org/
业务逻辑很简单,就是实现用户登录验证,然后对restful api进行鉴权。
数据库用mysql。
数据库名:myshiro
数据表:
-- ----------------------------
-- Table structure for t_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` int(11) NOT NULL,
`permission` varchar(255) NOT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` int(11) NOT NULL,
`role_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
后台代码:
package com.wof.realtime.apigateway;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.tomcat.jdbc.pool.DataSource;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.shiro.ShiroAuth;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import io.vertx.ext.web.handler.AuthHandler;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CookieHandler;
import io.vertx.ext.web.handler.RedirectAuthHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.handler.UserSessionHandler;
import io.vertx.ext.web.sstore.LocalSessionStore;
public class ApiGatewayVerticle2 extends AbstractVerticle {
private AuthProvider authProvider;
@Override
public void start(Future<Void> startFuture) throws Exception {
// 用户权限信息-JDBC形式
JdbcRealm jdbcRealm = getJdbcRealm();
authProvider = ShiroAuth.create(vertx, jdbcRealm);
// 路由器
Router router = Router.router(vertx);
// 为所有route创建session handler
router.route().handler(BodyHandler.create());
router.route().handler(CookieHandler.create());
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)).setSessionTimeout(1000 * 60 * 1));
router.route().handler(UserSessionHandler.create(authProvider));
// 当请求中没有user session时,自动跳转到 /login
AuthHandler authHandler = RedirectAuthHandler.create(authProvider, "/login");
Set<String> authorities = new HashSet<String>();
authHandler.addAuthorities(authorities);
// 为所有需要鉴权的路由安装authHandler
router.route("/").handler(authHandler);
router.route("/api/*").handler(authHandler);
// restful api 鉴权
router.get("/api/liaota/liaota").handler(this::listLiaotaHandler);
router.put("/api/liaota/liaota/:id").handler(this::updateLiaotaHandler);
router.post("/api/liaota/liaota/").handler(this::addLiaotaHandler);
router.delete("/api/liaota/liaota/:id").handler(this::deleteLiaotaHandler);
// 登录跳转、登录验证、登出处理handler
router.get("/login").handler(this::loginHandler);
router.post("/login-auth").handler(this::loginAuthHandler);
router.get("/logout").handler(context -> {
context.clearUser();
context.response().setStatusCode(302).putHeader("Location", "/").end();
});
// 启动httpServer
vertx.createHttpServer().requestHandler(router::accept).listen(8080, h-> {
if(h.succeeded())
System.out.println("server start.");
else
h.cause().printStackTrace();
});
}
/**
* 通过JDBC获取用户、角色、权限
*
* @return
*/
private JdbcRealm getJdbcRealm(){
// 数据库连接池 此处用硬编码方式(生产环境用配置文件方式)
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/myshiro?useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("demo");
dataSource.setPassword("123456");
// 配置数据库断开后自动连接
dataSource.setLogAbandoned(true);
dataSource.setRemoveAbandoned(true);
dataSource.setRemoveAbandonedTimeout(60);
dataSource.setTestWhileIdle(true);
dataSource.setValidationQuery("select id from user where name='demo'");
// 配置jdbcRealm
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true);//true:允许查找角色的权限。false:只查找用户和角色,不会查找角色的权限。
// jdbcRealm.setAuthenticationCachingEnabled(false);//禁止缓存用户查询结果。禁止后,每次都要从数据库查询。
// jdbcRealm.setAuthorizationCachingEnabled(false);//禁止缓存角色,权限查询结果。禁止后,每次都要从数据库查询。
jdbcRealm.setCachingEnabled(false);//禁止缓存
// 修改查询数据库SQL,根据自己的数据库表结构进行修改。
jdbcRealm.setAuthenticationQuery("select password from t_user where username = ?");
jdbcRealm.setUserRolesQuery("select t_r.role_name from t_user_role t_ur "
+ "inner join t_role t_r on t_ur.role_id=t_r.id "
+ "inner join t_user t_u on t_u.id = t_ur.user_id where t_u.username = ?");
jdbcRealm.setPermissionsQuery("select permission from t_permission t_p "
+ "inner join t_role t_r on t_r.id = t_p.role_id where t_r.role_name = ?");
return jdbcRealm;
}
private void loginAuthHandler(RoutingContext context) {
HttpServerRequest req = context.request();
MultiMap params = req.formAttributes();
String username = params.get("username");
String password = params.get("password");
Session session = context.session();
JsonObject authInfo = new JsonObject().put("username", username).put("password", password);
authProvider.authenticate(authInfo, res -> {
JsonObject json = new JsonObject();
json.put("message", "loginFail");
if (res.succeeded()) {
json.put("message", "loginSuccess");
User user = res.result();
context.setUser(user);
if (session != null) {
session.regenerateId(); // 更新session id
}
}
req.response().headers().set("Content-Type", "text/html; charset=UTF-8");
req.response().end(json.encode());
});
}
private void loginHandler(RoutingContext context) {
HttpServerRequest req = context.request();
req.response().headers().set("Content-Type", "text/html; charset=UTF-8");
req.response().end("login");
}
private void listLiaotaHandler(RoutingContext context) {
context.user().isAuthorised("query", h -> {
if(h.result())
doSomething(context);
else {
authFail(context);
}
});
}
private void updateLiaotaHandler(RoutingContext context) {
context.user().isAuthorised("update", h -> {
if(h.result())
doSomething(context);
else {
authFail(context);
}
});
}
private void addLiaotaHandler(RoutingContext context) {
context.user().isAuthorised("add", h -> {
if(h.result())
doSomething(context);
else {
authFail(context);
}
});
}
private void deleteLiaotaHandler(RoutingContext context) {
context.user().isAuthorised("delete", h -> {
if(h.result())
doSomething(context);
else {
authFail(context);
}
});
}
private void doSomething(RoutingContext context){
System.out.println("鉴权通过,进行业务逻辑处理。");
JsonObject json = new JsonObject();
json.put("success", true).put("message", "业务处理完成。");
context.request().response().headers().set("Content-Type", "text/html; charset=UTF-8");
context.request().response().end(json.toString());
}
private void authFail(RoutingContext context){
JsonObject json = new JsonObject();
json.put("success", false).put("message", "无此权限。");
context.request().response().headers().set("Content-Type", "text/html; charset=UTF-8");
context.request().response().end(json.toString());
}
}
pom.xml需要引入:
<dependencies> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-auth-shiro</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> <version>8.5.11</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-juli</artifactId> <version>8.5.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> </dependencies>