用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
数据表:
1 -- ---------------------------- 2 -- Table structure for t_permission 3 -- ---------------------------- 4 DROP TABLE IF EXISTS `t_permission`; 5 CREATE TABLE `t_permission` ( 6 `id` int(11) NOT NULL, 7 `permission` varchar(255) NOT NULL, 8 `role_id` int(11) DEFAULT NULL, 9 PRIMARY KEY (`id`) 10 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 11 12 -- ---------------------------- 13 -- Table structure for t_role 14 -- ---------------------------- 15 DROP TABLE IF EXISTS `t_role`; 16 CREATE TABLE `t_role` ( 17 `id` int(11) NOT NULL, 18 `role_name` varchar(255) DEFAULT NULL, 19 PRIMARY KEY (`id`) 20 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 21 22 -- ---------------------------- 23 -- Table structure for t_user 24 -- ---------------------------- 25 DROP TABLE IF EXISTS `t_user`; 26 CREATE TABLE `t_user` ( 27 `id` int(11) NOT NULL, 28 `username` varchar(255) DEFAULT NULL, 29 `password` varchar(255) DEFAULT NULL, 30 PRIMARY KEY (`id`) 31 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 32 33 -- ---------------------------- 34 -- Table structure for t_user_role 35 -- ---------------------------- 36 DROP TABLE IF EXISTS `t_user_role`; 37 CREATE TABLE `t_user_role` ( 38 `id` int(11) NOT NULL, 39 `user_id` int(11) DEFAULT NULL, 40 `role_id` int(11) DEFAULT NULL, 41 PRIMARY KEY (`id`) 42 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 43 后台代码: 44 45
1 package cn.endv.realtime.apigateway; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 import org.apache.shiro.realm.jdbc.JdbcRealm; 7 import org.apache.tomcat.jdbc.pool.DataSource; 8 9 import io.vertx.core.AbstractVerticle; 10 import io.vertx.core.Future; 11 import io.vertx.core.MultiMap; 12 import io.vertx.core.http.HttpServer; 13 import io.vertx.core.http.HttpServerRequest; 14 import io.vertx.core.json.JsonObject; 15 import io.vertx.ext.auth.AuthProvider; 16 import io.vertx.ext.auth.User; 17 import io.vertx.ext.auth.shiro.ShiroAuth; 18 import io.vertx.ext.web.Router; 19 import io.vertx.ext.web.RoutingContext; 20 import io.vertx.ext.web.Session; 21 import io.vertx.ext.web.handler.AuthHandler; 22 import io.vertx.ext.web.handler.BodyHandler; 23 import io.vertx.ext.web.handler.CookieHandler; 24 import io.vertx.ext.web.handler.RedirectAuthHandler; 25 import io.vertx.ext.web.handler.SessionHandler; 26 import io.vertx.ext.web.handler.UserSessionHandler; 27 import io.vertx.ext.web.sstore.LocalSessionStore; 28 29 public class ApiGatewayVerticle2 extends AbstractVerticle { 30 31 private AuthProvider authProvider; 32 33 @Override 34 public void start(Future<Void> startFuture) throws Exception { 35 36 // 用户权限信息-JDBC形式 37 JdbcRealm jdbcRealm = getJdbcRealm(); 38 authProvider = ShiroAuth.create(vertx, jdbcRealm); 39 40 // 路由器 41 Router router = Router.router(vertx); 42 43 // 为所有route创建session handler 44 router.route().handler(BodyHandler.create()); 45 router.route().handler(CookieHandler.create()); 46 router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)).setSessionTimeout(1000 * 60 * 1)); 47 router.route().handler(UserSessionHandler.create(authProvider)); 48 49 // 当请求中没有user session时,自动跳转到 /login 50 AuthHandler authHandler = RedirectAuthHandler.create(authProvider, "/login"); 51 Set<String> authorities = new HashSet<String>(); 52 authHandler.addAuthorities(authorities); 53 54 // 为所有需要鉴权的路由安装authHandler 55 router.route("/").handler(authHandler); 56 router.route("/api/*").handler(authHandler); 57 58 // restful api 鉴权 59 router.get("/api/liaota/liaota").handler(this::listLiaotaHandler); 60 router.put("/api/liaota/liaota/:id").handler(this::updateLiaotaHandler); 61 router.post("/api/liaota/liaota/").handler(this::addLiaotaHandler); 62 router.delete("/api/liaota/liaota/:id").handler(this::deleteLiaotaHandler); 63 64 // 登录跳转、登录验证、登出处理handler 65 router.get("/login").handler(this::loginHandler); 66 router.post("/login-auth").handler(this::loginAuthHandler); 67 router.get("/logout").handler(context -> { 68 context.clearUser(); 69 context.response().setStatusCode(302).putHeader("Location", "/").end(); 70 }); 71 72 // 启动httpServer 73 vertx.createHttpServer().requestHandler(router::accept).listen(8080, h-> { 74 if(h.succeeded()) 75 System.out.println("server start."); 76 else 77 h.cause().printStackTrace(); 78 }); 79 } 80 81 /** 82 * 通过JDBC获取用户、角色、权限 83 * 84 * @return 85 */ 86 private JdbcRealm getJdbcRealm(){ 87 // 数据库连接池 此处用硬编码方式(生产环境用配置文件方式) 88 DataSource dataSource = new DataSource(); 89 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 90 dataSource.setUrl("jdbc:mysql://localhost:3306/myshiro?useUnicode=true&characterEncoding=utf8"); 91 dataSource.setUsername("demo"); 92 dataSource.setPassword("123456"); 93 // 配置数据库断开后自动连接 94 dataSource.setLogAbandoned(true); 95 dataSource.setRemoveAbandoned(true); 96 dataSource.setRemoveAbandonedTimeout(60); 97 dataSource.setTestWhileIdle(true); 98 dataSource.setValidationQuery("select id from user where name='demo'"); 99 100 // 配置jdbcRealm 101 JdbcRealm jdbcRealm = new JdbcRealm(); 102 jdbcRealm.setDataSource(dataSource); 103 jdbcRealm.setPermissionsLookupEnabled(true);//true:允许查找角色的权限。false:只查找用户和角色,不会查找角色的权限。 104 105 // jdbcRealm.setAuthenticationCachingEnabled(false);//禁止缓存用户查询结果。禁止后,每次都要从数据库查询。 106 // jdbcRealm.setAuthorizationCachingEnabled(false);//禁止缓存角色,权限查询结果。禁止后,每次都要从数据库查询。 107 jdbcRealm.setCachingEnabled(false);//禁止缓存 108 109 // 修改查询数据库SQL,根据自己的数据库表结构进行修改。 110 jdbcRealm.setAuthenticationQuery("select password from t_user where username = ?"); 111 jdbcRealm.setUserRolesQuery("select t_r.role_name from t_user_role t_ur " 112 + "inner join t_role t_r on t_ur.role_id=t_r.id " 113 + "inner join t_user t_u on t_u.id = t_ur.user_id where t_u.username = ?"); 114 jdbcRealm.setPermissionsQuery("select permission from t_permission t_p " 115 + "inner join t_role t_r on t_r.id = t_p.role_id where t_r.role_name = ?"); 116 117 return jdbcRealm; 118 } 119 120 private void loginAuthHandler(RoutingContext context) { 121 HttpServerRequest req = context.request(); 122 MultiMap params = req.formAttributes(); 123 String username = params.get("username"); 124 String password = params.get("password"); 125 126 Session session = context.session(); 127 JsonObject authInfo = new JsonObject().put("username", username).put("password", password); 128 129 authProvider.authenticate(authInfo, res -> { 130 JsonObject json = new JsonObject(); 131 json.put("message", "loginFail"); 132 133 if (res.succeeded()) { 134 json.put("message", "loginSuccess"); 135 User user = res.result(); 136 context.setUser(user); 137 if (session != null) { 138 session.regenerateId(); // 更新session id 139 } 140 } 141 req.response().headers().set("Content-Type", "text/html; charset=UTF-8"); 142 req.response().end(json.encode()); 143 }); 144 } 145 146 private void loginHandler(RoutingContext context) { 147 HttpServerRequest req = context.request(); 148 req.response().headers().set("Content-Type", "text/html; charset=UTF-8"); 149 req.response().end("login"); 150 } 151 152 private void listLiaotaHandler(RoutingContext context) { 153 context.user().isAuthorised("query", h -> { 154 if(h.result()) 155 doSomething(context); 156 else { 157 authFail(context); 158 } 159 }); 160 } 161 162 private void updateLiaotaHandler(RoutingContext context) { 163 context.user().isAuthorised("update", h -> { 164 if(h.result()) 165 doSomething(context); 166 else { 167 authFail(context); 168 } 169 }); 170 } 171 172 private void addLiaotaHandler(RoutingContext context) { 173 context.user().isAuthorised("add", h -> { 174 if(h.result()) 175 doSomething(context); 176 else { 177 authFail(context); 178 } 179 }); 180 } 181 182 private void deleteLiaotaHandler(RoutingContext context) { 183 context.user().isAuthorised("delete", h -> { 184 if(h.result()) 185 doSomething(context); 186 else { 187 authFail(context); 188 } 189 }); 190 } 191 192 private void doSomething(RoutingContext context){ 193 System.out.println("鉴权通过,进行业务逻辑处理。"); 194 JsonObject json = new JsonObject(); 195 json.put("success", true).put("message", "业务处理完成。"); 196 context.request().response().headers().set("Content-Type", "text/html; charset=UTF-8"); 197 context.request().response().end(json.toString()); 198 } 199 200 private void authFail(RoutingContext context){ 201 JsonObject json = new JsonObject(); 202 json.put("success", false).put("message", "无此权限。"); 203 context.request().response().headers().set("Content-Type", "text/html; charset=UTF-8"); 204 context.request().response().end(json.toString()); 205 } 206 207 }
pom.xml需要引入:
1 <dependencies> 2 <dependency> 3 <groupId>io.vertx</groupId> 4 <artifactId>vertx-core</artifactId> 5 <version>3.4.2</version> 6 </dependency> 7 <dependency> 8 <groupId>io.vertx</groupId> 9 <artifactId>vertx-web</artifactId> 10 <version>3.4.2</version> 11 </dependency> 12 <dependency> 13 <groupId>io.vertx</groupId> 14 <artifactId>vertx-auth-shiro</artifactId> 15 <version>3.4.2</version> 16 </dependency> 17 <dependency> 18 <groupId>org.apache.tomcat</groupId> 19 <artifactId>tomcat-jdbc</artifactId> 20 <version>8.5.11</version> 21 </dependency> 22 <dependency> 23 <groupId>org.apache.tomcat</groupId> 24 <artifactId>tomcat-juli</artifactId> 25 <version>8.5.11</version> 26 </dependency> 27 <dependency> 28 <groupId>mysql</groupId> 29 <artifactId>mysql-connector-java</artifactId> 30 <version>5.1.26</version> 31 </dependency> 32 </dependencies>