用Vert.x shiro jdbcRealm对restful api鉴权

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

相关推荐