Browse Source

jwt + shiro demo

Rlax 7 years ago
parent
commit
716720d95b

+ 2 - 2
README.md

@@ -15,12 +15,12 @@
 
 ### 技术选型
 
- - 核心框架:jboot 1.4.0/jfinal 3.3
+ - 核心框架:jboot 1.4.4/jfinal 3.3
  - 模版引擎:jfinal enjoy
  - 注册中心:consul/zookeeper
  - RPC:motan/dubbo
  - RPC治理:motan-manager
- - 安全框架:shiro/jwt [jwt多设备token demo](./jboot-b2c/README.md)
+ - 安全框架:shiro/jwt [jwt + shiro 无状态认证授权 demo](./jboot-b2c/README.md)
  - 缓存框架:ehcache/redis
  - 容错隔离:hystrix
  - 调用监控:hystrix-dashboard

+ 0 - 41
jboot-admin-base/src/main/java/io/jboot/admin/base/plugin/jwt/JwtToken.java

@@ -1,41 +0,0 @@
-package io.jboot.admin.base.plugin.jwt;
-
-import java.io.Serializable;
-
-/**
- * JwtToken
- * @author Rlax
- *
- */
-public class JwtToken implements Serializable {
-
-    public JwtToken() {
-
-    }
-
-    public JwtToken(String userId, String version) {
-        this.userId = userId;
-        this.version = version;
-    }
-
-    /** 用户id */
-    private String userId;
-    /** 版本号 */
-    private String version;
-
-    public String getUserId() {
-        return userId;
-    }
-
-    public void setUserId(String userId) {
-        this.userId = userId;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public void setVersion(String version) {
-        this.version = version;
-    }
-}

+ 59 - 0
jboot-admin-base/src/main/java/io/jboot/admin/base/plugin/jwt/shiro/JwtAuthenticationToken.java

@@ -0,0 +1,59 @@
+package io.jboot.admin.base.plugin.jwt.shiro;
+
+import org.apache.shiro.authc.AuthenticationToken;
+
+import java.util.List;
+
+/**
+ * jwt shiro token
+ * @author Rlax
+ *
+ */
+public class JwtAuthenticationToken implements AuthenticationToken {
+
+    /** 用户id */
+    private String userId;
+
+    /** token */
+    private String token;
+
+    /** 角色 */
+    private List<String> roles;
+
+    /** 权限 */
+    private List<String> permissions;
+
+    @Override
+    public Object getPrincipal() {
+        return userId;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return token;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List<String> roles) {
+        this.roles = roles;
+    }
+
+    public List<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(List<String> permissions) {
+        this.permissions = permissions;
+    }
+}

+ 35 - 0
jboot-admin-base/src/main/java/io/jboot/admin/base/plugin/jwt/shiro/JwtShiroAuthzBridge.java

@@ -0,0 +1,35 @@
+package io.jboot.admin.base.plugin.jwt.shiro;
+
+import com.jfinal.core.Controller;
+import io.jboot.component.jwt.JwtShiroBridge;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+import java.util.Map;
+
+/**
+ * jwt shiro bridge
+ * @author Rlax
+ *
+ */
+public class JwtShiroAuthzBridge implements JwtShiroBridge {
+
+    private final static String USER_ID = "userId";
+    private final static String ROLES = "ROLE_LIST";
+    private final static String PERMISSIONS = "PERMISSIONS";
+
+    @Override
+    public Subject buildSubject(Map jwtParas, Controller controller) {
+        String userId = (String) jwtParas.get(USER_ID);
+
+        JwtAuthenticationToken token = new JwtAuthenticationToken();
+        token.setUserId(userId);
+        token.setToken(userId);
+
+        Subject subject = SecurityUtils.getSubject();
+        subject.login(token);
+
+        return subject;
+    }
+
+}

+ 23 - 0
jboot-admin-base/src/main/java/io/jboot/admin/base/plugin/jwt/shiro/JwtSubjectFactory.java

@@ -0,0 +1,23 @@
+package io.jboot.admin.base.plugin.jwt.shiro;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
+
+/**
+ * jwt 不创建 session
+ * @author Rlax
+ *
+ */
+public class JwtSubjectFactory extends DefaultWebSubjectFactory {
+
+    @Override
+    public Subject createSubject(SubjectContext context) {
+        if (context.getAuthenticationToken() instanceof JwtAuthenticationToken) {
+            // jwt 不创建 session
+            context.setSessionCreationEnabled(false);
+        }
+
+        return super.createSubject(context);
+    }
+}

+ 8 - 2
jboot-b2c/README.md

@@ -1,3 +1,9 @@
-### JWT 多设备token支持
+### jwt + shiro 认证授权 demo
+
+登录后返回 jwt,配合 shiro 完成认证。
+
+shiro完全无状态化,不创建session,无cookie环境下使用。
+
+实现 jwt 退出机制,退出后 jwt 将被加入黑名单,保证安全。
+
 
-![jwt](../doc/img/jwt.png)

+ 11 - 18
jboot-b2c/src/main/java/io/jboot/b2c/controller/MainController.java

@@ -1,16 +1,14 @@
 package io.jboot.b2c.controller;
 
-import com.jfinal.aop.Before;
 import com.jfinal.kit.Ret;
 import io.jboot.Jboot;
 import io.jboot.admin.base.common.CacheKey;
 import io.jboot.admin.base.common.RestResult;
 import io.jboot.admin.base.exception.BusinessException;
 import io.jboot.admin.base.interceptor.NotNullPara;
-import io.jboot.admin.base.plugin.jwt.JwtToken;
 import io.jboot.admin.base.web.base.BaseController;
-import io.jboot.b2c.support.JwtTokenInterceptor;
 import io.jboot.web.controller.annotation.RequestMapping;
+import org.apache.shiro.authz.annotation.RequiresAuthentication;
 
 
 /**
@@ -21,9 +19,9 @@ import io.jboot.web.controller.annotation.RequestMapping;
 @RequestMapping("/")
 public class MainController extends BaseController {
 
-    @Before(JwtTokenInterceptor.class)
+    @RequiresAuthentication
     public void index() {
-        renderJson(RestResult.buildSuccess(Ret.create("userId", getJwtPara("userId")).set("version", getJwtPara("version"))));
+        renderJson(RestResult.buildSuccess(Ret.create("userId", getJwtPara("userId"))));
     }
 
     /**
@@ -39,19 +37,13 @@ public class MainController extends BaseController {
     @NotNullPara({"loginName", "pwd"})
     public void postLogin(String loginName, String pwd) {
 
-        // 实际项目这里应该调用service
+        // 此处为 demo , 实际项目这里应该调用service 判断
         if ("user1".equals(loginName) && "123123".equals(pwd)) {
-            // 此处1 为用户id,实际项目中,使用服务查询id
-            JwtToken jwtToken =  Jboot.me().getCache().get(CacheKey.CACHE_JWT_TOKEN, String.valueOf(1L));
-            if (jwtToken == null) {
-                // 新创建 jwt token,此处为 demo
-                jwtToken = new JwtToken(String.valueOf(1L), String.valueOf(System.currentTimeMillis()));
-                // token 缓存 默认 2小时
-                Jboot.me().getCache().put(CacheKey.CACHE_JWT_TOKEN, jwtToken.getUserId(), jwtToken, 2 * 60 * 60);
-            }
+            String userId = "user1";
 
-            setJwtAttr("userId", jwtToken.getUserId());
-            setJwtAttr("version", jwtToken.getVersion());
+            // 登录成功移除用户退出标识
+            Jboot.me().getCache().remove(CacheKey.CACHE_JWT_TOKEN, userId);
+            setJwtAttr("userId", userId);
         } else {
             throw new BusinessException("用户名密码错误");
         }
@@ -59,9 +51,10 @@ public class MainController extends BaseController {
         renderJson(RestResult.buildSuccess("登录成功"));
     }
 
-    @Before(JwtTokenInterceptor.class)
+    @RequiresAuthentication
     public void logout() {
-        Jboot.me().getCache().remove(CacheKey.CACHE_JWT_TOKEN, getJwtPara("userId"));
+        // 退出后将 jwt 加入黑名单
+        Jboot.me().getCache().put(CacheKey.CACHE_JWT_TOKEN, getJwtPara("userId"), getJwtPara("userId"), 2 * 60 * 60);
         renderJson(RestResult.buildSuccess("退出成功"));
     }
 }

+ 50 - 0
jboot-b2c/src/main/java/io/jboot/b2c/support/JwtAuthorizingRealm.java

@@ -0,0 +1,50 @@
+package io.jboot.b2c.support;
+
+import io.jboot.Jboot;
+import io.jboot.admin.base.common.CacheKey;
+import io.jboot.admin.base.plugin.jwt.shiro.JwtAuthenticationToken;
+import io.jboot.admin.base.plugin.shiro.ShiroCacheUtils;
+import io.jboot.utils.StringUtils;
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ * JwtAuthorizingRealm
+ * @author Rlax
+ *
+ */
+public class JwtAuthorizingRealm extends AuthorizingRealm {
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JwtAuthenticationToken;
+    }
+
+    @Override
+    public void setCacheManager(CacheManager cacheManager) {
+        super.setCacheManager(cacheManager);
+        ShiroCacheUtils.setCacheManager(cacheManager);
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) token;
+        String uid = (String) jwtToken.getPrincipal();
+
+        String uidCache = Jboot.me().getCache().get(CacheKey.CACHE_JWT_TOKEN, uid);
+        if (StringUtils.isNotBlank(uidCache)) {
+            /** 说明改 token 已被加入黑名单 */
+            throw new UnknownAccountException();
+        }
+
+        return new SimpleAuthenticationInfo(uid, jwtToken.getCredentials(), this.getName());
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        return null;
+    }
+}

+ 0 - 49
jboot-b2c/src/main/java/io/jboot/b2c/support/JwtTokenInterceptor.java

@@ -1,49 +0,0 @@
-package io.jboot.b2c.support;
-
-import com.jfinal.aop.Interceptor;
-import com.jfinal.aop.Invocation;
-import io.jboot.Jboot;
-import io.jboot.admin.base.common.CacheKey;
-import io.jboot.admin.base.exception.BusinessException;
-import io.jboot.admin.base.plugin.jwt.JwtToken;
-import io.jboot.utils.StringUtils;
-import io.jboot.web.controller.JbootController;
-
-/**
- * jwt 拦截器
- * @author Rlax
- *
- */
-public class JwtTokenInterceptor implements Interceptor {
-
-    @Override
-    public void intercept(Invocation inv) {
-
-        if (inv.getController() instanceof JbootController) {
-            JbootController c = (JbootController) inv.getController();
-            String userId = c.getJwtPara("userId");
-            String version = c.getJwtPara("version");
-
-            if (StringUtils.isBlank(userId) || StringUtils.isBlank(version) ) {
-                throw new BusinessException("not auth");
-            }
-
-            // 此处1L 为测试,实际会从服务获取
-            if (!"1".equals(userId)) {
-                throw new BusinessException("not auth");
-            }
-
-            JwtToken jwtToken =  Jboot.me().getCache().get(CacheKey.CACHE_JWT_TOKEN, userId);
-            if (jwtToken == null) {
-                throw new BusinessException("not auth");
-            }
-
-            if (!jwtToken.getVersion().equals(version)) {
-                throw new BusinessException("not auth");
-            }
-        }
-
-        inv.invoke();
-    }
-
-}

+ 1 - 0
jboot-b2c/src/main/resources/jboot.properties

@@ -106,4 +106,5 @@ jboot.web.jwt.httpHeaderName=Jwt
 jboot.web.jwt.secret=wtSB6bFrfJWLBmfhtyJbpF1L17F7XrGX
 # 60 * 60 * 24 * 7 * 1000
 jboot.web.jwt.validityPeriod=604800000
+jboot.web.jwt.jwtShiroBridge=io.jboot.admin.base.plugin.jwt.shiro.JwtShiroAuthzBridge
 #---------------------------------------------------------------------------------#

+ 31 - 0
jboot-b2c/src/main/resources/shiro.ini

@@ -0,0 +1,31 @@
+#不用spring的时候可以用这个
+[main]
+
+#cache Manager
+shiroCacheManager = io.jboot.component.shiro.cache.JbootShiroCacheManager
+securityManager.cacheManager = $shiroCacheManager
+
+#realm
+dbRealm=io.jboot.b2c.support.JwtAuthorizingRealm
+dbRealm.authorizationCacheName=shiro-authorizationCache
+
+securityManager.realm=$dbRealm
+
+#session manager
+sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
+sessionManager.sessionValidationSchedulerEnabled=false
+
+#use jwt
+subjectFactory=io.jboot.admin.base.plugin.jwt.shiro.JwtSubjectFactory
+securityManager.subjectFactory=$subjectFactory
+securityManager.sessionManager=$sessionManager
+
+#session storage false
+securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled=false
+
+shiro.loginUrl =/login
+
+
+
+
+

+ 1 - 1
pom.xml

@@ -13,7 +13,7 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <jboot.version>1.4.0</jboot.version>
+        <jboot.version>1.4.4</jboot.version>
         <slf4j-log4j12.version>1.7.25</slf4j-log4j12.version>
         <logback.version>1.1.11</logback.version>
     </properties>