本文旨在用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>