Browse Source

使用Shiro做登录及入口方法权限管理

Luxnk 7 years ago
parent
commit
f890461507

+ 1 - 0
conf/mvc/luxnkproject-mvc-chain.js

@@ -5,6 +5,7 @@ var chain = {
             "org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
             "org.nutz.mvc.impl.processor.EncodingProcessor",
             "org.nutz.mvc.impl.processor.ModuleProcessor",
+            "org.nutz.integration.shiro.NutShiroProcessor",
             "!org.nutz.integration.shiro.NutShiroProcessor",
             "org.nutz.mvc.impl.processor.ActionFiltersProcessor",
             "org.nutz.mvc.impl.processor.AdaptorProcessor",

+ 10 - 1
conf/shiro.ini

@@ -1,4 +1,13 @@
 [main]
+nutzdao_realm = xyz.luxnk.lproject.shiro.realm.SimpleAuthorizingRealm
+
+authc = org.nutz.integration.shiro.SimpleAuthenticationFilter
+authc.loginUrl = /user/login
+logout.redirectUrl = /user/login
 
 [urls]
-/* = anon
+/rs/* = anon
+/user/logout = logout
+/user/error = anon
+/user/login = anon
+/user/profile/active/mail = anon

+ 13 - 1
src/xyz/luxnk/lproject/module/UserModule.java

@@ -1,6 +1,7 @@
 package xyz.luxnk.lproject.module;
 
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresUser;
 import org.nutz.aop.interceptor.ioc.TransAop;
 import org.nutz.dao.Cnd;
 import org.nutz.dao.Dao;
@@ -35,8 +36,15 @@ public class UserModule extends BaseModule {
 
     @At("/")
     @Ok("jsp:jsp.user.list")    // 真实路径是 /WEB-INF/jsp/user/list.jsp
+    @RequiresUser
     public void index() {}
 
+    @GET
+    @At("/login")
+    @Filters
+    @Ok("jsp:jsp.user.login")   // 重定向到登录jsp
+    public void loginPage() {}
+
     /**
      * 统计用户数
      * @return
@@ -66,7 +74,7 @@ public class UserModule extends BaseModule {
             return re.setv("ok", false).setv("msg", "用户名或密码错误");
         } else {
             session.setAttribute("me", userId);
-            //SecurityUtils.getSubject().login(new SimpleShiroToken(userId));
+            SecurityUtils.getSubject().login(new SimpleShiroToken(userId));
             return re.setv("ok", true);
         }
     }
@@ -125,6 +133,7 @@ public class UserModule extends BaseModule {
      * @return
      */
     @At
+    @RequiresUser
     public Object add(@Param("..")UserInfo userInfo) {  // 两个点号是表示按对象属性一一设置
         NutMap re = new NutMap();
         String msg = checkUser(userInfo, true);
@@ -142,6 +151,7 @@ public class UserModule extends BaseModule {
      * @return
      */
     @At
+    @RequiresUser
     public Object update(@Param("password")String password, @Attr("me")String me) {
         NutMap re = new NutMap();
         if (Strings.isBlank(password) || password.length() < 6) {
@@ -159,6 +169,7 @@ public class UserModule extends BaseModule {
      */
     @At
     @Aop(TransAop.READ_COMMITTED)
+    @RequiresUser
     public Object delete(@Param("id")String id, @Attr("me")String me) {
         if (me.equals(id)) {
             return new NutMap().setv("ok", false).setv("msg", "不能删除当前用户!");
@@ -175,6 +186,7 @@ public class UserModule extends BaseModule {
      * @return
      */
     @At
+    @RequiresUser
     public Object query(@Param("name")String name, @Param("..")Pager pager) {
         Cnd cnd = Strings.isBlank(name)? null : Cnd.where("name", "like", "%" + name + "%");
         QueryResult qr = new QueryResult();

+ 6 - 0
src/xyz/luxnk/lproject/module/UserProfileModule.java

@@ -1,5 +1,6 @@
 package xyz.luxnk.lproject.module;
 
+import org.apache.shiro.authz.annotation.RequiresUser;
 import org.nutz.dao.Chain;
 import org.nutz.dao.Cnd;
 import org.nutz.dao.DaoException;
@@ -41,6 +42,7 @@ public class UserProfileModule extends BaseModule {
     @At("/")
     @GET
     @Ok("jsp:jsp.user.profile")
+    @RequiresUser
     public UserProfile index(@Attr(scope = Scope.SESSION, value = "me")String userId) {
         return get(userId);
     }
@@ -51,6 +53,7 @@ public class UserProfileModule extends BaseModule {
      * @return
      */
     @At
+    @RequiresUser
     public UserProfile get(@Attr(scope = Scope.SESSION, value = "me")String userId) {
         UserProfile profile = Daos.ext(dao, FieldFilter.locked(UserProfile.class, "avatar")).fetch(UserProfile.class, userId);
         if (profile == null) {
@@ -71,6 +74,7 @@ public class UserProfileModule extends BaseModule {
     @At
     @AdaptBy(type = JsonAdaptor.class)
     @Ok("void")
+    @RequiresUser
     public void update(@Param("..")UserProfile profile, @Attr(scope = Scope.SESSION, value = "me")String userId) {
         if (profile == null)
             return;
@@ -110,6 +114,7 @@ public class UserProfileModule extends BaseModule {
     @POST
     @Ok(">>:/user/profile")
     @At("/avatar")
+    @RequiresUser
     public void uploadAvatar(@Param("file")TempFile tf, @Attr(scope = Scope.SESSION, value = "me")String userId, AdaptorErrorContext err) {
         String msg = null;
         if (err != null && err.getAdaptorErr() != null) {
@@ -165,6 +170,7 @@ public class UserProfileModule extends BaseModule {
      */
     @At("/active/mail")
     @POST
+    @RequiresUser
     public Object activeMail(@Attr(scope = Scope.SESSION, value = "me")String userId, HttpServletRequest req) {
         NutMap re = new NutMap();
         UserProfile profile = get(userId);

+ 110 - 0
src/xyz/luxnk/lproject/shiro/realm/SimpleAuthorizingRealm.java

@@ -0,0 +1,110 @@
+package xyz.luxnk.lproject.shiro.realm;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.nutz.dao.Dao;
+import org.nutz.integration.shiro.SimpleShiroToken;
+import org.nutz.mvc.Mvcs;
+import xyz.luxnk.lproject.bean.Permission;
+import xyz.luxnk.lproject.bean.Role;
+import xyz.luxnk.lproject.bean.UserInfo;
+
+public class SimpleAuthorizingRealm extends AuthorizingRealm {
+
+    protected Dao dao;  // ShiroFilter先于NutFilter初始化,所以无法使用注入功能
+
+    public Dao dao() {
+        if (dao == null) {
+            dao = Mvcs.ctx().getDefaultIoc().get(Dao.class, "dao");
+            return dao;
+        }
+        return dao;
+    }
+
+    public void setDao(Dao dao) {
+        this.dao = dao;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+
+        if (principalCollection == null) {
+            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
+        }
+        String userId = (String) principalCollection.getPrimaryPrincipal();
+        UserInfo userInfo = dao().fetch(UserInfo.class, userId);
+        if (userInfo == null)
+            return null;
+        if (userInfo.isLocked())
+            throw new LockedAccountException("Account [" + userInfo.getUsername() + "] is locked.");
+
+        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
+        userInfo = dao().fetchLinks(userInfo, null);
+        if (userInfo.getRoles() != null) {
+            dao().fetchLinks(userInfo.getRoles(), null);
+            for (Role role : userInfo.getRoles()) {
+                auth.addRole(role.getName());
+                if (role.getPermissions() != null) {
+                    for (Permission p : role.getPermissions()) {
+                        auth.addStringPermission(p.getName());
+                    }
+                }
+            }
+        }
+        if (userInfo.getPermissions() != null) {    // 特许、临时分配的权限
+            for (Permission p : userInfo.getPermissions()) {
+                auth.addStringPermission(p.getName());
+            }
+        }
+
+        return auth;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
+        SimpleShiroToken upToken = (SimpleShiroToken) authenticationToken;
+
+        // upToken.getPrincipal()的返回值就是SimpleShiroToken构造方法传入的值
+        // 可以是int也可以是UserInfo类实例,或任何你希望的值,自行处理一下就好了
+        UserInfo userInfo = dao().fetch(UserInfo.class, (String)upToken.getPrincipal());
+        if (userInfo == null)
+            return null;
+        if (userInfo.isLocked())
+            throw new LockedAccountException("Account [" + userInfo.getUsername() + "] is locked.");
+        return new SimpleAccount(userInfo.getId(), userInfo.getPassword(), getName());
+    }
+
+    /**
+     * 覆盖父类的验证,直接pass
+     * 在Shiro内做验证的话,出错了都不知道哪里错
+     * @param token
+     * @param info
+     * @throws AuthenticationException
+     */
+    @Override
+    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { }
+
+    public SimpleAuthorizingRealm() {
+        this(null, null);
+    }
+
+    public SimpleAuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
+        super(cacheManager, matcher);
+        setAuthenticationTokenClass(SimpleShiroToken.class);    // 非常非常重要,与SecurityUtils.getSubject().login是对应关系!!!
+    }
+
+    public SimpleAuthorizingRealm(CacheManager cacheManager) {
+        this(cacheManager, null);
+    }
+
+    public SimpleAuthorizingRealm(CredentialsMatcher matcher) {
+        this(null, matcher);
+    }
+
+}

+ 68 - 0
web/WEB-INF/jsp/user/login.jsp

@@ -0,0 +1,68 @@
+<%-- Created by IntelliJ IDEA. --%>
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<html>
+  <head>
+    <title>Nutz Demo</title>
+    <script src="${base}/js/jquery-1.8.3.min.js"></script>
+  </head>
+  <body>
+
+    <div id="login_div">
+      <form action="#" id="loginForm" method="post">
+        用户名 <input name="username" type="text" value="admin" />
+        密码 <input name="password" type="password" value="123456" />
+        验证码 <input name="captcha" type="text" value="" />
+        <img id="captcha_img" onclick="next_captcha(); return false;" src="${base}/captcha/next" />
+        <script>
+          function next_captcha() {
+              $('#captcha_img').attr('src', '${base}/captcha/next?_=' + new Date().getTime());
+          }
+        </script>
+        <button type="button" id="login_button">提交</button>
+      </form>
+    </div>
+    <div id="user_info_div">
+      <p id="userInfo"></p>
+      <a href="${base}/user">用户列表</a><br />
+      <a href="${base}/user/profile">个人信息</a><br />
+      <a href="${base}/user/logout">登出</a>
+    </div>
+
+    <script>
+      var me = '<%=session.getAttribute("me")%>';
+      var base = '${base}';
+      $(function () {
+          $('#login_button').click(function () {
+              console.log('尝试登录……');
+              $.ajax({
+                  url: base + '/user/login',
+                  type: 'post',
+                  data: $('#loginForm').serialize(),
+                  error: function (request) {
+                      alert('Connection error');
+                  },
+                  dataType: 'json',
+                  success: function (data) {
+                      if (data && data.ok) {
+                          alert('登录成功');
+                          location.reload();
+                      } else {
+                          alert(data.msg);
+                      }
+                  }
+              });
+              return false;
+          });
+          if (me != 'null') {
+              $('#login_div').hide();
+              $('#userInfo').html('您的Id是' + me);
+              $('#user_info_div').show();
+          } else {
+              $('#login_div').show();
+              $('#user_info_div').hide();
+          }
+      });
+    </script>
+
+  </body>
+</html>